知方号

知方号

多数据源@DS和@Transactional

多数据源@DS和@Transactional

考虑到业务层面有多数据源切换的需求,同时又要考虑事务,我使用了Mybatis-Plus3中的@DS作为多数据源的切换,它的原理的就是一个拦截器

@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable { try { DynamicDataSourceContextHolder.push(determineDatasource(invocation)); return invocation.proceed(); } finally { DynamicDataSourceContextHolder.poll(); }}

里面的pull和poll实际就是操作一个容器,在环绕里面进来做"压栈",出去做"弹栈",数据结构是这样的

public final class DynamicDataSourceContextHolder { /** * 为什么要用链表存储(准确的是栈) * * 为了支持嵌套切换,如ABC三个service都是不同的数据源 * 其中A的某个业务要调B的方法,B的方法需要调用C的方法。一级一级调用切换,形成了链。 * 传统的只设置当前线程的方式不能满足此业务需求,必须模拟栈,后进先出。 * */ @SuppressWarnings("unchecked") private static final ThreadLocal LOOKUP_KEY_HOLDER = new ThreadLocal() { @Override protected Object initialValue() { return new ArrayDeque(); } }; private DynamicDataSourceContextHolder() { } /** * 获得当前线程数据源 * * @return 数据源名称 */ public static String peek() { return LOOKUP_KEY_HOLDER.get().peek(); } /** * 设置当前线程数据源 *

* 如非必要不要手动调用,调用后确保最终清除 *

* * @param ds 数据源名称 */ public static void push(String ds) { LOOKUP_KEY_HOLDER.get().push(StringUtils.isEmpty(ds) ? "" : ds); } /** * 清空当前线程数据源 *

* 如果当前线程是连续切换数据源 只会移除掉当前线程的数据源名称 *

*/ public static void poll() { Deque deque = LOOKUP_KEY_HOLDER.get(); deque.poll(); if (deque.isEmpty()) { LOOKUP_KEY_HOLDER.remove(); } } /** * 强制清空本地线程 *

* 防止内存泄漏,如手动调用了push可调用此方法确保清除 *

*/ public static void clear() { LOOKUP_KEY_HOLDER.remove(); }

上面就是@DS大概实现,然后我就碰到坑了,外层service加了@Transactional,通过service调用另一个数据源做insert,在切面里看数据源切换了,但是还是显示事务内的数据源还是旧的,代码结构简单罗列下:

数据源:

dynamic: primary: master strict: false datasource: master: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://***/phorcys-centre?useSSL=false username: root password: ***** interface: url: jdbc:mysql://***/phorcys-interface?useSSL=false username: root password: ***** driver-class-name: com.mysql.cj.jdbc.Driver

外层controller调用的service

@AutowiredUserService userService;@AutowiredRedisClient redisClient;@GetMapping("/demo")@Transactionalpublic GeneralResponse demo(@RequestBody(required = false) GeneralRequest request){ SysUser sysUser = new SysUser(); sysUser.setCode("wonder"); sysUser.setName("王吉坤"); sysUser.insert(); redisClient.set("token",sysUser); List sysUsers = new SysUser().selectAll(); String item01 = userService.getUserInfo("ITEM01"); return GeneralResponse.success();}

内层service

@Servicepublic class UserServiceImpl implements UserService { @Override @DS("interface") @Transactional// @Transactional(propagation = Propagation.REQUIRES_NEW) public String getUserInfo(String name) { SapItemRecord sr = new SapItemRecord(); sr.setBatchId(1L); sr.setItemCode("ITEM01"); sr.setDescription("物料1号"); if(sr.insert()){ LambdaQueryWrapper item01 = new QueryWrapper().lambda().eq(SapItemRecord::getItemCode, name); SapItemRecord sapItemRecord = new SapItemRecord().selectOne(item01); ExceptionUtils.seed("内层事务异常");// return sapItemRecord.getDescription(); } return "response : wonder"; }}

1 最开始内层不加事务,全局只有一个事务,无效;2 内层加事务@Transactional,无效;3 改变事务的传播方式@Transactional(propagation = Propagation.REQUIRES_NEW),事务生效

看了java方法栈和源码,springframework5 里面spring-tx,知道问题出在什么地方,贴一个调用栈截图

spring的事务是基于aop的,这个不解释了,直接进入事务拦截器TransactionInterceptor,找到它调用的invokeWithinTransaction方法,只看

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至lizi9903@foxmail.com举报,一经查实,本站将立刻删除。