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 源码
- 源码中,所有
select的 Demo 示例:参考《SpringBoot3 MybatisPlus 查询》
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 都是围绕实体元信息自动生成
关键元数据来源:
TableInfoTableInfoHelper
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)
这段源码信息量极大,核心处理逻辑:
- 解析实体 Class
- 读取 TableInfo
- 判断:
- 是否逻辑删除
- 是否需要 updateFill
- 决定执行:
- 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
- 语义清晰
- 扩展性极强
- 逻辑删除 / 填充高度统一
⚠️ 常见坑点
selectOne多数据异常update(wrapper)无自动填充insertOrUpdate性能误用- 批量操作事务未控制
十一、什么时候"不要用" BaseMapper?
以下场景建议写自定义 SQL:
- 复杂多表 join
- 极致性能 SQL
- 数据库特性强依赖(hint / merge)
👉 MP 是提效工具,不是银弹。
十二、总结
BaseMapper 的源码非常值得反复阅读,它体现了:
- Java 接口设计
- ORM 框架边界
- 工程级 CRUD 抽象能力
若有转载,请标明出处:https://blog.csdn.net/CharlesYuangc/article/details/157429985