1. 理解MyBatis-Plus中的BaseMapper
1.1 什么是BaseMapper
BaseMapper是MyBatis-Plus框架提供的一个核心接口,它封装了最基础的CRUD(Create, Read, Update, Delete)操作方法。当我们在DAO层接口中声明extends BaseMapper<UserEntity>时,实际上是在继承这个预置的通用接口。
这个设计采用了泛型编程的思想,<UserEntity>表示这个Mapper专门操作UserEntity类型的数据。MyBatis-Plus会在运行时自动为这个接口生成实现类,无需开发者手动编写SQL语句。
1.2 BaseMapper的核心方法
BaseMapper接口中预定义了约20个常用方法,主要包括:
// 插入操作 int insert(T entity); // 删除操作 int deleteById(Serializable id); int deleteByMap(@Param("cm") Map<String, Object> columnMap); int delete(@Param("ew") Wrapper<T> wrapper); // 更新操作 int updateById(@Param("et") T entity); int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper); // 查询操作 T selectById(Serializable id); List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList); List<T> selectByMap(@Param("cm") Map<String, Object> columnMap); T selectOne(@Param("ew") Wrapper<T> queryWrapper); // 计数操作 Long selectCount(@Param("ew") Wrapper<T> queryWrapper);这些方法已经覆盖了90%以上的单表操作场景,开发者无需再编写重复的SQL语句。
2. 泛型参数的作用与实现原理
2.1 泛型参数的意义
<UserEntity>这个泛型参数起到了三个关键作用:
- 类型安全:所有方法的参数和返回值都会自动适配UserEntity类型
- 表映射:框架会自动将UserEntity类与数据库表进行关联
- SQL生成:根据实体类属性自动生成对应的SQL语句
2.2 MyBatis-Plus的实现机制
当Spring容器启动时,MyBatis-Plus会通过以下步骤处理这个接口:
- 扫描所有继承BaseMapper的接口
- 使用动态代理技术生成实现类
- 解析泛型参数获取实体类类型
- 根据实体类注解(@TableName等)确定对应的数据库表
- 为每个方法生成对应的SQL实现
整个过程对开发者完全透明,这也是MyBatis-Plus"约定优于配置"理念的体现。
3. 实际开发中的应用场景
3.1 基础CRUD操作
继承BaseMapper后,我们可以直接使用预定义的方法:
// 插入用户 UserEntity user = new UserEntity(); user.setName("张三"); user.setAge(25); userMapper.insert(user); // 查询用户 UserEntity result = userMapper.selectById(1L); // 更新用户 user.setAge(26); userMapper.updateById(user); // 删除用户 userMapper.deleteById(1L);3.2 配合Wrapper使用
BaseMapper的强大之处在于可以与Wrapper条件构造器配合使用:
// 条件查询 QueryWrapper<UserEntity> wrapper = new QueryWrapper<>(); wrapper.like("name", "张") .between("age", 20, 30) .orderByDesc("create_time"); List<UserEntity> users = userMapper.selectList(wrapper); // 条件更新 UpdateWrapper<UserEntity> updateWrapper = new UpdateWrapper<>(); updateWrapper.set("age", 30) .eq("name", "张三"); userMapper.update(null, updateWrapper);4. 扩展与自定义
4.1 自定义SQL方法
虽然BaseMapper提供了丰富的方法,但有时我们仍需要自定义SQL:
public interface UserMapper extends BaseMapper<UserEntity> { @Select("SELECT * FROM user WHERE age > #{age}") List<UserEntity> selectByAgeGreaterThan(@Param("age") Integer age); }MyBatis-Plus会智能地合并继承的方法和自定义方法。
4.2 多表联查解决方案
对于复杂的多表查询,建议采用以下方案:
- 使用@Select注解编写自定义SQL
- 创建VO类接收结果
- 在XML中定义resultMap处理复杂映射
@Select("SELECT u.*, d.dept_name FROM user u LEFT JOIN department d ON u.dept_id = d.id") List<UserVO> selectUserWithDept();5. 性能优化与最佳实践
5.1 批量操作优化
BaseMapper提供了批量操作方法,但需要注意:
// 批量插入(实际是循环单条插入,性能一般) List<UserEntity> userList = ...; userMapper.insertBatchSomeColumn(userList); // 需要配置logic-not-delete-field // 真正高效的批量插入应该使用SQLSession SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH); UserMapper batchMapper = sqlSession.getMapper(UserMapper.class); for(UserEntity user : userList) { batchMapper.insert(user); } sqlSession.commit();5.2 字段注解的正确使用
合理使用注解可以优化SQL生成:
@TableName("sys_user") // 指定表名 public class UserEntity { @TableId(type = IdType.AUTO) // 主键策略 private Long id; @TableField("user_name") // 字段映射 private String name; @TableField(exist = false) // 非表字段 private String tempValue; }6. 常见问题排查
6.1 映射失败问题
当出现"Table 'xxx' doesn't exist"错误时,检查:
- 实体类是否有@TableName注解
- 数据库表名是否正确
- 是否配置了全局表名前缀(global-config.db-config.table-prefix)
6.2 主键冲突问题
主键策略配置不当会导致插入失败:
// 正确的主键策略配置 @TableId(type = IdType.AUTO) // 数据库自增 @TableId(type = IdType.ASSIGN_ID) // 雪花算法(默认) @TableId(type = IdType.INPUT) // 手动输入6.3 逻辑删除配置
实现逻辑删除需要正确配置:
mybatis-plus: global-config: db-config: logic-delete-field: deleted # 逻辑删除字段名 logic-not-delete-value: 0 # 未删除值 logic-delete-value: 1 # 删除值7. 源码解析与实现原理
7.1 动态代理机制
MyBatis-Plus通过MapperProxy实现接口方法的动态代理:
- 为每个Mapper接口创建代理对象
- 方法调用时,根据方法名和参数选择执行策略
- 对于BaseMapper方法,使用预定义的SQL模板
- 对于自定义方法,走原生MyBatis流程
7.2 SQL自动生成
核心类SqlMethod定义了各种操作对应的SQL模板:
public enum SqlMethod { INSERT("insert", "插入一条数据", "INSERT INTO %s %s VALUES %s"), DELETE_BY_ID("deleteById", "根据ID删除一条数据", "DELETE FROM %s WHERE %s=#{%s}"), SELECT_BY_ID("selectById", "根据ID查询一条数据", "SELECT %s FROM %s WHERE %s=#{%s}"), // 其他方法... }框架会根据实体类信息填充这些模板中的占位符。
8. 高级特性与扩展点
8.1 自定义注入方法
通过ISqlInjector接口可以扩展BaseMapper:
public class MySqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = super.getMethodList(mapperClass); methodList.add(new DeleteAll()); // 添加自定义方法 return methodList; } }8.2 租户处理器
实现多租户数据隔离:
public class MyTenantHandler implements TenantLineHandler { @Override public Expression getTenantId() { return new StringValue(TenantContext.getCurrentTenant()); } @Override public String getTenantIdColumn() { return "tenant_id"; } @Override public boolean ignoreTable(String tableName) { return !"user".equals(tableName); // 指定哪些表需要过滤 } }9. 版本差异与升级指南
9.1 3.x与2.x的主要区别
- 移除了
AR(ActiveRecord)模式 - 条件构造器API重构
- 分页插件实现方式变化
- 主键策略调整
9.2 升级注意事项
- 检查所有Wrapper的使用方式
- 重新评估分页实现
- 测试批量操作方法
- 验证自动填充功能
10. 实战经验分享
在实际项目中,我们总结了以下经验:
复杂查询处理:对于超过3表关联的查询,建议使用原生MyBatis实现
性能监控:添加
@InterceptorIgnore注解排除不需要监控的方法
@InterceptorIgnore(illegalSql = "true", blockAttack = "true") List<UserEntity> selectAll();- 动态表名:实现
IDynamicTableName接口处理分表场景
public class UserEntity implements IDynamicTableName { private String year; // 分表后缀 @Override public String getDynamicTableName() { return "user_" + year; } }- 字段加密:通过
@FieldEncrypt注解实现自动加解密
@FieldEncrypt private String password;- 多数据源:结合
@DS注解实现动态数据源切换
@DS("slave") // 指定从库 UserEntity selectById(Long id);