Python 官方文档:入门教程 => 点击学习
目录@Transactional跟@DS动态数据源注解冲突背景@Transactional执行流程解决方法动态数据源切换失败由事务@Transactional注解导致动态数据源切换失
前阵子写一个项目时,有个需求是要往3个库,3个表里插入数据,在同一个方法里,公司是用baomidou的@DS注解来实现配置动态数据源的。这是背景,然后呢,我在一个service方法里,就操作了这三张表,同时,我还加上了@Transactional注解,因为该方法是个save方法,我就加上了事务。
伪代码如下:
spring:
datasource:
dynamic:
primary: A
datasource:
A:
url:..
driver-class-name: com.Mysql.jdbc.Driver
username: root
passWord:
B:
url: ..
driver-class-name: com.mysql.jdbc.Driver
username: root
password:
C:
url: ..
driver-class-name: com.mysql.jdbc.Driver
username: root
password:
@Mapper
@DS("A")
public interface AMapper{
@Insert("insert into a ...")
void save();
}
@Mapper
@DS("B")
public interface BMapper{
@Insert("insert into b ...")
void save();
}
@Mapper
@DS("C")
public interface CMapper{
@Insert("insert into c ...")
void save();
}
public class aService{
@Autowired
private aMapper、bMapper、cMapper
@Transactional
public void save(){
aMapper.save();
bMapper.save(); #报错
cMapper.save();
}
}
在开发完成用postman自测时,发现bMapper.save()那一行报错了,报错内容:找不到b表。如果把@Transactional注释掉,代码正常运行,数据成功落库。我们明明在3个mapper上面都加了@DS注解来切换数据源,那为啥加了@Transactional就不行了呢?
我们在上面了解到,因为@Transactional会创建事务然后获得数据源,因为我们service方法上没有@DS注解,就拿了默认数据源,并且在这之后,这个事务信息会通过threadLocal跟当前线程绑定,事务信息包括了connection连接,也就意味着,在进入这个service方法的时候,当前事务就绑定了数据源a,在运行到bMapper.save()时,因为connection已经存在,所以拿到的数据源还是a,这时候就找不到b库里的表了。
把这三个入库操作分为3个独立的方法,并且都加上@Transactional和 @DS注解(在service上加)。ps:在完成了aMapper.save()之后去调用bMapper.save()时,一定要把@Transactional设置为Propagation.REQUIRES_NEW,这样在调用另一个事务方法时,TransactionInterceptor 会将原事务挂起,暂时性的将原事务信息和当前线程解绑。
pps:
在一个事务方法里用this来调用另一个事务方法时,@DS也会起作用,原因是this调用的不是事务对象,不会开启事务。想具体了解可以看我之前发的这篇文章 //www.jb51.net/article/222082.htm
不多BB,直接上代码:
public class DataSourceKey {
public final static String USER = "userDataSource";
public final static String REPORT = "reportDataSource";
final static List<String> SOURCES = ImmutableList.of(USER, REPORT);
public static String getDataSourceKey(String pack) {
return SOURCES.stream().filter(s -> s.startsWith(pack)).findFirst().orElse(USER);
}
}
@Component
@Aspect
@Order(-1)
@Slf4j
public class DynamicDataSourceAspect {
@Pointcut("execution(* com.in.g.data.mapper..*.*(..))")
public void dataSourcePointcut() {
}
@Before("dataSourcePointcut()")
public void doBefore(JoinPoint point) throws Throwable {
log.debug("切换数据源开始。。。。。。。。。。。。");
Package pack = point.getSignature().getDeclaringType().getPackage();
String str = StringUtils.substringAfterLast(pack.getName(), ".");
String dataSourceKey = DataSourceKey.getDataSourceKey(str);
DynamicDataSourceHolder.set(dataSourceKey);
log.debug("切换数据源成功,当前数据源:{}", dataSourceKey);
}
@After("dataSourcePointcut()")
public void doAfterReturning() throws Throwable {
DynamicDataSourceHolder.clear();
}
}
public class DynamicDataSourceHolder {
public static ThreadLocal<String> keyHolder = new ThreadLocal<>();
public static void clear() {
keyHolder.remove();
}
public static void set(String key) {
keyHolder.set(key);
}
public static String get() {
return keyHolder.get();
}
}
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.get();
}
}
//正确的代码 --这里偷懒了,直接controller调用dao层
@GetMapping("/testdb")
public String testDateSources(){
//缩小事务的范围
add();
rptFieldMapper.selectxxxx();
return "sss";
}
@Transactional(rollbackFor = Exception.class)
public void add() {
userMapper.insertXXXX(xxxx);
}
//错误的代码,@Transactional注解会导致 数据源切换失败
@GetMapping("/testdb")
@Transactional(rollbackFor = Exception.class)
public String testDateSources(){
userMapper.insertXXXX(xxxx);
rptFieldMapper.selectXXXX();
return "sss";
}
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。
--结束END--
本文标题: @Transactional跟@DS动态数据源注解冲突的解决
本文链接: https://lsjlt.com/news/134866.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-03-01
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0