诶,我的动态数据源怎么失效了

x33g5p2x  于2022-02-12 转载在 其他  
字(5.8k)|赞(0)|评价(0)|浏览(279)

项目中是有用到多数据源的,是用AbstractRoutingDataSource这个类来实现数据源的切换。在使用的过程中,发现在一个事务中,是没办法切换数据源的。本文就简单介绍一下场景及原因。

背景

项目中是有用到多数据源的,是用AbstractRoutingDataSource这个类来实现数据源的切换。

在使用的过程中,发现在一个事务中,是没办法切换数据源的。

下面就简单介绍一下场景及原因。

模拟现场

Mapper类如下:

@Mapper
public interface UserMapper {
    @Results(value = {
            @Result(property = "id", column = "id", javaType = Long.class, jdbcType = JdbcType.BIGINT),
            @Result(property = "age", column = "age", javaType = Integer.class, jdbcType = JdbcType.INTEGER),
            @Result(property = "name", column = "name", javaType = String.class, jdbcType = JdbcType.VARCHAR),
            @Result(property = "createTime", column = "create_time", javaType = Date.class, jdbcType = JdbcType.DATE)
    })
    @Select("SELECT id, age, name, create_time FROM user WHERE id = #{id}")
    User selectUser(Long id);
}

自定义AbstractRoutingDataSource:

public class MyDynamicDataSource extends AbstractRoutingDataSource {

    @Setter
    @Getter
    private String key;

    @Override
    protected Object determineCurrentLookupKey() {
        return key;
    }
}

一些配置:

@Configuration
@MapperScan("cn.eagle.li.spring.datasource")
@EnableTransactionManagement
public class Config {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }

    @Bean(name = "sqlSessionFactory")
    @ConditionalOnMissingBean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("myDynamicDataSource") DataSource dataSource) throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        return sessionFactory.getObject();
    }

    @Bean(name = "transactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("myDynamicDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean("myDynamicDataSource")
    public MyDynamicDataSource dataSource() {
        MyDynamicDataSource myDynamicDataSource = new MyDynamicDataSource();
        Map<Object, Object> targetDataSource = Maps.newHashMap();
        targetDataSource.put("d1", getDataSource1());
        targetDataSource.put("d2", getDataSource2());
        myDynamicDataSource.setDefaultTargetDataSource(getDataSource1());
        myDynamicDataSource.setTargetDataSources(targetDataSource);
        return myDynamicDataSource;
    }

    private static DataSource getDataSource1() {
        MysqlConnectionPoolDataSource dataSource = new MysqlConnectionPoolDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8");
        return dataSource;
    }

    private static DataSource getDataSource2() {
        MysqlConnectionPoolDataSource dataSource = new MysqlConnectionPoolDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8");
        return dataSource;
    }
}

一个测试的bean:

@Slf4j
@Component
public class MyBean {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MyDynamicDataSource myDynamicDataSource;

    public void test1() {
        myDynamicDataSource.setKey("d1");
        log.info("user:{}", userMapper.selectUser(1L));
        myDynamicDataSource.setKey("d2");
        log.info("user:{}", userMapper.selectUser(1L));
    }

    @Transactional
    public void test2() {
        test1();
    }
}

测试类:

@Slf4j
public class DataSourceMain {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);
        System.out.println(context.getBean(PlatformTransactionManager.class));
        MyBean myBean = context.getBean(MyBean.class);
        myBean.test1();
        myBean.test2();
    }
}

返回结果如下:
其中有两个数据库
test数据库里的数据如下:

test2数据库里的数据如下:

运行结果如下:

1473 [main] INFO  c.eagle.li.spring.datasource.MyBean - user:User(age=2, name=3, id=1, createTime=Thu Nov 04 00:00:00 CST 2021) 
1482 [main] INFO  c.eagle.li.spring.datasource.MyBean - user:User(age=2, name=kkk, id=1, createTime=Sat Nov 20 00:00:00 CST 2021) 
1494 [main] INFO  c.eagle.li.spring.datasource.MyBean - user:User(age=2, name=kkk, id=1, createTime=Sat Nov 20 00:00:00 CST 2021) 
1495 [main] INFO  c.eagle.li.spring.datasource.MyBean - user:User(age=2, name=kkk, id=1, createTime=Sat Nov 20 00:00:00 CST 2021)

从结果可以看出,第一个不带事务的方法,分别从test和test2两个数据库选出了数据

而第二个带事务的方法,只从test2这一个数据库选出了两条相同的数据。

原因

入口

我们从这里打断点,看谁调到了这里

不带事务的调用顺序如下:

SimpleExecuto.doQuery
SimpleExecutor.prepareStatement
BaseExecutor.getConnection
SpringManagedTransaction.getConnection
SpringManagedTransaction.openConnection
DataSourceUtils.getConnection
DataSourceUtils.doGetConnection
DataSourceUtils.fetchConnection
AbstractRoutingDataSource.getConnection
AbstractRoutingDataSource.determineTargetDataSource
MyDynamicDataSource.determineCurrentLookupKey

SimpleExecutor.prepareStatement如下:

带事务的调用顺序如下:

TransactionAspectSupport.invokeWithinTransaction
TransactionAspectSupport.createTransactionIfNecessary
AbstractPlatformTransactionManager.getTransaction
AbstractPlatformTransactionManager.startTransaction
DataSourceTransactionManager.doBegin
AbstractRoutingDataSource.getConnection
AbstractRoutingDataSource.determineTargetDataSource
MyDynamicDataSource.determineCurrentLookupKey

DataSourceTransactionManager.doBegin如下:

找不同

SimpleExecutor.prepareStatement开始

不带事务情况下:

带事务的情况下:

可以看到带事务的方法中,conHolder不为null,从这里是可以直接获得Connection

而不带事务的方法中,conHoldernull,每次都会获取一个新的Connection

什么时候放入到conHolder

看一下是怎么获取conHolder的,DataSourceUtils.doGetConnection如下:

进去看一下,TransactionSynchronizationManager.getResource如下:

继续调用,TransactionSynchronizationManager.doGetResource如下:

从上图可以看到,conHolder是从resources中获取的

resources的定义如下:
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");

经过调试,调用顺序是这样的:

DataSourceTransactionManager.doBegin
TransactionSynchronizationManager.bindResource

DataSourceTransactionManager.doBegin如下:

TransactionSynchronizationManager.bindResource如下:

结论

结论就是在事务的方法中,会提前获得一个connection放到ThreadLocal里,然后整个事务都会使用同一个connection

而不带事务的的方法,不会这么做,每次都去获得新的connection

相关文章

微信公众号

最新文章

更多