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


    背景

    项目中是有用到多数据源的,是用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

  • 相关阅读:
    vue滑块拖拽校验
    vue和原生自动聚焦
    vue实现bar左右拖拽
    fastclick插件使用
    三大家族易忘点和案例
    移动端调试工具chrome+devtools
    restful 与 webapi 详解
    .NET Core 中依赖注入框架详解 Autofac
    .NET Core 对象( Transient、Scope、Singleton )生命周期详解 (对象创建以及释放)
    C# 通过DataSet 获取SQL 存储过程返回的多个结果集(tables)
  • 原文地址:https://www.cnblogs.com/eaglelihh/p/15515200.html
Copyright © 2020-2023  润新知