1. Spring与MyBatis整合实战解析
作为Java开发者,Spring和MyBatis的整合是我们日常开发中最常见的组合之一。这种组合既保留了MyBatis对SQL的精细控制能力,又充分利用了Spring的IoC容器和事务管理优势。下面我将结合多年项目经验,详细拆解整合过程中的关键配置和注意事项。
1.1 依赖管理最佳实践
在Maven项目中,依赖版本管理是第一个需要关注的重点。以下是经过生产验证的依赖配置方案:
xml复制<!-- Spring核心容器(必须保持版本一致) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<!-- MyBatis-Spring适配器(版本需与MyBatis核心匹配) -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.0</version>
</dependency>
<!-- 数据库连接池选型建议 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
关键提示:Spring 5.x系列与MyBatis 3.5+的兼容性最好。Druid作为连接池时,建议同时配置监控过滤器(WallFilter和StatFilter)来预防SQL注入和监控性能。
1.2 日志配置的工程化实践
日志系统是排查问题的第一道防线。推荐采用Log4j 1.x(虽然已停止维护,但企业存量项目仍大量使用)的增强配置:
properties复制# 根日志级别控制(生产环境建议WARN以上)
log4j.rootLogger=INFO, stdout, file
# 控制台输出(开发环境使用)
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p [%t] %c{2}:%L - %m%n
# 文件输出(生产环境必备)
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File=logs/app.log
log4j.appender.file.DatePattern='.'yyyy-MM-dd
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p [%t] %c{2}:%L - %m%n
# MyBatis调试配置(开发阶段开启)
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
实际项目中,我们还需要考虑:
- 日志文件按大小和日期滚动
- 敏感信息过滤(如密码字段的脱敏)
- 异步日志写入提升性能
1.3 数据源配置的隐藏陷阱
Druid作为高性能连接池,其完整配置往往被大多数教程忽略。以下是生产级配置示例:
xml复制<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 连接池核心参数 -->
<property name="initialSize" value="5"/>
<property name="minIdle" value="5"/>
<property name="maxActive" value="20"/>
<property name="maxWait" value="60000"/>
<!-- 连接有效性检测 -->
<property name="validationQuery" value="SELECT 1"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<!-- 超时配置 -->
<property name="removeAbandoned" value="true"/>
<property name="removeAbandonedTimeout" value="1800"/>
<property name="logAbandoned" value="true"/>
</bean>
常见坑点包括:
- 忘记配置
init-method和destroy-method导致连接泄漏 - 连接验证查询(validationQuery)与数据库类型不匹配
- maxActive设置过大反而导致性能下降(建议20-50之间)
1.4 MyBatis-Spring整合的三种模式
模式一:传统DAO方式(已淘汰)
java复制public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
public User getById(Long id) {
return getSqlSession().selectOne("com.hg.mapper.UserMapper.getById", id);
}
}
模式二:MapperFactoryBean(适合少量Mapper)
xml复制<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.hg.mapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
模式三:MapperScannerConfigurer(企业主流)
xml复制<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.hg.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 可选:指定注解过滤 -->
<property name="annotationClass" value="org.springframework.stereotype.Repository"/>
</bean>
性能对比:
- 扫描方式启动稍慢但运行期无差别
- 接口越多,扫描方式的优势越明显
- 对于超大型项目(Mapper接口>200),建议分模块扫描
2. Spring事务管理深度解析
2.1 事务核心API设计原理
Spring事务抽象的核心是PlatformTransactionManager接口体系:
java复制public interface PlatformTransactionManager {
// 获取事务状态
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
// 提交事务
void commit(TransactionStatus status) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
实现类选择策略:
DataSourceTransactionManager:JDBC/MyBatis项目首选JpaTransactionManager:JPA/Hibernate项目使用JtaTransactionManager:分布式事务场景
2.2 事务定义详解
隔离级别实战建议
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 适用场景 |
|---|---|---|---|---|
| READ_UNCOMMITTED | ❌ | ❌ | ❌ | 几乎不用 |
| READ_COMMITTED | ✅ | ❌ | ❌ | 金融核心系统(Oracle默认) |
| REPEATABLE_READ | ✅ | ✅ | ❌ | 电商系统(MySQL默认) |
| SERIALIZABLE | ✅ | ✅ | ✅ | 对一致性要求极高的场景 |
项目经验:MySQL的RR级别通过MVCC+间隙锁实际解决了幻读问题,与标准SQL规范不同
传播行为场景分析
java复制@Service
public class OrderService {
@Transactional
public void placeOrder(Order order) {
// 主事务逻辑...
inventoryService.reduceStock(order); // PROPAGATION_REQUIRED
logService.record(order); // PROPAGATION_REQUIRES_NEW
couponService.useCoupon(order); // PROPAGATION_NESTED
}
}
传播行为选择指南:
REQUIRED:90%场景的默认选择REQUIRES_NEW:审计日志、操作记录等独立业务NESTED:部分失败需要部分回滚的复杂业务
2.3 声明式事务的陷阱与解决方案
失效场景1:同类方法调用
java复制@Service
public class UserService {
public void updateProfile(User user) {
// 此调用事务失效!
this.updatePassword(user.getPassword());
}
@Transactional
public void updatePassword(String password) {
// ...
}
}
解决方案:
- 使用
AopContext.currentProxy() - 将方法拆分到不同Service
- 使用
@EnableAspectJAutoProxy(exposeProxy = true)
失效场景2:异常捕获不当
java复制@Transactional
public void transfer() {
try {
accountDao.deduct();
accountDao.add();
} catch (Exception e) {
// 默认只回滚RuntimeException
log.error("转账失败", e);
}
}
修正方案:
java复制@Transactional(rollbackFor = Exception.class)
public void transfer() throws BusinessException {
// ...
}
2.4 混合事务管理策略
对于需要同时操作多个数据源的场景:
xml复制<!-- 主库事务管理器 -->
<bean id="masterTxManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="masterDataSource"/>
</bean>
<!-- 从库事务管理器 -->
<bean id="slaveTxManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="slaveDataSource"/>
</bean>
<!-- 链式事务管理器 -->
<bean id="chainedTxManager"
class="org.springframework.data.transaction.ChainedTransactionManager">
<property name="transactionManagers">
<list>
<ref bean="masterTxManager"/>
<ref bean="slaveTxManager"/>
</list>
</property>
</bean>
使用方式:
java复制@Service
public class ReportService {
@Transactional(transactionManager = "chainedTxManager")
public void generateReport() {
// 同时操作主库和从库
}
}
3. 性能优化与监控
3.1 事务超时配置原则
java复制@Transactional(timeout = 30) // 单位:秒
public void batchProcess() {
// 批量处理逻辑
}
配置建议:
- 简单查询:5-10秒
- 常规业务:30秒
- 批量处理:按数据量动态计算
- 后台任务:可设置更长但需有补偿机制
3.2 连接池监控指标
通过Druid监控发现性能瓶颈:
java复制@RestController
public class DruidStatController {
@Autowired
private DruidDataSource dataSource;
@GetMapping("/druid/stat")
public Object stat() {
return dataSource.getStatDataForMBean();
}
}
关键指标监控:
- ActiveCount:活跃连接数(接近maxActive说明需要扩容)
- PoolingCount:空闲连接数(长期为0说明连接创建频繁)
- WaitThreadCount:等待线程数(大于0说明连接不足)
3.3 MyBatis二级缓存陷阱
配置示例:
xml复制<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- Mapper级别缓存 -->
<cache eviction="LRU" flushInterval="60000" size="1024"/>
使用禁忌:
- 多表关联查询不要开启
- 频繁更新的数据不要缓存
- 分布式环境需要配合Redis等集中式缓存
4. 常见问题排查指南
4.1 事务不回滚排查清单
-
检查异常类型:
- 默认只回滚RuntimeException和Error
- 检查是否捕获了异常未抛出
- 检查
rollbackFor配置
-
检查代理机制:
- 确保是通过代理对象调用
- 检查
@EnableTransactionManagement是否启用
-
检查数据库引擎:
- MyISAM引擎不支持事务
- 确认表使用InnoDB引擎
4.2 连接泄漏排查步骤
- 启用Druid的removeAbandoned:
xml复制<property name="removeAbandoned" value="true"/>
<property name="removeAbandonedTimeout" value="300"/>
<property name="logAbandoned" value="true"/>
- 分析堆栈信息:
code复制ERROR [Druid-ConnectionPool-Destroy-1] - abandon connection, owner thread:
http-nio-8080-exec-6, connected at: 1587711111000...
- 常见泄漏点:
- 未关闭的ResultSet/Statement
- 未在finally块中释放连接
- 事务未正确结束
4.3 性能问题诊断
慢SQL分析流程:
- 开启MyBatis日志:
properties复制log4j.logger.java.sql.PreparedStatement=DEBUG
- 使用Druid的StatFilter:
xml复制<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
<property name="slowSqlMillis" value="1000"/>
<property name="logSlowSql" value="true"/>
</bean>
- 分析执行计划:
sql复制EXPLAIN SELECT * FROM user WHERE name LIKE '张%';
优化方向:
- 添加合适索引
- 重构复杂查询
- 引入读写分离