【03】SpringBoot3 MybatisPlus BaseMapper 源码分析

SpringBoot3 MybatisPlus BaseMapper 源码分析

  • 前言
  • [BaseMapper 源码](#BaseMapper 源码)
  • [BaseMapper 源码分析](#BaseMapper 源码分析)
    • [一、BaseMapper 是什么?解决了什么问题?](#一、BaseMapper 是什么?解决了什么问题?)
    • [二、BaseMapper 的整体设计思路](#二、BaseMapper 的整体设计思路)
      • [1️⃣ 接口 + default 方法](#1️⃣ 接口 + default 方法)
      • [2️⃣ 一切围绕实体 T](#2️⃣ 一切围绕实体 T)
      • [3️⃣ 运行期而不是编译期绑定 SQL](#3️⃣ 运行期而不是编译期绑定 SQL)
    • [三、CRUD 能力拆解(源码级)](#三、CRUD 能力拆解(源码级))
    • 四、Create:插入相关方法
      • [1️⃣ 单条插入:insert](#1️⃣ 单条插入:insert)
      • [2️⃣ 批量插入(3.5.7+)](#2️⃣ 批量插入(3.5.7+))
    • 五、Delete:删除的"复杂度天花板"
      • [1️⃣ deleteById 的隐藏逻辑](#1️⃣ deleteById 的隐藏逻辑)
      • [2️⃣ 批量删除 deleteByIds](#2️⃣ 批量删除 deleteByIds)
    • 六、Update:更新的两条主线
      • [1️⃣ updateById](#1️⃣ updateById)
      • [2️⃣ update + Wrapper](#2️⃣ update + Wrapper)
    • 七、Read:查询方法全景图
      • [1️⃣ selectOne 的异常设计](#1️⃣ selectOne 的异常设计)
      • [2️⃣ exists 的"最佳实践"](#2️⃣ exists 的“最佳实践”)
      • [3️⃣ selectObjs:只查一列](#3️⃣ selectObjs:只查一列)
    • [八、分页设计:IPage 的"责任转移"](#八、分页设计:IPage 的“责任转移”)
    • 九、insertOrUpdate:看似简单,其实很"重"
      • [1️⃣ 单条 insertOrUpdate](#1️⃣ 单条 insertOrUpdate)
      • [2️⃣ 批量 insertOrUpdate](#2️⃣ 批量 insertOrUpdate)
    • [十、BaseMapper 的优点 & 易踩坑](#十、BaseMapper 的优点 & 易踩坑)
      • [✅ 优点总结](#✅ 优点总结)
      • [⚠️ 常见坑点](#⚠️ 常见坑点)
    • [十一、什么时候"不要用" BaseMapper?](#十一、什么时候“不要用” BaseMapper?)
    • 十二、总结

前言

JDK 版本:21.0.10

MyBatis Plus 版本:3.5.15

Spring Boot 版本:3.2.4

BaseMapper 源码

java 复制代码
/**
 * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
 * <p>这个 Mapper 支持 id 泛型</p>
 *
 * @author hubin
 * @since 2016-01-23
 */
public interface BaseMapper<T> extends Mapper<T> {

    /**
     * 插入一条记录
     *
     * @param entity 实体对象
     */
    int insert(T entity);

    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    default int deleteById(Serializable id) {
        return deleteById(id, true);
    }

    /**
     * 根据 ID 删除
     *
     * @param useFill 是否填充
     * @param obj      主键ID或实体
     * @since 3.5.7
     */
    default int deleteById(Object obj, boolean useFill) {
        Class<?> entityClass = ReflectionKit.getSuperClassGenericType(getClass(), BaseMapper.class, 0);
        Assert.notNull(entityClass, "entityClass must not be null");
        if (!entityClass.isAssignableFrom(obj.getClass()) && useFill) {
            TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
            Assert.notNull(tableInfo, "Can not get TableInfo for entity " + entityClass);
            String keyProperty = tableInfo.getKeyProperty();
            Assert.notEmpty(keyProperty, "The current table has no primary key.");
            if (tableInfo.isWithLogicDelete() && tableInfo.isWithUpdateFill()) {
                T instance = tableInfo.newInstance();
                tableInfo.setPropertyValue(instance, keyProperty, OgnlOps.convertValue(obj, tableInfo.getKeyType()));
                return this.deleteById(instance);
            }
        }
        MapperProxyMetadata mapperProxyMetadata = MybatisUtils.getMapperProxy(this);
        SqlSession sqlSession = mapperProxyMetadata.getSqlSession();
        return sqlSession.delete(mapperProxyMetadata.getMapperInterface().getName() + Constants.DOT + SqlMethod.DELETE_BY_ID.getMethod(), obj);
    }

    /**
     * 根据实体(ID)删除
     *
     * @param entity 实体对象
     * @since 3.4.4
     */
    int deleteById(T entity);

    /**
     * 根据 columnMap 条件,删除记录
     *
     * @param columnMap 表字段 map 对象
     */
    default int deleteByMap(Map<String, Object> columnMap) {
        return this.delete(Wrappers.<T>query().allEq(columnMap));
    }

    /**
     * 根据 entity 条件,删除记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);


    /**
     * 删除(根据ID或实体 批量删除)
     *
     * @param idList 主键ID列表或实体列表(不能为 null 以及 empty)
     * @deprecated 3.5.7 {@link #deleteByIds(Collection)}
     */
    @Deprecated
    default int deleteBatchIds(@Param(Constants.COLL) Collection<?> idList) {
        return deleteByIds(idList);
    }


    /**
     * 删除(根据ID或实体 批量删除)
     *
     * @param idList 主键ID列表或实体列表(不能为 null 以及 empty)
     * @since 3.5.7
     */
    default int deleteByIds(@Param(Constants.COLL) Collection<?> idList) {
        return deleteByIds(idList, true);
    }

    /**
     * 删除(根据ID或实体 批量删除)
     * <p>
     * 普通删除: DELETE FROM h2user WHERE id IN ( ? , ? )
     * </p>
     * <p>
     * 逻辑删除: UPDATE h2user SET deleted=1 WHERE id IN ( ? , ? ) AND deleted=0
     * </p>
     * <p>
     * 逻辑删除(填充): UPDATE h2user SET delete_user = 'xxx', deleted=1 WHERE id IN ( ? , ? ) AND deleted=0
     *     <ul>注意:无论参数为id还是实体,填充参数只会以方法追加的et参数为准.<ul/>
     * </p>
     *
     * @param collections 主键ID列表或实体列表(不能为 null 以及 empty)
     * @param useFill     逻辑删除下是否填充
     * @since 3.5.7
     */
    default int deleteByIds(@Param(Constants.COLL) Collection<?> collections, boolean useFill) {
        if (CollectionUtils.isEmpty(collections)) {
            return 0;
        }
        MapperProxyMetadata mapperProxyMetadata = MybatisUtils.getMapperProxy(this);
        Class<?> entityClass = ReflectionKit.getSuperClassGenericType(getClass(), BaseMapper.class, 0);
        Assert.notNull(entityClass, "entityClass must not be null");
        SqlSession sqlSession = mapperProxyMetadata.getSqlSession();
        Class<?> mapperInterface = mapperProxyMetadata.getMapperInterface();
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        Assert.notNull(tableInfo, "Can not get TableInfo for entity " + entityClass);
        Map<String, Object> params = new HashMap<>();
        if (useFill && tableInfo.isWithLogicDelete() && tableInfo.isWithUpdateFill()) {
            params.put(Constants.MP_FILL_ET, tableInfo.newInstance());
        }
        params.put(Constants.COLL, collections);
        return sqlSession.delete(mapperInterface.getName() + StringPool.DOT + SqlMethod.DELETE_BY_IDS.getMethod(), params);
    }

    /**
     * 根据 ID 修改
     *
     * @param entity 实体对象
     */
    int updateById(@Param(Constants.ENTITY) T entity);

    /**
     * 根据 whereEntity 条件,更新记录
     *
     * @param entity        实体对象 (set 条件值,可以为 null,当entity为null时,无法进行自动填充)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    /**
     * 根据 Wrapper 更新记录
     * <p>此方法无法进行自动填充,如需自动填充请使用{@link #update(Object, Wrapper)}</p>
     *
     * @param updateWrapper {@link UpdateWrapper} or {@link LambdaUpdateWrapper}
     * @since 3.5.4
     */
    default int update(@Param(Constants.WRAPPER) Wrapper<T> updateWrapper) {
        return update(null, updateWrapper);
    }

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    T selectById(Serializable id);

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     * @return 数据列表
     */
    List<T> selectByIds(@Param(Constants.COLL) Collection<? extends Serializable> idList);

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     * @return 数据列表
     * @deprecated 3.5.8
     */
    default List<T> selectBatchIds(@Param(Constants.COLL) Collection<? extends Serializable> idList) {
        return selectByIds(idList);
    }

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList        idList 主键ID列表(不能为 null 以及 empty)
     * @param resultHandler resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.8
     */
    void selectByIds(@Param(Constants.COLL) Collection<? extends Serializable> idList, ResultHandler<T> resultHandler);

    /**
     * @param idList        idList 主键ID列表(不能为 null 以及 empty)
     * @param resultHandler resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     * @deprecated 3.5.8
     */
    @Deprecated
    default void selectBatchIds(@Param(Constants.COLL) Collection<? extends Serializable> idList, ResultHandler<T> resultHandler) {
        selectByIds(idList, resultHandler);
    }

    /**
     * 查询(根据 columnMap 条件)
     *
     * @param columnMap 表字段 map 对象
     */
    default List<T> selectByMap(Map<String, Object> columnMap) {
        return this.selectList(Wrappers.<T>query().allEq(columnMap));
    }

    /**
     * 查询(根据 columnMap 条件)
     *
     * @param columnMap     表字段 map 对象
     * @param resultHandler resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    default void selectByMap(Map<String, Object> columnMap, ResultHandler<T> resultHandler) {
        this.selectList(Wrappers.<T>query().allEq(columnMap), resultHandler);
    }

    /**
     * 根据 entity 条件,查询一条记录
     * <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常</p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
        return this.selectOne(queryWrapper, true);
    }

    /**
     * 根据 entity 条件,查询一条记录,现在会根据{@code throwEx}参数判断是否抛出异常,如果为false就直接返回一条数据
     * <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常</p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     * @param throwEx      boolean 参数,为true如果存在多个结果直接抛出异常
     */
    default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, boolean throwEx) {
        List<T> list = this.selectList(queryWrapper);
        int size = list.size();
        if (size == 1) {
            return list.get(0);
        } else if (size > 1) {
            if (throwEx) {
                throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + size);
            }
            return list.get(0);
        }
        return null;
    }

    /**
     * 根据 Wrapper 条件,判断是否存在记录
     *
     * @param queryWrapper 实体对象封装操作类
     * @return 是否存在记录
     */
    default boolean exists(Wrapper<T> queryWrapper) {
        Long count = this.selectCount(queryWrapper);
        return null != count && count > 0;
    }

    /**
     * 根据 Wrapper 条件,查询总记录数
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录
     *
     * @param queryWrapper  实体对象封装操作类(可以为 null)
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    void selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<T> resultHandler);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     * @since 3.5.3.2
     */
    List<T> selectList(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     * @param page          分页查询条件
     * @param queryWrapper  实体对象封装操作类(可以为 null)
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    void selectList(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<T> resultHandler);


    /**
     * 根据 Wrapper 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类
     */
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     *
     * @param queryWrapper  实体对象封装操作类
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    void selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<Map<String, Object>> resultHandler);

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     * @since 3.5.3.2
     */
    List<Map<String, Object>> selectMaps(IPage<? extends Map<String, Object>> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page          分页查询条件
     * @param queryWrapper  实体对象封装操作类
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    void selectMaps(IPage<? extends Map<String, Object>> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<Map<String, Object>> resultHandler);

    /**
     * 根据 Wrapper 条件,查询全部记录
     * <p>注意: 只返回第一个字段的值</p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    <E> List<E> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     * <p>注意: 只返回第一个字段的值</p>
     *
     * @param queryWrapper  实体对象封装操作类(可以为 null)
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    <E> void selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<E> resultHandler);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    default <P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
        page.setRecords(selectList(page, queryWrapper));
        return page;
    }

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     */
    default <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
        page.setRecords(selectMaps(page, queryWrapper));
        return page;
    }

    /**
     * 主键存在更新记录,否插入一条记录
     *
     * @param entity 实体对象 (不能为空)
     * @since 3.5.7
     */
    default boolean insertOrUpdate(T entity) {
        Class<?> entityClass = ReflectionKit.getSuperClassGenericType(getClass(), BaseMapper.class, 0);
        Assert.notNull(entityClass, "entityClass must not be null");
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        Assert.notNull(tableInfo, "Can not get TableInfo for entity " + entityClass);
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notEmpty(keyProperty, "The current table has no primary key.");
        Object idVal = tableInfo.getPropertyValue(entity, keyProperty);
        return StringUtils.checkValNull(idVal) || Objects.isNull(selectById((Serializable) idVal)) ? insert(entity) > 0 : updateById(entity) > 0;
    }


    /**
     * 插入(批量)
     *
     * @param entityList 实体对象集合
     * @since 3.5.7
     */
    default List<BatchResult> insert(Collection<T> entityList) {
        return insert(entityList, Constants.DEFAULT_BATCH_SIZE);
    }

    /**
     * 插入(批量)
     *
     * @param entityList 实体对象集合
     * @param batchSize  插入批次数量
     * @since 3.5.7
     */
    default List<BatchResult> insert(Collection<T> entityList, int batchSize) {
        MapperProxyMetadata mapperProxyMetadata = MybatisUtils.getMapperProxy(this);
        MybatisBatch.Method<T> method = new MybatisBatch.Method<>(mapperProxyMetadata.getMapperInterface());
        SqlSessionFactory sqlSessionFactory = MybatisUtils.getSqlSessionFactory(mapperProxyMetadata.getSqlSession());
        return MybatisBatchUtils.execute(sqlSessionFactory, entityList, method.insert(), batchSize);
    }

    /**
     * 根据ID 批量更新
     *
     * @param entityList 实体对象集合
     * @since 3.5.7
     */
    default List<BatchResult> updateById(Collection<T> entityList) {
        return updateById(entityList, Constants.DEFAULT_BATCH_SIZE);
    }

    /**
     * 根据ID 批量更新
     *
     * @param entityList 实体对象集合
     * @param batchSize  插入批次数量
     * @since 3.5.7
     */
    default List<BatchResult> updateById(Collection<T> entityList, int batchSize) {
        MapperProxyMetadata mapperProxyMetadata = MybatisUtils.getMapperProxy(this);
        MybatisBatch.Method<T> method = new MybatisBatch.Method<>(mapperProxyMetadata.getMapperInterface());
        SqlSessionFactory sqlSessionFactory = MybatisUtils.getSqlSessionFactory(mapperProxyMetadata.getSqlSession());
        return MybatisBatchUtils.execute(sqlSessionFactory, entityList, method.updateById(), batchSize);
    }

    /**
     * 批量修改或插入
     *
     * @param entityList 实体对象集合
     * @since 3.5.7
     */
    default List<BatchResult> insertOrUpdate(Collection<T> entityList) {
        return insertOrUpdate(entityList, Constants.DEFAULT_BATCH_SIZE);
    }

    /**
     * 批量修改或插入
     *
     * @param entityList 实体对象集合
     * @param batchSize  插入批次数量
     * @since 3.5.7
     */
    default List<BatchResult> insertOrUpdate(Collection<T> entityList, int batchSize) {
        MapperProxyMetadata mapperProxyMetadata = MybatisUtils.getMapperProxy(this);
        Class<?> entityClass = ReflectionKit.getSuperClassGenericType(getClass(), BaseMapper.class, 0);
        Assert.notNull(entityClass, "entityClass must not be null");
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        Assert.notNull(tableInfo, "Can not get TableInfo for entity " + entityClass);
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notEmpty(keyProperty, "The current table has no primary key.");
        String statement = mapperProxyMetadata.getMapperInterface().getName() + StringPool.DOT + SqlMethod.SELECT_BY_ID.getMethod();
        return insertOrUpdate(entityList, (sqlSession, entity) -> {
            Object idVal = tableInfo.getPropertyValue(entity, keyProperty);
            return StringUtils.checkValNull(idVal) || CollectionUtils.isEmpty(sqlSession.selectList(statement, entity));
        }, batchSize);
    }

    /**
     * 批量修改或插入
     *
     * @param entityList 实体对象集合
     * @since 3.5.7
     */
    default List<BatchResult> insertOrUpdate(Collection<T> entityList, BiPredicate<BatchSqlSession, T> insertPredicate) {
        return insertOrUpdate(entityList, insertPredicate, Constants.DEFAULT_BATCH_SIZE);
    }

    /**
     * 批量修改或插入
     *
     * @param entityList 实体对象集合
     * @param batchSize  插入批次数量
     * @since 3.5.7
     */
    default List<BatchResult> insertOrUpdate(Collection<T> entityList, BiPredicate<BatchSqlSession, T> insertPredicate, int batchSize) {
        MapperProxyMetadata mapperProxyMetadata = MybatisUtils.getMapperProxy(this);
        MybatisBatch.Method<T> method = new MybatisBatch.Method<>(mapperProxyMetadata.getMapperInterface());
        SqlSessionFactory sqlSessionFactory = MybatisUtils.getSqlSessionFactory(mapperProxyMetadata.getSqlSession());
        return MybatisBatchUtils.saveOrUpdate(sqlSessionFactory, entityList, method.insert(), insertPredicate, method.updateById(), batchSize);
    }

}

BaseMapper 源码分析

关键词:MyBatis-Plus、BaseMapper、CRUD、逻辑删除、自动填充、批量操作、源码解析

在 MyBatis-Plus 中,BaseMapper<T> 是最核心、也是我们日常使用频率最高的接口之一。

一句话概括它的价值:

只要 Mapper 继承 BaseMapper,就能"零 XML"拥有完整且高性能的 CRUD 能力。


一、BaseMapper 是什么?解决了什么问题?

在传统 MyBatis 中:

  • Mapper 接口需要写
  • mapper.xml 也必须写
  • CRUD SQL 几乎是模板化重复劳动

而 MyBatis-Plus 的目标很明确:

让 80% 的单表 CRUD 不再写 SQL

BaseMapper<T> 就是这个目标的"地基"。

java 复制代码
public interface UserMapper extends BaseMapper<User> {}

这一行代码,直接解锁:

  • insert / delete / update / select
  • 单条 / 批量
  • 条件查询 / 分页
  • 逻辑删除
  • 自动填充

二、BaseMapper 的整体设计思路

从源码结构看,BaseMapper 的设计有几个非常明显的特点:

1️⃣ 接口 + default 方法

  • 基础 CRUD:声明为接口方法(由 MP 自动生成 SQL)
  • 增强能力 :大量使用 default 方法

👉 好处:

  • 不破坏 MyBatis Mapper 机制
  • 又能在接口层直接写逻辑

这是 Java 8 default 方法的教科书级用法


2️⃣ 一切围绕实体 T

java 复制代码
public interface BaseMapper<T>
  • T 就是数据库表的 Java 映射
  • 所有 SQL 都是围绕实体元信息自动生成

关键元数据来源:

  • TableInfo
  • TableInfoHelper

3️⃣ 运行期而不是编译期绑定 SQL

你在 BaseMapper 中看不到 SQL,但 SQL 确实存在。

答案是:

SQL 是在运行期,通过 SqlMethod + MapperProxyMetadata 拼出来的


三、CRUD 能力拆解(源码级)

下面我们按 CRUD + 扩展能力来拆解源码。


四、Create:插入相关方法

1️⃣ 单条插入:insert

java 复制代码
int insert(T entity);
  • 最基础能力
  • 自动处理:
    • 主键策略(雪花 / 自增 / UUID)
    • insert 填充(@TableField(fill = INSERT)

📌 注意

  • null 字段是否插入,取决于 FieldStrategy

2️⃣ 批量插入(3.5.7+)

java 复制代码
default List<BatchResult> insert(Collection<T> entityList)

底层核心逻辑:

java 复制代码
MybatisBatchUtils.execute(sqlSessionFactory, entityList, method.insert(), batchSize)

💡 特点:

  • 使用 ExecutorType.BATCH
  • 真正 JDBC 级别的批处理
  • 支持自定义 batchSize

⚠️ 注意点:

  • 不等于 foreach insert into values
  • 事务要自己控制

五、Delete:删除的"复杂度天花板"

1️⃣ deleteById 的隐藏逻辑

java 复制代码
default int deleteById(Object obj, boolean useFill)

这段源码信息量极大,核心处理逻辑:

  1. 解析实体 Class
  2. 读取 TableInfo
  3. 判断:
    • 是否逻辑删除
    • 是否需要 updateFill
  4. 决定执行:
    • DELETE
    • 还是 UPDATE deleted = 1

👉 一句话总结

deleteById ≠ delete SQL,一切取决于表结构配置


2️⃣ 批量删除 deleteByIds

java 复制代码
DELETE FROM table WHERE id IN (?, ?)

如果是逻辑删除:

sql 复制代码
UPDATE table
SET deleted = 1
WHERE id IN (?, ?) AND deleted = 0

如果开启了 updateFill:

sql 复制代码
UPDATE table
SET deleted = 1,
    delete_user = 'xxx'
WHERE id IN (?, ?) AND deleted = 0

📌 非常重要的一点:

fill 参数永远来自 MP 自动构造的 et,而不是你传入的实体


六、Update:更新的两条主线

1️⃣ updateById

java 复制代码
int updateById(@Param(Constants.ENTITY) T entity);

特点:

  • 必须有主键
  • 自动 updateFill
  • 默认只更新非 null 字段

2️⃣ update + Wrapper

java 复制代码
int update(T entity, Wrapper<T> updateWrapper);

职责分离:

  • entity:SET
  • wrapper:WHERE

⚠️ 特别注意:

java 复制代码
default int update(Wrapper<T> updateWrapper) {
    return update(null, updateWrapper);
}

👉 这个方法无法自动填充(源码已明确说明)


七、Read:查询方法全景图

1️⃣ selectOne 的异常设计

java 复制代码
default T selectOne(Wrapper<T> queryWrapper, boolean throwEx)

逻辑非常直白:

  • size == 1 → 返回
  • size > 1:
    • throwEx = true → 抛异常
    • 否则返回第一条

💡 设计哲学:

把"是否唯一"的判断权交给调用者


2️⃣ exists 的"最佳实践"

java 复制代码
default boolean exists(Wrapper<T> queryWrapper)

内部实现:

java 复制代码
selectCount > 0

📌 建议:

  • 不要自己 selectCount 再判断
  • 直接用 exists,语义更清晰

3️⃣ selectObjs:只查一列

java 复制代码
<E> List<E> selectObjs(Wrapper<T> queryWrapper);
  • 只返回 第一个字段
  • 适合:
    • id 列表
    • 单列统计

八、分页设计:IPage 的"责任转移"

java 复制代码
default <P extends IPage<T>> P selectPage(P page, Wrapper<T> queryWrapper)

关键点:

  • BaseMapper 不负责分页 SQL
  • 分页插件通过拦截器改写 SQL

👉 Mapper 的职责非常纯粹:

只关心查数据,不关心怎么分页


九、insertOrUpdate:看似简单,其实很"重"

1️⃣ 单条 insertOrUpdate

java 复制代码
return id 为空 || selectById(id) 为空
    ? insert
    : update

⚠️ 注意:

  • 必然会多一次 select
  • 高并发场景可能有问题

2️⃣ 批量 insertOrUpdate

java 复制代码
BiPredicate<BatchSqlSession, T>

亮点:

  • 支持自定义是否 insert
  • 逻辑非常灵活

但代价是:

  • SQL 次数多
  • 不适合超大批量

十、BaseMapper 的优点 & 易踩坑

✅ 优点总结

  • 零 XML
  • 语义清晰
  • 扩展性极强
  • 逻辑删除 / 填充高度统一

⚠️ 常见坑点

  1. selectOne 多数据异常
  2. update(wrapper) 无自动填充
  3. insertOrUpdate 性能误用
  4. 批量操作事务未控制

十一、什么时候"不要用" BaseMapper?

以下场景建议写自定义 SQL:

  • 复杂多表 join
  • 极致性能 SQL
  • 数据库特性强依赖(hint / merge)

👉 MP 是提效工具,不是银弹


十二、总结

BaseMapper 的源码非常值得反复阅读,它体现了:

  • Java 接口设计
  • ORM 框架边界
  • 工程级 CRUD 抽象能力

若有转载,请标明出处:https://blog.csdn.net/CharlesYuangc/article/details/157429985

相关推荐
进击的小头2 小时前
行为型模式:策略模式的C语言实战指南
c语言·开发语言·策略模式
天马37982 小时前
Canvas 倾斜矩形绘制波浪效果
开发语言·前端·javascript
六义义2 小时前
java基础十二
java·数据结构·算法
Tansmjs2 小时前
C++与GPU计算(CUDA)
开发语言·c++·算法
qx092 小时前
esm模块与commonjs模块相互调用的方法
开发语言·前端·javascript
Suchadar2 小时前
if判断语句——Python
开发语言·python
毕设源码-钟学长3 小时前
【开题答辩全过程】以 基于SpringBoot的智能书城推荐系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
笨手笨脚の3 小时前
深入理解 Java 虚拟机-03 垃圾收集
java·jvm·垃圾回收·标记清除·标记复制·标记整理
cyforkk3 小时前
MyBatis Plus 字段自动填充:生产级实现方案与原理分析
mybatis
莫问前路漫漫3 小时前
WinMerge v2.16.41 中文绿色版深度解析:文件对比与合并的全能工具
java·开发语言·python·jdk·ai编程