1. 项目概述:SpringBoot3与MyBatis-Plus 3.5.9整合实践
最近在重构一个老项目的持久层时,我选择了SpringBoot 3.x + MyBatis-Plus 3.5.9这套技术组合。作为Java生态中最主流的ORM框架组合之一,这套方案在开发效率、性能表现和可维护性上达到了很好的平衡。本文将分享我在实际项目中的完整整合过程,包含版本适配、核心功能实现和那些官方文档没写的实战技巧。
2. 环境准备与基础配置
2.1 依赖管理关键点
在pom.xml中需要特别注意版本匹配问题。SpringBoot 3.x默认使用Jakarta EE 9+的命名空间,这与MyBatis-Plus 3.5.9的依赖存在潜在冲突。以下是经过生产验证的依赖配置:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.9</version> <!-- 排除旧版javax.persistence依赖 --> <exclusions> <exclusion> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> </exclusion> </exclusions> </dependency> <!-- 添加Jakarta EE兼容版本 --> <dependency> <groupId>jakarta.persistence</groupId> <artifactId>jakarta.persistence-api</artifactId> <version>3.1.0</version> </dependency>提示:如果项目中使用Lombok,需要确保版本≥1.18.24,否则可能遇到注解处理器与Java 17的兼容性问题。
2.2 数据源配置优化
在application.yml中,建议采用HikariCP连接池并启用MyBatis-Plus的性能优化参数:
spring: datasource: type: com.zaxxer.hikari.HikariDataSource hikari: maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发环境SQL日志 default-scripting-language: org.apache.ibatis.scripting.defaults.RawLanguageDriver global-config: db-config: logic-delete-field: deleted # 逻辑删除字段名 logic-not-delete-value: 0 logic-delete-value: 13. 核心功能实现详解
3.1 实体类与Mapper设计
MyBatis-Plus 3.5.9对注解体系进行了增强。推荐使用新版@TableName和@TableField:
@Data @TableName(value = "sys_user", autoResultMap = true) public class User { @TableId(type = IdType.ASSIGN_ID) // 雪花算法ID private Long id; @TableField(value = "username", condition = SqlCondition.LIKE) private String name; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @Version private Integer version; }对应的Mapper接口只需继承BaseMapper即可获得CRUD能力:
@Mapper public interface UserMapper extends BaseMapper<User> { // 自定义复杂查询 @Select("SELECT * FROM sys_user WHERE status = #{status}") List<User> selectByStatus(@Param("status") Integer status); }3.2 动态SQL构建最佳实践
MyBatis-Plus 3.5.9的LambdaQueryWrapper在类型安全方面有显著提升:
public List<User> searchUsers(String keyword, Integer status) { return lambdaQuery() .and(wrapper -> wrapper .like(User::getName, keyword) .or() .like(User::getEmail, keyword)) .eq(status != null, User::getStatus, status) .orderByDesc(User::getCreateTime) .list(); }经验:对于超过3个条件的复杂查询,建议使用QueryChainWrapper保持代码可读性:
public List<User> complexQuery(QueryParams params) { return new QueryChainWrapper<>(userMapper) .like(StringUtils.isNotBlank(params.getKeyword()), User::getName, params.getKeyword()) .ge(params.getStartTime() != null, User::getCreateTime, params.getStartTime()) .le(params.getEndTime() != null, User::getCreateTime, params.getEndTime()) .list(); }4. 高级特性实战
4.1 多租户方案实现
MyBatis-Plus 3.5.9内置了多租户支持,以下是基于请求头中tenant_id的完整实现:
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 租户处理器 TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor(); tenantInterceptor.setTenantLineHandler(new TenantLineHandler() { @Override public String getTenantIdColumn() { return "tenant_id"; } @Override public Expression getTenantId() { return new StringValue(RequestContextHolder.getRequestAttributes() .getAttribute("tenant_id", RequestAttributes.SCOPE_REQUEST)); } @Override public boolean ignoreTable(String tableName) { return !Arrays.asList("sys_user", "sys_role").contains(tableName); } }); interceptor.addInnerInterceptor(tenantInterceptor); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }4.2 乐观锁与自动填充
乐观锁配置需要在实体类添加@Version注解(见3.1节),自动填充则需要实现MetaObjectHandler:
@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()); } }5. 性能优化与监控
5.1 SQL执行监控
结合p6spy实现完整SQL日志记录(生产环境需谨慎):
<dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version> </dependency>配置spy.properties:
module.log=com.p6spy.engine.logging.P6LogFactory appender=com.p6spy.engine.spy.appender.Slf4JLogger logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat customLogMessageFormat=%(executionTime)ms | %(category) | connection %(connectionId) | %(sqlSingleLine)5.2 分页查询优化
对于大数据量分页,推荐使用优化后的分页插件:
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination = new PaginationInnerInterceptor(DbType.MYSQL); pagination.setOptimizeJoin(true); // 优化JOIN查询 pagination.setMaxLimit(1000L); // 单页最大1000条 interceptor.addInnerInterceptor(pagination); return interceptor; }使用示例:
Page<User> page = new Page<>(1, 10); page.setSearchCount(false); // 大数据量时关闭count查询 userMapper.selectPage(page, queryWrapper);6. 常见问题排查
6.1 类型转换异常处理
当遇到"java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long"这类错误时,通常是因为:
- 实体类中使用了基本类型long,应改为包装类型Long
- MyBatis类型处理器配置不当,建议检查@TableId的type属性
6.2 事务失效场景
SpringBoot 3.x与MyBatis-Plus整合时常见的事务问题:
- 确保@Transactional注解添加到实现类而非接口
- 检查是否配置了@EnableTransactionManagement
- 避免同类内方法调用(AOP代理失效)
6.3 缓存一致性方案
对于使用MyBatis二级缓存的系统,建议采用以下策略保证一致性:
@CacheNamespace(implementation = MybatisRedisCache.class, eviction = MybatisRedisCache.class) public interface UserMapper extends BaseMapper<User> { @Override @Options(flushCache = Options.FlushCachePolicy.TRUE) int insert(User entity); @CacheEvict(key = "'user:'+#id") default void clearUserCache(Long id) { // 空方法,仅用于触发缓存清除 } }配套的Redis缓存实现需要自定义:
public class MybatisRedisCache implements Cache { private final String id; private final RedisTemplate<String, Object> redisTemplate; public MybatisRedisCache(String id) { this.id = id; this.redisTemplate = (RedisTemplate<String, Object>) SpringContextHolder.getBean("redisTemplate"); } @Override public void putObject(Object key, Object value) { redisTemplate.opsForValue().set(key.toString(), value, 30, TimeUnit.MINUTES); } // 其他方法实现... }7. 升级迁移建议
从MyBatis-Plus旧版升级到3.5.9时需要注意:
- 分页插件配置方式变化(旧版PaginationInterceptor已废弃)
- Lambda表达式API更加严格,部分链式调用需要调整
- 新版SQL注入器机制重构,自定义SQL需要适配新接口
对于从MyBatis原生迁移的项目,建议分阶段进行:
- 先引入MyBatis-Plus但不修改原有Mapper
- 逐步将XML中的SQL迁移到Wrapper构建
- 最后替换BaseMapper中的通用方法
实际项目中,我通过这套技术栈将DAO层代码量减少了60%以上,复杂查询的开发效率提升明显。特别是在动态数据源切换、多租户隔离这些场景下,MyBatis-Plus 3.5.9提供的抽象封装让代码保持简洁的同时不失灵活性。