门户网站 源码,建设网站制作项目描述,网站建设专家哪家好,wordpress请求接口的方式最近业务中出现了多商户多租户的逻辑#xff0c;所以需要分库#xff0c;项目框架使用了mybatisplus所以我们自然而然的选择了同是baomidou开发的dynamic.datasource来实现多数据源的切换。在使用初期程序运行都很好#xff0c;但之后发现在调用com.baomidou.mybatisplus.ex… 最近业务中出现了多商户多租户的逻辑所以需要分库项目框架使用了mybatisplus所以我们自然而然的选择了同是baomidou开发的dynamic.datasource来实现多数据源的切换。在使用初期程序运行都很好但之后发现在调用com.baomidou.mybatisplus.extension.service.IService.saveBatch时DS切换数据源会失效。
问题原因 进入saveBatch方法我们可以看到方法上添加了Transactional我们知道Transactional用来管理事务在事务开启后进行数据库的切换时并不会生效源代码如下当线程持有数据库连接时会复用当前线程绑定的数据库连接否则绑定默认的主库连接既然最终连接到主库说明DS并没有生效。 尝试解决问题step1 前往Github的dynamic-datasource代码仓库查看Issues发现了大量的关于DS多数据源切换无效的Issuesbut官方看起来很傲慢要么直接回复未复现要么直接关闭。 只有一条信息比较有用在调用被Transactional注解的方法的方法或类上添加DS注解我试了有效果。 但是我认为在Service和方法上加DS注解并不合适Spring框架就是因为清晰明了的分层结构深受大家喜爱控制层专注WebService层专注业务逻辑持久层专注数据库交互所以DS数据库切换放在Mapper我觉得是合理的而不应该为了解决问题硬生生的放在方法和类上来破坏这种分层结构。况且mybatisplus中那么多添加了Transactional的方法在调用的地方我都需要重写并添加DS这太2了。
尝试解决问题step2 离开Github我马上找google和度娘毕竟我遇到的这点问题前辈们可能早就遇到了并给出了解决方案。 这里不得不吐槽一下中文技术博客的现状很多偷文贼将别人的文章偷走也不标转载自哪里导致大量博客内容雷同且存在很多词不达意的内容。因为喜欢所以才会分享表达不喜欢不热爱你说你偷别人文章干啥。 根据搜索引擎的结果主要分为3种解决方案。
在Service类或者方法上添加DS注解在调用带有Transactional注解的方法前切换数据库自己实现TransactionManager在使用Transactional时手动指定来替换Spring默认的DataSourceTransactionManager 方案1在step1我自己并不认可 方案2相对方案1更加灵活毕竟因为在方法中切换可以根据不同的Service来获取需要切换的数据源但这种方案个人觉得侵入性太强需要对使用了mybatisplus批量方法的Service全部进行处理 方案3我认为风险太高自己实现TransactionManager事务、异步、同步等都需要考虑到还要保证单元测试尽可能的覆盖我不认为短时间内能做的比迭代了多年的框架更好所以也放弃
尝试解决问题step3 我们知道Spring因为AOP特性可以轻松的实现在不对原有代码侵入的情况下对特定内容进行增强所以我决定使用切面编程对mybatisplus中带有Transactional注解的方法进行拦截然后手动切换数据库注册切面部分很快完成剩下的就是调试数据库切换。 数据库切换我使用了dynamic.datasource包内的DynamicDataSourceContextHolder.push方法但遗憾的是数据库切换一直不成功并卡了很久期间使用DynamicRoutingDataSource.setPrimary方法将需要使用的数据库指定为主库运行成功但这种骚操作肯定不合适将别的库指定为主库风险肯定很大。 之后就是漫长的Debug不断的F7、F8一直没有头绪我在方法上添加了DS注解并关闭了我的切面类再进行调试突然发现了点不一样的东西不知道有没有敏感的同学发现端倪。 请关注chain变量里面包含3个拦截器更重要的是动态数据库切换的拦截器在事务拦截器前面而我们的目的不就是在事务开启前切换数据库吗那我现在的问题就是我的切面类在事务后执行的所以调整我的切面类执行优先级就好了立马把Order注解抬上来执行程序完美运行。
切面类最终代码 如果你也遇到了调用mybatisplus中批量方法无法切换多数据源的话可直接拷贝安全食用不会对现有的人和代码侵入和更改。如果你只是处理Transactional和DS的冲突你可以对切面类的作用范围小小修改即可解决你的问题。
package com.spman.common.aspect;import com.alibaba.fastjson2.JSON;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.lang.reflect.Field;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;Slf4j
Aspect
Order(0)
Component
public class MyBatisPlusServiceTransactionalAspect {/*** 存储当前切面主动切换的数据库, 在方法执行完成后主动出栈*/private static final ThreadLocalString DS_KEY new ThreadLocal();Pointcut(execution(* com.baomidou.mybatisplus.extension.service.IService.*(..)))public void myBatisPlusMethodPointcut() {}Pointcut(annotation(org.springframework.transaction.annotation.Transactional))public void transactionalPointcut() {}Before(myBatisPlusMethodPointcut() transactionalPointcut())public void beforeHandler(JoinPoint joinPoint) {String argsJson JSON.toJSONString(joinPoint.getArgs());ServiceImpl?, ? target (ServiceImpl?, ?)joinPoint.getTarget();String methodName target.getClass().getTypeName() . joinPoint.getSignature().getName();log.info(MyBatisPlusServiceAspect拦截到{}开始执行, 参数列表-{}, methodName, argsJson);Class? extends BaseMapper? mapperClass getMapperClass(target);DS dsAnnotation getDSAnnotation(mapperClass);if (dsAnnotation null) {log.info({}未绑定DS注解, 跳过数据源切换, mapperClass.getName());} else {DS_KEY.set(dsAnnotation.value());DynamicDataSourceContextHolder.push(dsAnnotation.value());log.info({}已绑定DS注解, 已主动切换数据源为{}, mapperClass.getName(), dsAnnotation.value());}}After(myBatisPlusMethodPointcut() transactionalPointcut())public void afterHandler(JoinPoint joinPoint) {String dsKey DS_KEY.get();ServiceImpl?, ? target (ServiceImpl?, ?)joinPoint.getTarget();String methodName target.getClass().getTypeName() . joinPoint.getSignature().getName();if (dsKey ! null !dsKey.isEmpty()) {DynamicDataSourceContextHolder.poll();log.info(DS_KEY线程变量为{}, 已执行数据源变量出栈操作, dsKey);} else {log.info(DS_KEY线程变量不存在, 跳过数据源变量出栈操作);}log.info(MyBatisPlusServiceAspect拦截到{}结束执行, methodName);}/*** 从ServiceImpl中获取service绑定的mapper** param target ServiceImpl实例*/SneakyThrowsprivate Class? extends BaseMapper? getMapperClass(ServiceImpl?, ? target) {Field mapperClassField target.getClass().getSuperclass().getDeclaredField(mapperClass);mapperClassField.setAccessible(true);return (Class? extends BaseMapper?) mapperClassField.get(target);}/*** 根据BaseMapper接口获取标记的DS注解** param clazz 继承自BaseMapper的mapper接口*/public static DS getDSAnnotation(Class? extends BaseMapper? clazz) {if (clazz null) return null;DS target clazz.getAnnotation(DS.class);// 找不到DS注解时从继承的接口上继续查找if (target null) {for (Class? parentInterface: clazz.getInterfaces()) {target getDSAnnotation((Class? extends BaseMapper?)parentInterface);if (target ! null) return target;}}return target;}
}如果真的需要解决问题还是需要自己耐心的跟进拒绝为了解决问题而解决问题。 参考资料
[1] mybatisplus官网: https://baomidou.com/
[2] dynamic-datasource代码仓库: https://github.com/baomidou/dynamic-datasource
[3] Spring之AOP的详细讲解: https://blog.csdn.net/m0_74097410/article/details/137476783