场景:Spring事务没有生效。
环境:SpringBoot+mybatis 或者SpringBoot+JdbcTemplate等
1. 问题原因
xml等配置信息详见:SpringBoot2.x实现链式事务(分库事务)
@Slf4j
@Configuration
public class DBConfig {
@Value("${mysql.mapperLocations}")
private String mapperLocations;
@Value("${mysql.configLocation}")
private String configLocation;
/**
* 数据源
*/
@Bean("mysqlDataSource")
@ConfigurationProperties(prefix = "mysql.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
@Bean(name = "mysqlSessionFactory")
public SqlSessionFactory sqlSessionFactorys(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
try {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
//获取配置文件的dataSource对象
sessionFactoryBean.setDataSource(dataSource);
//设置mybatis-config.xml配置文件位置
sessionFactoryBean.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
//设置mapper.xml文件所在位置
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(mapperLocations);
sessionFactoryBean.setMapperLocations(resources);
return sessionFactoryBean.getObject();
} catch (IOException e) {
log.error("mybatis解析 mapper*xml 失败", e);
return null;
} catch (Exception e) {
log.error("mybatis sqlSessionFactoryBean创建失败", e);
return null;
}
}
/**
* 操作事务的Template
* 此处传入的dataSource是mysqlDataSource的bean。
*/
@Bean(name = "mysqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(
@Qualifier("mysqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
/**
* 事务管理,此处填充的AbstractDataSource并不是mysqlDataSource的bean,而是自定义创建出来的DataSource对象。
* 因为和sqlSessionTemplate并不是一个dataSource,这也是事务失效的原因。
*/
@Bean(name = "mysqlTransactionManager")
public PlatformTransactionManager xxxTransactionManager(@Qualifier("mysqlDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(new AbstractDataSource() {
protected DataSource determineTargetDataSource() {
return dataSource;
}
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
});
}
}
如此上面的配置,在项目启动后,事务不会生效。原因:
- Spring开启事务,则事务管理器mysqlTransactionManager在开启事务时,会通过mysqlTransactionManager内部的DataSource获取connection连接;
- Spring真正操作sql时,实际上依靠的是mysqlSessionTemplate来实现的,会通过mysqlSessionTemplate内部的DataSource去缓存中获取mysqlTransactionManager开启事务时生成的connection对象,因为不是一个datasource对象,所以没有获取到,最终生成一个新的connection对象;
- 因为事务管理器的dataSource和Template的dataSource并不是同一个对象,所以获取的不是同一个connection连接,故事务不会生效。
2. 源码复现
开启事务时,事务管理器获取connection。
源码位置:org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
源码位置:org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
3. 改进方案
SqlSessionTemplate
需要和PlatformTransactionManager
是同一个dataSource,事务才会生效。
/**
* 事务管理器和mysqlSessionTemplate的dataSource必须是同一个,事务才会生效。
*/
@Bean(name = "mysqlTransactionManager")
public PlatformTransactionManager xxxTransactionManager(@Qualifier("mysqlDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}