小伙伴们将另外一个项目的代码迁移到当前项目来,为了图省事数据库直接拷贝到一个新的数据库里,在自己项目里使用动态数据源的方式来配置相应的代码访问到对应的数据库。
所以也就有了这篇文章。
1 | <dependency> |
现象
接口始终报错:
1 | org.postgresql.util.PSQLException: ERROR: relation "xxx_tablename" does not exist |
部分接口能够正常工作,使用到的是相同的mapper或者service类。
在接口异常的方法里调用mapper的对应方法,任何一个mapper方法都会报上述错误。
分析
看到这个现象的第一反应是数据库没有完全迁移过来,仔细检查后发现没有问题。
然后因为是使用pg,所以分析是不是对应的schema没配置对,仔细检查后发现也没有问题。
再仔细观察现象及对比部分接口,发现基本上查询类的接口没问题,更新新增的接口会有问题。
再仔细定位发现只要有Transational注解的方法就有问题,没有对应注解的方法没问题。
这样就能够大致猜到问题的原因了。
因为使用的动态数据源,动态数据源的实现原理就是使用动态代理的方式生成新的代理对象。
而transactional注解的实现方式也是使用动态代理,这两个组件在实现的时候应该是代理对象冲突了。
差了下果然有类似的坑出现:
@Transactional跟@DS动态数据源注解冲突_林蜗牛snail的博客-CSDN博客
问题原因:
@Transactional执行流程
save方法添加了 @Transactional 注解,Spring 事务就会生效。此时,Spring TransactionInterceptor 会通过 AOP 拦截该方法,创建事务。而创建事务,势必就会获得数据源。那么,TransactionInterceptor 会使用 Spring DataSourceTransactionManager 创建事务,并将事务信息通过 ThreadLocal 绑定在当前线程。
而事务信息,就包括事务对应的 Connection 连接。那也就意味着,还没走到 OrderMapper 的查询操作,Connection 就已经被创建出来了。并且,因为事务信息会和当前线程绑定在一起,在 OrderMapper 在查询操作需要获得 Connection 时,就直接拿到当前线程绑定的 Connection ,而不是 OrderMapper 添加 @DS 注解所对应的 DataSource 所对应的 Connection 。
OK ,那么我们现在可以把问题聚焦到 DataSourceTransactionManager 是怎么获取 DataSource 从而获得 Connection 的了。对于每个 DataSourceTransactionManager 数据库事务管理器,创建时都会传入其需要管理的 DataSource 数据源。在使用 dynamic-datasource-spring-boot-starter 时,它创建了一个 DynamicRoutingDataSource ,传入到 DataSourceTransactionManager 中。
而 DynamicRoutingDataSource 负责管理我们配置的多个数据源。例如说,本示例中就管理了 a、b、c 三个数据源,并且默认使用 a 数据源。那么在当前场景下,DynamicRoutingDataSource 需要基于 @DS 获得数据源名,从而获得对应的 DataSource ,结果因为我们在 Service 方法上,并没有添加 @DS 注解,所以它只好返回默认数据源,也就是 a 。故此,就发生了 找不到表 的异常。
解决办法
把这三个入库操作分为3个独立的方法,并且都加上@Transactional和 @DS注解(在service上加)
并且把事务的传播属性改成
1 | @Transactional设置为 Propagation.REQUIRES_NEW |
这里就用到了面试时候常被问到的事务传播属性问题。