1. MyBatis-Plus 核心架构解析
MyBatis-Plus 作为 MyBatis 的增强工具,其核心设计理念是"简化开发、提高效率"。在实际项目中,我们通常会遇到大量重复的 CRUD 操作,而 MyBatis-Plus 正是为了解决这个问题而生。它通过两个核心接口 BaseMapper 和 IService,分别覆盖了 DAO 层和业务逻辑层的常见操作需求。
1.1 BaseMapper 的定位与价值
BaseMapper 是 MyBatis-Plus 对 MyBatis Mapper 接口的增强实现。它内置了 17 个常用方法,几乎涵盖了单表操作的所有场景:
java复制public interface BaseMapper<T> extends Mapper<T> {
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> wrapper);
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> wrapper);
Integer selectCount(@Param("ew") Wrapper<T> wrapper);
List<T> selectList(@Param("ew") Wrapper<T> wrapper);
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> wrapper);
List<Object> selectObjs(@Param("ew") Wrapper<T> wrapper);
<E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> wrapper);
<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param("ew") Wrapper<T> wrapper);
}
这些方法的设计有几个显著特点:
- 参数命名规范统一,使用 @Param 注解明确参数用途
- 支持多种查询条件构造方式(ID、Map、Wrapper)
- 返回值类型考虑周全(单个对象、列表、分页等)
提示:在实际开发中,我们只需要让自己的 Mapper 接口继承 BaseMapper 并指定泛型类型,就能直接使用这些方法,无需编写任何 XML 或注解。
1.2 通用 Service 的设计哲学
IService 接口则是针对业务层的增强,它在 BaseMapper 基础上进一步封装,主要解决以下问题:
- 批量操作性能优化
- 事务控制的统一管理
- 复杂查询的链式调用
- 逻辑删除等业务场景支持
java复制public interface IService<T> {
// 批量操作
boolean saveBatch(Collection<T> entityList);
boolean saveOrUpdateBatch(Collection<T> entityList);
// 链式查询
QueryChainWrapper<T> query();
LambdaQueryChainWrapper<T> lambdaQuery();
// 事务控制
default boolean transaction(Supplier<Boolean> executor) {
// 默认事务实现
}
}
IService 的实现类 ServiceImpl 采用了模板方法模式,开发者只需继承它并指定 Mapper 类型即可获得完整功能:
java复制public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// 可以在此添加业务特有方法
}
这种设计既保证了通用功能的开箱即用,又保留了业务扩展的可能性。
2. BaseMapper 实战详解
2.1 基础 CRUD 操作
让我们通过用户管理的例子来演示 BaseMapper 的基本用法。首先定义实体类:
java复制@Data
@TableName("sys_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private Integer status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
然后创建对应的 Mapper 接口:
java复制public interface UserMapper extends BaseMapper<User> {
// 无需任何方法声明即可获得完整CRUD功能
}
现在我们可以直接注入 UserMapper 进行各种操作:
java复制@Autowired
private UserMapper userMapper;
// 插入
User user = new User();
user.setUsername("test");
user.setPassword("123456");
userMapper.insert(user); // 返回影响行数
// 查询
User dbUser = userMapper.selectById(1L); // 按ID查询
List<User> users = userMapper.selectList(null); // 查询所有
// 更新
user.setPassword("654321");
userMapper.updateById(user);
// 删除
userMapper.deleteById(1L);
2.2 条件构造器的高级用法
MyBatis-Plus 的条件构造器是其强大功能的核心,支持多种查询方式:
- 使用 QueryWrapper 构建条件:
java复制QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id", "username")
.like("username", "张")
.between("create_time", startDate, endDate)
.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
- 使用 Lambda 表达式(推荐):
java复制LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.select(User::getId, User::getUsername)
.like(User::getUsername, "张")
.ge(User::getCreateTime, startDate)
.le(User::getCreateTime, endDate);
List<User> users = userMapper.selectList(lambdaWrapper);
- 使用条件构造器进行更新:
java复制LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(User::getPassword, "newPassword")
.eq(User::getUsername, "admin");
userMapper.update(null, updateWrapper);
注意:条件构造器中的字段名可以使用字符串(容易出错)或 Lambda 表达式(类型安全)。在实际项目中,强烈建议使用 Lambda 方式,这样在重构时 IDE 可以帮助检查错误。
2.3 分页查询实现
MyBatis-Plus 的分页功能需要先配置分页插件:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
然后就可以进行分页查询:
java复制// 构造分页参数(当前页, 每页大小)
Page<User> page = new Page<>(1, 10);
// 构造查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByDesc(User::getCreateTime);
// 执行查询
Page<User> result = userMapper.selectPage(page, wrapper);
// 获取分页数据
List<User> records = result.getRecords(); // 当前页数据
long total = result.getTotal(); // 总记录数
long pages = result.getPages(); // 总页数
3. 通用 Service 层实战
3.1 Service 层基础架构
通用 Service 的使用通常需要三个步骤:
- 定义业务接口:
java复制public interface UserService extends IService<User> {
// 可以扩展业务特有方法
User getByUsername(String username);
}
- 实现业务接口:
java复制@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public User getByUsername(String username) {
return lambdaQuery().eq(User::getUsername, username).one();
}
}
- 在Controller中注入使用:
java复制@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public Page<User> list(Page<User> page) {
return userService.page(page);
}
}
3.2 批量操作优化
IService 提供了多种批量操作方法,内部已经做了性能优化:
java复制// 批量插入(每批1000条)
List<User> userList = generateUsers(5000);
userService.saveBatch(userList);
// 批量更新(带事务)
boolean success = userService.updateBatchById(userList);
// 保存或更新(根据ID是否存在自动判断)
userService.saveOrUpdateBatch(userList);
提示:默认的批量操作是每1000条执行一次,可以通过重载方法调整批次大小。对于超大数据量,建议在业务层自行分批次处理。
3.3 链式查询与事务控制
IService 提供了更友好的链式查询API:
java复制// 链式查询
List<User> users = userService.lambdaQuery()
.like(User::getUsername, "张")
.gt(User::getCreateTime, LocalDateTime.now().minusDays(7))
.list();
// 链式更新
boolean updated = userService.lambdaUpdate()
.set(User::getStatus, 0)
.eq(User::getLastLoginTime, null)
.update();
事务控制也非常简单:
java复制public boolean transfer(Long fromId, Long toId, BigDecimal amount) {
return userService.transaction(() -> {
User from = userService.getById(fromId);
User to = userService.getById(toId);
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
return userService.updateById(from) && userService.updateById(to);
});
}
4. 高级特性与实战技巧
4.1 自动填充功能
MyBatis-Plus 提供了字段自动填充功能,非常适合创建时间、更新时间等通用字段:
- 实现 MetaObjectHandler 接口:
java复制@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
- 在实体类中标记需要填充的字段:
java复制public class User {
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
4.2 逻辑删除实现
逻辑删除是现代系统的常见需求,MyBatis-Plus 提供了优雅的实现:
- 配置逻辑删除:
yaml复制mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 逻辑删除字段名
logic-not-delete-value: 0 # 未删除值
logic-delete-value: 1 # 删除值
- 实体类添加字段:
java复制public class User {
@TableLogic
private Integer deleted;
}
配置后,所有删除操作将自动变为更新操作,查询也会自动过滤已删除数据。
4.3 多租户实现
对于SaaS系统,多租户是必备功能。MyBatis-Plus 通过插件方式支持:
- 实现租户处理器:
java复制public class MyTenantHandler implements TenantLineHandler {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public Expression getTenantId() {
// 从当前上下文中获取租户ID
return new StringValue(TenantContext.getCurrentTenant());
}
@Override
public boolean ignoreTable(String tableName) {
// 忽略不需要租户隔离的表
return "sys_tenant".equals(tableName);
}
}
- 配置多租户插件:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MyTenantHandler()));
return interceptor;
}
4.4 性能优化建议
-
批量操作优化:对于超过10万条的数据处理,建议:
- 使用并行流处理
- 合理设置批次大小(通常1000-5000为宜)
- 考虑使用原生JDBC批量操作
-
查询优化:
- 只查询需要的字段(select()方法)
- 合理使用索引(通过条件构造器体现)
- 避免N+1查询问题
-
事务优化:
- 短事务原则
- 合理设置事务隔离级别
- 避免在事务中进行远程调用
-
缓存策略:
- 二级缓存谨慎使用
- 考虑使用Redis等分布式缓存
- 注意缓存一致性
在实际项目中,我通常会结合具体业务场景对 MyBatis-Plus 进行深度定制。比如为所有实体添加审计字段(创建人、创建时间等),封装统一的分页查询参数,以及开发各种实用的Wrapper扩展方法。这些经验积累可以显著提升开发效率和代码质量。
