1. Spring与MyBatis整合的核心价值
在企业级Java开发中,持久层框架的选择往往决定了整个项目的开发效率和运行性能。MyBatis作为半自动化的ORM框架,以其灵活的SQL编写方式和优秀的性能表现,成为众多开发者的首选。而Spring框架提供的IoC容器和事务管理能力,则是构建健壮应用的基石。
我经历过多个从零搭建的JavaEE项目,发现Spring与MyBatis的整合质量直接影响着后续的开发体验。一个配置得当的整合方案,可以让开发者专注于业务逻辑,而不必反复处理数据库连接、事务边界等基础问题。反之,则可能导致NPE频发、事务失效等"玄学"问题。
2. 基础环境搭建
2.1 依赖管理关键点
在Maven项目中,除了常规的spring-context和mybatis依赖外,需要特别注意以下三个核心依赖:
xml复制<!-- Spring JDBC 提供事务管理支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.22</version>
</dependency>
<!-- MyBatis-Spring 桥接包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- 连接池实现(以HikariCP为例) -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
经验之谈:我曾在一个生产项目中因为漏掉spring-jdbc依赖,导致@Transactional注解完全失效,排查了整整两天。切记这三个依赖缺一不可。
2.2 数据源配置的陷阱
在application.properties中,以下配置项最容易出现问题:
properties复制# HikariCP推荐配置(生产环境需要调整)
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
# 关键配置:必须开启事务自动提交
spring.datasource.hikari.auto-commit=true
特别提醒:如果使用MySQL 8.x,需要显式指定驱动类名:
properties复制spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
3. MyBatis核心组件配置
3.1 SqlSessionFactoryBean的精细配置
在Spring配置类中,SqlSessionFactoryBean的配置决定了MyBatis的核心行为:
java复制@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
// 重要:解决TypeHandler扫描问题
factoryBean.setTypeAliasesPackage("com.example.domain");
// XML映射文件位置(适应Maven标准目录结构)
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/**/*.xml"));
// 自定义配置(下划线转驼峰等)
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setDefaultFetchSize(100);
configuration.setDefaultStatementTimeout(30);
factoryBean.setConfiguration(configuration);
return factoryBean.getObject();
}
踩坑记录:setTypeAliasesPackage的路径如果包含不存在的包,不会报错但会导致后续Mapper注入失败。建议先用IDE检查包路径是否存在。
3.2 Mapper扫描的三种方式对比
方式一:经典XML配置(适合老项目迁移)
xml复制<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
方式二:JavaConfig注解(Spring Boot推荐)
java复制@MapperScan(basePackages = "com.example.mapper",
sqlSessionFactoryRef = "sqlSessionFactory")
方式三:接口标注@Mapper注解(最灵活但需要每个接口标注)
实测建议:中型项目推荐方式二,大型微服务项目可以考虑方式三实现按需加载。
4. 事务管理的正确姿势
4.1 声明式事务配置
java复制@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
关键点说明:
- @EnableTransactionManagement要放在@SpringBootApplication主类或单独配置类上
- 事务管理器bean名称必须为transactionManager(默认约定)
- 建议在测试类中添加@Transactional(rollbackFor = Exception.class)验证事务是否生效
4.2 事务传播行为的实战选择
在Service层方法上,不同场景应选择不同传播行为:
java复制// 默认值:当前有事务就加入,没有就新建
@Transactional(propagation = Propagation.REQUIRED)
public void standardOperation() {...}
// 需要独立事务(如日志记录)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void auditLogOperation() {...}
// 非事务执行(性能敏感操作)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void bulkImportOperation() {...}
血泪教训:在批量处理中混用REQUIRED和REQUIRES_NEW可能导致数据库连接耗尽,建议配合@Async使用。
5. 高级整合技巧
5.1 分页插件集成
PageHelper是目前最流行的MyBatis分页方案:
java复制@Bean
public PageInterceptor pageInterceptor() {
PageInterceptor interceptor = new PageInterceptor();
Properties props = new Properties();
// 使用MySQL方言
props.setProperty("helperDialect", "mysql");
// 分页合理化(超出最大页数返回最后一页)
props.setProperty("reasonable", "true");
interceptor.setProperties(props);
return interceptor;
}
使用示例:
java复制PageHelper.startPage(1, 10); // 第一页,每页10条
List<User> users = userMapper.selectAll();
PageInfo<User> pageInfo = new PageInfo<>(users);
5.2 多数据源配置方案
对于需要连接多个数据库的场景:
java复制@Configuration
@MapperScan(basePackages = "com.example.primary.mapper",
sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
// 同单数据源配置...
}
@Bean
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
关键点:
- 每个数据源要有独立的@Configuration类
- 使用@Qualifier明确指定bean名称
- 事务管理器也要区分不同数据源
6. 性能优化实战
6.1 批量操作的最佳实践
MyBatis批量插入的三种方式对比:
java复制// 方式一:foreach动态SQL(适合小批量)
@Insert("<script>INSERT INTO user(name,age) VALUES " +
"<foreach collection='list' item='item' separator=','>" +
"(#{item.name},#{item.age})</foreach></script>")
void batchInsert(@Param("list") List<User> users);
// 方式二:ExecutorType.BATCH(适合中等批量)
public void batchInsert(List<User> users) {
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : users) {
mapper.insert(user);
}
session.commit();
} finally {
session.close();
}
}
// 方式三:JDBC批量模式(超大批量)
jdbcTemplate.batchUpdate("INSERT INTO user(name,age) VALUES(?,?)",
new BatchPreparedStatementSetter() {
// 实现方法...
});
性能测试数据(插入10000条记录):
- 方式一:约3.2秒
- 方式二:约1.8秒
- 方式三:约0.9秒
6.2 二级缓存整合Redis
通过MyBatis的Cache接口实现Redis二级缓存:
java复制public class RedisCache implements Cache {
private final String id;
private final RedisTemplate<String, Object> redisTemplate;
public RedisCache(String id) {
this.id = id;
this.redisTemplate = (RedisTemplate<String, Object>)
SpringContextHolder.getBean("redisTemplate");
}
@Override
public String getId() { return id; }
@Override
public void putObject(Object key, Object value) {
redisTemplate.opsForHash().put(id, key.toString(), value);
}
// 其他方法实现...
}
在Mapper接口上启用:
java复制@CacheNamespace(implementation = RedisCache.class)
public interface UserMapper {...}
缓存策略建议:
- 查询多修改少的表:使用READ_WRITE缓存
- 财务等关键数据:建议禁用缓存或使用严格事务控制
7. 常见问题排查指南
7.1 经典异常解决方案
问题一:Invalid bound statement (not found)
- 检查XML文件是否在mapperLocations指定路径下
- 检查namespace是否与Mapper接口全限定名一致
- 运行mvn clean清理编译缓存
问题二:事务不生效
- 确认是否抛出RuntimeException(默认只回滚RuntimeException)
- 检查方法是否为public(Spring AOP要求)
- 确认是否被同类方法调用(自调用失效)
问题三:连接泄露
- 在日志中开启DEBUG级别查看连接获取/释放记录
- 使用Druid的监控页面检查活跃连接数
- 确保所有SqlSession都正确关闭(try-with-resources语法)
7.2 日志配置技巧
完整的MyBatis日志配置(logback为例):
xml复制<logger name="org.mybatis" level="DEBUG"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="TRACE"/>
特别有用的日志组件:
- MyBatis Log Plugin(IDEA插件,格式化SQL日志)
- p6spy(显示真实SQL参数值)
8. 现代Spring Boot整合方案
8.1 自动配置原理
Spring Boot的mybatis-spring-boot-starter自动完成了:
- 自动检测DataSource
- 创建SqlSessionFactory(带下划线转驼峰等默认配置)
- 扫描@Mapper注解的接口
- 注册事务管理器
可以通过以下属性定制:
properties复制mybatis.configuration.map-underscore-to-camel-case=true
mybatis.type-aliases-package=com.example.domain
mybatis.mapper-locations=classpath*:mapper/**/*.xml
8.2 MyBatis-Plus整合
新一代ORM框架MyBatis-Plus的整合更简单:
java复制@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
优势功能:
- 通用CRUD方法(无需写SQL)
- Lambda表达式查询
- 自动分页
- 乐观锁支持
在最近的一个电商项目中,使用MyBatis-Plus后,基础CRUD代码量减少了约70%,但复杂查询仍保持MyBatis的灵活性。
