V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
choice4
V2EX  ›  Java

多数据源和事务

  •  1
     
  •   choice4 · 2018-12-17 18:43:55 +08:00 · 3650 次点击
    这是一个创建于 1928 天前的主题,其中的信息可能已经有所发展或是发生改变。
    各位在用 mybatis 的时候,如果碰到多数据源的时候是不是使用 aop 的方式做动态多数据源呢? 如果是的话一般切入点是什么?我是在网上看了一些,主要是两部分。有的是通过 MapperScan 分包。为每个 DataSource 都去做单独的 SqlSessionFactory 以及 TransactionManager 等。 还有一种就是从 AbstractRoutingDataSource 上下功夫,大部分都起名字叫 DynamicDataSource 这种。然后将每个数据源放入 map,set 到这个 bean 里面。然后定义 SQLSessionFactory 将这个类 set 进去。其中看着比较舒服的是做个注解。然后切入点直接走注解。但是我写下来之后,增删改查的确是按照预期分开了。但是由于切入点是在 dao 层。事务的话 @Transactional 一般都会写在 service 层。 这个时候就会发生设置数据源发生在设置事务之后然后就凉了(这个结论正确性不敢打保票,看了一些文章暂下的结论)。特来取经。。问下多数据源都是怎么弄的。 其中还看到一个 AtomikosDataSource 还没有细琢磨。暂时是默认的 HikariDataSource。 springboot 2
    15 条回复    2019-12-19 13:50:23 +08:00
    abcbuzhiming
        1
    abcbuzhiming  
       2018-12-17 23:51:52 +08:00
    多数据源时的事务一致性属于跨库事务,跨库事务非常麻烦而且绝对不是你描述的这种结构能摆平的,一般的注解式事务只能针对单库,所以你用法就是错的,如果你再 service 层用了 @Transactional,那你就要保证这个 service 里调用的 mapper 都是一个库的,否则的话,就不要在 service 上用 @Transactional
    helloZwq
        2
    helloZwq  
       2018-12-18 09:15:56 +08:00
    同楼主,用的 AbstractRoutingDataSource,注解+切面 切换数据源
    choice4
        3
    choice4  
    OP
       2018-12-18 10:15:22 +08:00
    @abcbuzhiming
    /*
    如果你再 service 层用了 @Transactional,那你就要保证这个 service 里调用的 mapper 都是一个库的
    */
    我是在 service 层用了 @Transactional, 同时我在测试的时候也保证了这个 service 中的 mapper 都是一个库的,并且手动抛出了异常。 但是不行。因为是根据 dao 层方法上的注解去选择数据源。但是事务的开启是在 service 层, 但是开启一个事务又需要有确定的数据源。由于现在数据源还没有确定下来,也就是 AbstractRoutingDataSource 里面那个 determinkey 方法还没有执行,动态数据源还没有确定下来,(即开启事务的 aop 先于动态选择数据源的 aop 执行了)就出现了如下错误:
    Could not open JDBC Connection for transaction; nested exception is java.lang.IllegalStateException: Cannot determine target DataSource for lookup key [null]
    就是这样了。不知道有没有说明白
    choice4
        4
    choice4  
    OP
       2018-12-18 10:15:52 +08:00
    @helloZwq 事务呢?怎么加的
    abcbuzhiming
        5
    abcbuzhiming  
       2018-12-18 10:47:57 +08:00
    @choice4 我明白了,你的事务管理器不止一个,那你必须明确的在 service 层的 @Transactional 里指定特定的事务管理器,否则就出错
    choice4
        6
    choice4  
    OP
       2018-12-18 11:15:00 +08:00
    @abcbuzhiming 对 最开始想弄成一个事务管理器。大概如这中 retrun new DataSourceManager(dynamicDataSource)。
    然后就会出现我在 #3 出现的那种错误。
    后来我干脆就创建两个 DataSourceManager 的 bean。 比如分别指定 beanName 为 A, B。
    然后在 service 层比如我用到 ADao 类。 就使用 @Transactional(transactionManager="A", rollbackFor=xxx.class)
    并在 service 层抛出相应异常,但是不能回滚。我 debug 跟断点是
    TransactionInterceptor.invoke(MethodInvocation) ---->
    TransactionAspectJSupport.invokeWithinTransaction(Method, Class, final InvocationCallback) ----> TransactionAspectJSupport.completeTransactionAfterThrowing(TransactionInfo, Throwable)
    看到了代码执行了
    txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
    这一行 。并且是正常执行(这行没有出现异常)。但是事务就是没有回滚,想不明白为什么。难道我插入数据时使用的连接,和抛出异常事务回滚时的连接不一样?
    choice4
        7
    choice4  
    OP
       2018-12-18 11:16:10 +08:00
    楼上应该是 DataSourceTransactionManager 不是 DataSourceManager。 打错了
    abcbuzhiming
        8
    abcbuzhiming  
       2018-12-18 13:10:58 +08:00
    @choice4 不回滚?你确定你的数据库事务设置没问题吗?变成单数据库,单事务管理器,同样的 serviceImpl 代码看是否回滚
    choice4
        9
    choice4  
    OP
       2018-12-18 13:34:40 +08:00
    @abcbuzhiming 确定,不过不知道是不是别的哪里写错了 大致代码意思如下吧

    @Configuration
    class Config{
    @Bean("a")
    @PropertiesConfiguration("...")
    public DataSource a() {return new DataSource;}

    @Bean("b")
    @PropertiesConfiguration("...")
    public DataSource a() {return new DataSource;}

    @Bean("dynamicDS")
    public DataSource dataSource() {new DynamicDataSource(); setMap(asMap(a(), b())); return;}

    @Bean
    public SqlSessionFactory fac() {return new SqlSessionFactoryBean(dataSource()).getObject();}

    @Bean("at")
    public TransactionManager ma() {
    return new TransactionManager(a());
    // return new TransactionManager(dataSource()); //这种就会出现我在 #3 写的那种情况
    }
    }

    @Service
    class Service {
    @Autowired
    ADao aDao;
    @Transactional(transaction="at", rollbackFor=RuntimeException.class)
    public void insert() {
    aDao.insert();
    throw new RuntimeException();
    }
    }

    @Mapper
    interface aDao{
    @DataSource("a") //自定义注解,aop 会动态选择 a 数据源
    @Insert("......")
    int insert();
    }

    @SpringbootTest
    class Test() {
    @Autowired
    Service aService;

    @Junit.Test
    void test() {
    aService.insert();
    }
    }

    大概就是这个意思的代码吧。
    choice4
        10
    choice4  
    OP
       2018-12-18 13:34:58 +08:00
    空格被吃了
    abcbuzhiming
        11
    abcbuzhiming  
       2018-12-18 16:52:16 +08:00
    @choice4 感觉没写错,你单数据库,单事务管理器,确认一下是否能回滚
    choice4
        12
    choice4  
    OP
       2018-12-18 17:36:41 +08:00
    @abcbuzhiming 单数据源可以回滚, (我是直接是在创建 SqlSessionFactory bean 的时候 bean.setDataSource(a()))
    这种方式设置的 (成功回滚)。
    原来动态多数据源的时候相当于是 bean.setDataSource(dynamicDataSource()) (无法回滚)
    afsun
        13
    afsun  
       2019-10-29 10:41:49 +08:00
    @choice4 请问后面解决了吗
    choice4
        14
    choice4  
    OP
       2019-10-29 12:43:21 +08:00 via Android
    @afsun 当时最后应该是 mapperscan 分包了,没用动态数据源,同时事务里不要有跨库的数据源 ,这个做不了分布式事务
    afsun
        15
    afsun  
       2019-12-19 13:50:23 +08:00 via Android
    @choice4 谢谢。用 jta 实现了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3162 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 12:30 · PVG 20:30 · LAX 05:30 · JFK 08:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.