1. MyBatis-Plus 核心价值与适用场景解析
作为MyBatis的增强工具包,MyBatis-Plus在保留原生MyBatis所有特性的基础上,通过内置通用Mapper和Service,显著减少了约80%的样板代码量。我在电商系统开发中深有体会——原本需要手动编写的20余个单表CRUD接口,使用MyBatis-Plus后只需定义实体类即可自动生成。这种"约定优于配置"的设计理念特别适合快速迭代的中后台管理系统开发。
典型应用场景包括:
- 需要快速搭建原型的创业项目(3天内完成基础数据管理模块)
- 企业内部的OA/CRM系统(包含大量标准表单操作)
- 微服务架构中的基础数据服务(配合Spring Cloud使用)
注意:复杂多表关联查询场景仍需配合XML手写SQL,这正是MyBatis-Plus保持扩展性的聪明之处——它没有试图替代MyBatis,而是在其基础上做增强。
2. 环境搭建与基础配置
2.1 依赖引入关键点
在Spring Boot项目中引入依赖时,需要特别注意版本兼容性。以下是经过多个生产项目验证的稳定组合:
xml复制<!-- pom.xml -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version> <!-- 与Spring Boot 2.7.x完美兼容 -->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
常见版本冲突问题:
- 与MyBatis原生包冲突:移除mybatis-spring-boot-starter
- 分页插件失效:检查是否同时存在多个PageHelper依赖
2.2 配置文件黄金参数
这些配置项来自千万级数据量的生产环境调优:
yaml复制# application.yml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发环境SQL日志
default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler # 枚举处理
global-config:
db-config:
id-type: auto # 适合MySQL自增主键
logic-delete-field: deleted # 逻辑删除字段名
logic-not-delete-value: 0
logic-delete-value: 1
mapper-locations: classpath*:/mapper/**/*.xml # XML扫描路径
踩坑记录:logic-delete-field如果设置为"is_deleted",需要对应实体类字段为isDeleted,否则逻辑删除失效。
3. 核心注解深度解析
3.1 实体类注解实战
java复制@TableName(value = "sys_user", autoResultMap = true) // 自动映射resultMap
public class User {
@TableId(type = IdType.ASSIGN_ID) // 雪花算法ID
private Long id;
@TableField(value = "username", fill = FieldFill.INSERT) // 插入时自动填充
private String name;
@TableLogic // 逻辑删除标记
private Integer deleted;
@Version // 乐观锁版本号
private Integer version;
@EnumValue // 标记枚举存储值
private UserStatus status;
@TableField(exist = false) // 非表字段
private String tempToken;
}
注解使用禁忌:
- 避免在@TableField同时使用condition和update属性,会导致动态SQL混乱
- @Version字段必须配合乐观锁插件使用,否则数据不一致
3.2 条件构造器高级用法
Wrapper的链式调用能构建复杂查询条件:
java复制// 查询最近30天活跃的VIP用户
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>()
.select(User::getId, User::getName)
.ge(User::getLastLoginTime, LocalDateTime.now().minusDays(30))
.eq(User::getVipLevel, 3)
.nested(i -> i.like(User::getEmail, "@gmail.com")
.or()
.like(User::getEmail, "@qq.com"))
.orderByDesc(User::getLoginCount)
.last("limit 500");
性能优化技巧:
- 避免在wrapper中使用"SELECT *"
- 超过3个OR条件建议改用queryChainWrapper
4. 常用功能模块实现
4.1 分页查询最佳实践
java复制// 配置分页插件(必需)
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
// 实际分页查询
Page<User> page = new Page<>(1, 10); // 当前页, 每页数量
Page<User> result = userMapper.selectPage(page,
Wrappers.<User>lambdaQuery()
.eq(User::getDepartmentId, 5));
// 获取分页数据
List<User> records = result.getRecords();
long total = result.getTotal();
常见分页问题处理:
- 总数查询慢:page.setSearchCount(false)禁用count查询
- 内存溢出:用Page.notOptimizeCount()优化大数据量场景
4.2 批量操作性能对比
测试数据(10万条记录):
| 操作方式 | 耗时(ms) | 内存峰值(MB) |
|---|---|---|
| 循环单条插入 | 28500 | 512 |
| MP的saveBatch | 4200 | 680 |
| 手写XML批量插入 | 900 | 350 |
结论:大数据量时推荐使用XML批量操作,中小批量用saveBatch更方便。
5. 生产环境避坑指南
5.1 事务失效场景
java复制// 错误示例:同类方法调用导致事务失效
public void createUser(User user) {
this.saveUserDetail(user); // 事务不生效
}
@Transactional
public void saveUserDetail(User user) {
// ...
}
// 正确做法:通过代理对象调用
@Autowired
private UserService selfProxy; // 注入自身代理
public void createUser(User user) {
selfProxy.saveUserDetail(user); // 事务生效
}
5.2 多数据源配置要点
java复制@Configuration
@MapperScan(basePackages = "com.xx.mapper.db1", sqlSessionTemplateRef = "db1SqlSessionTemplate")
public class Db1Config {
@Bean
@ConfigurationProperties("spring.datasource.db1")
public DataSource db1DataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/db1/*.xml"));
// 关键配置:使用MyBatis-Plus配置
factory.setConfiguration(new MybatisConfiguration());
return factory.getObject();
}
}
多数据源常见问题:
- 事务管理器必须指定qualifier
- 分页插件需要在每个SqlSessionFactory单独配置
6. 微服务架构下的特殊处理
6.1 分布式ID生成策略
在Spring Cloud微服务中推荐使用Leaf方案:
java复制@Bean
public IdentifierGenerator idGenerator() {
return entity -> {
// 调用Leaf服务获取分布式ID
return leafService.getId(entity.getClass());
};
}
对比几种ID方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 数据库自增 | 简单可靠 | 分库分表困难 |
| 雪花算法 | 分布式友好 | 时钟回拨问题 |
| Leaf-snowflake | 解决时钟回拨 | 依赖ZK |
| Leaf-segment | 吞吐量高 | 需要预分配号段 |
6.2 多租户实现方案
基于MyBatis-Plus的多租户插件:
java复制public class TenantInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms,
Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) {
// 自动添加租户条件
if (parameter instanceof Criteria) {
((Criteria) parameter).eq("tenant_id", TenantContext.getCurrentId());
}
}
}
配套的SQL解析器需要处理:
- JOIN语句中的租户字段自动关联
- 子查询中的租户条件传播
- 系统管理员的白名单控制
7. 性能监控与调优
7.1 SQL执行监控
通过自定义拦截器记录慢查询:
java复制@Intercepts({
@Signature(type = StatementHandler.class, method = "query",
args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update",
args = {Statement.class})
})
public class PerformanceInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long time = System.currentTimeMillis() - start;
if (time > 500) { // 超过500ms记录警告
StatementHandler handler = (StatementHandler) invocation.getTarget();
log.warn("Slow SQL detected: {} \n Cost: {}ms",
handler.getBoundSql().getSql(), time);
}
return result;
}
}
7.2 二级缓存优化策略
Redis缓存配置示例:
java复制@Bean
public MybatisRedisCache mybatisRedisCache() {
return new MybatisRedisCache("userCache") {
@Override
protected RedisTemplate<Object, Object> createRedisTemplate() {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
return template;
}
};
}
缓存更新策略建议:
- 高频查询但更新少的表:TTL设置30分钟
- 财务类关键数据:采用主动清除策略
- 关联数据:使用Cache-aside模式
8. 扩展开发技巧
8.1 自定义全局操作
实现自动填充创建人信息:
java复制@Component
public class MetaObjectHandler implements com.baomidou.mybatisplus.core.handlers.MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUsername());
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUsername());
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
8.2 动态表名处理器
实现按月分表的动态路由:
java复制public class MonthTableNameParser implements ITableNameHandler {
@Override
public String dynamicTableName(String sql, String tableName) {
if ("order".equals(tableName)) {
return "order_" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
}
return tableName;
}
}
// 配置拦截器
interceptor.addInnerInterceptor(new DynamicTableNameInnerInterceptor(new MonthTableNameParser()));
历史数据查询技巧:
java复制// 临时指定查询历史表
DynamicTableNameHelper.set("order_202301");
List<Order> orders = orderMapper.selectList(null);
DynamicTableNameHelper.remove();