参考文章: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不生效

  1. 被同一个对象的方法调用,@Transactional(propagation = Propagation.REQUIRES_NEW)不会生效,此时可以在在该类中引入该类的父类接口,不能直接引入该类的对象,会造成循环依赖,通过父类的对象调用,可以实现不在同一个对象中调用的效果,特别注意,用this调用是不行的。此外,还可以使用AopContext.currentProxy()方法
  2. 用在非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所做的操作是无法实现事务的效果的


本站总访问量:次本站总访问数:人
本站由 Peng Tong 使用 Stellar 1.29.1 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。