MySQLspringspringboot

SpringBoot事务失效(1)—dataSource配置问题

2021-11-16  本文已影响0人  小胖学编程

场景: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);
            }
        });
    }
}

如此上面的配置,在项目启动后,事务不会生效。原因:

  1. Spring开启事务,则事务管理器mysqlTransactionManager在开启事务时,会通过mysqlTransactionManager内部的DataSource获取connection连接;
  2. Spring真正操作sql时,实际上依靠的是mysqlSessionTemplate来实现的,会通过mysqlSessionTemplate内部的DataSource去缓存中获取mysqlTransactionManager开启事务时生成的connection对象,因为不是一个datasource对象,所以没有获取到,最终生成一个新的connection对象;
  3. 因为事务管理器的dataSource和Template的dataSource并不是同一个对象,所以获取的不是同一个connection连接,故事务不会生效。

2. 源码复现

开启事务时,事务管理器获取connection。
源码位置:org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin

事务管理器.png

源码位置:org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection

实际操作的connection.png

3. 改进方案

SqlSessionTemplate需要和PlatformTransactionManager是同一个dataSource,事务才会生效。

    /**
     * 事务管理器和mysqlSessionTemplate的dataSource必须是同一个,事务才会生效。
     */
    @Bean(name = "mysqlTransactionManager")
    public PlatformTransactionManager xxxTransactionManager(@Qualifier("mysqlDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
上一篇下一篇

猜你喜欢

热点阅读