参考文章:https://blog.csdn.net/PyongSen/article/details/125421724
问题
代码
1 2 3 4
| @Transactional(rollbackFor = Exception.class) public Boolean feedback(String worderid, String currstatus){ return redrOrderService.upMidData() > 0; }
|
1 2 3 4 5
| @DS("hty") public int upMidData() { String sql = "select * from test"; return jdbcTemplate.query(sql); }
|
结果

从上图可以看出,调用了push方法,说明@DS产生的拦截器已经生效了,但是整个过程并没有执行peek,说明并没有切换数据源,后面报错找不到表也证明了这一点
原因
- 如果不存在事务(这里指整个过程都没有事务,因为一旦某个方法开启事务,不设置的话,这个事务会沿着方法调用链一直传递下去),每次调用service层方法或者mapper层方法,都会从数据库连接池获取数据库连接,所以搭配@DS可以设置成我们想要的数据源(@DS本质是通过拦截器生效的)
- 如果存在事务,则只会在事务开启时从数据库连接池获取数据库连接,并且事务和数据库连接绑定了,继续往下执行,也不会从数据库连接池获取数据库连接,即使使用了@DS,也只加一层拦截器而已,并不能切换数据库连接
平时我们切换数据源都是直接使用@DS,但是它本身并没有切换数据源的作用,spring boot 整合 mybatis plus环境下,系统每次从数据库连接池获取数据库连接时,都会从DynamicDataSourceContextHolder类中获取最新的数据源名,如果没有则使用默认数据源,而@DS的作用则是通过拦截器在设置数据库连接之前设置最新的数据源名,DynamicDataSourceContextHolder类中有一个队列,存储数据源名,并且由于队列的特点,允许@DS多层设置
解决
在方法上加入@Transactional(propagation = Propagation.REQUIRES_NEW)
1 2 3 4
| @Transactional(rollbackFor = Exception.class) public Boolean feedback(String worderid, String currstatus){ return redrOrderService.upMidData() > 0; }
|
1 2 3 4 5 6
| @DS("hty") @Transactional(propagation = Propagation.REQUIRES_NEW) public int upMidData() { String sql = "select * from test"; return jdbcTemplate.query(sql); }
|
@Transactional不生效
- 被同一个对象的方法调用,@Transactional(propagation = Propagation.REQUIRES_NEW)不会生效,此时可以在在该类中引入该类的父类接口,不能直接引入该类的对象,会造成循环依赖,通过父类的对象调用,可以实现不在同一个对象中调用的效果,特别注意,用this调用是不行的。此外,还可以使用AopContext.currentProxy()方法
- 用在非public方法上
@DS最终是如何生效的
我们知道@DS通过拦截器,设置当前的数据源,上面提到过每次从数据库连接池获取数据库连接时,都会从DynamicDataSourceContextHolder类中获取最新的数据源名,而这个就是在com.baomidou.dynamic.datasource.DynamicRoutingDataSource#getDataSource中实现的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public DataSource getDataSource(String ds) { if (StringUtils.isEmpty(ds)) { return this.determinePrimaryDataSource(); } else if (!this.groupDataSources.isEmpty() && this.groupDataSources.containsKey(ds)) { log.debug("从 {} 组数据源中返回数据源", ds); return ((DynamicGroupDataSource)this.groupDataSources.get(ds)).determineDataSource(); } else if (this.dataSourceMap.containsKey(ds)) { log.debug("从 {} 单数据源中返回数据源", ds); return (DataSource)this.dataSourceMap.get(ds); } else if (this.strict) { throw new RuntimeException("不能找到名称为" + ds + "的数据源"); } else { return this.determinePrimaryDataSource(); } }
|
这个方法根据不同的数据源名称,返回不同的数据源,是在determineDataSource中调用的
com.baomidou.dynamic.datasource.DynamicRoutingDataSource#determineDataSource
1 2 3
| public DataSource determineDataSource() { return this.getDataSource(DynamicDataSourceContextHolder.peek()); }
|
determineDataSource方法是获取DynamicDataSourceContextHolder中最新的数据源名称,是在com.baomidou.dynamic.datasource.AbstractRoutingDataSource#getConnection()中调用的
1 2 3
| public Connection getConnection() throws SQLException { return this.determineDataSource().getConnection(); }
|
AbstractRoutingDataSource是DataSource的抽象实现,代码中DataSource调用getConnection的时候,最后调用的就是AbstractRoutingDataSource的getConnection方法,实现了动态切换数据源的效果
DynamicRoutingDataSource继承了AbstractRoutingDataSource,determineDataSource是AbstractRoutingDataSource的抽象方法
为什么和事务有关系
既然动态切换是在getConnection中实现的,为什么通过jdbcTemplate时会和事务有关系,按道理jdbcTemplate在获取connection时就可以动态切换了,jdbcTemplate获取连接时,调用的以下方法
org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource); if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) { logger.debug("Fetching JDBC Connection from DataSource"); Connection con = fetchConnection(dataSource); if (TransactionSynchronizationManager.isSynchronizationActive()) { try { ConnectionHolder holderToUse = conHolder; if (conHolder == null) { holderToUse = new ConnectionHolder(con); } else { conHolder.setConnection(con); }
holderToUse.requested(); TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(holderToUse, dataSource)); holderToUse.setSynchronizedWithTransaction(true); if (holderToUse != conHolder) { TransactionSynchronizationManager.bindResource(dataSource, holderToUse); } } catch (RuntimeException var4) { releaseConnection(con, dataSource); throw var4; } }
return con; } else { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(fetchConnection(dataSource)); }
return conHolder.getConnection(); } }
|
可以看到在获取connection时,会先查看当前是不是事务中,如果是,则直接获取事务的connection,所以上面为什么要新开事务就是这个原因,如果用老的事务,那么connection也是老的事务的,而@DS就无法生效了,而如果没有事务,则通过dataSource.getConnection()获取,mybatis也是同理,这里提一下,事务开启时,会获取一次connection,jdbc事务的基础就是建立在connection上的,如果在事务中,我们在代码里面自己获取connection的话,我们自己获取的connection所做的操作是无法实现事务的效果的