Mybatis-plus多租户项目实战进阶指南
作者:码农参上 发布时间:2023-10-10 18:14:43
在基于Mybatis-plus实现多租户架构中,介绍了在多租户项目中如果要开启一个子线程,那么需要手动进行RequestAttributes的子线程共享。如果应用场景较少的话可能也不是特复杂,但是如果场景数量上来了,还是很容易忘记的,在测试的时候才会发现疏忽了这一块。所以想了半天,决定抽取一个公共方法,用来执行这些特定的子线程。
既然要复用这类线程的执行方式,线程池是个不错的选择。这里省略创建线程池的步骤,选择直接使用spring内已经初始化好的线程池ThreadPoolTaskExecutor。下面写一个工具类,通过线程池启动子线程,实现下面几个内容:
使用线程池启动子线程前获取当前的RequestAttributes
在子线程中开启RequestAttributes的继承
测试在子线程中能否拿到Request中的租户信息
@Component
public class AsyncExecutorUtil {
@Autowired
ThreadPoolTaskExecutor threadPoolTaskExecutor;
public void doMethodWithRequest() {
ServletRequestAttributes sra = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
threadPoolTaskExecutor.execute(()->{
RequestContextHolder.setRequestAttributes(sra, true);
System.out.println(sra.getRequest().getHeader("tenantId"));
});
}
}
使用postman进行测试,发现这样做确实可以实现Request的传递,那么下一个问题就来了,我怎么把要执行的方法逻辑传递给这个线程呢?可能每次要实际执行的逻辑都不一样,所以这里使用函数式接口来传递具体方法的实现:
@FunctionalInterface
public interface FunctionInterface {
void doMethod();
}
修改线程池的执行方法,首先保存当前RequestAttributes,在启动的子线程中实现对Request的继承,最后执行函数式接口的方法:
public void doMethodWithRequest(FunctionInterface functionInterface) {
ServletRequestAttributes sra = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
threadPoolTaskExecutor.execute(()->{
RequestContextHolder.setRequestAttributes(sra, true);
System.out.println(sra.getRequest().getHeader("tenantId"));
functionInterface.doMethod();
});
}
在web请求中,在函数式接口中实现实际执行的逻辑,这里为了使结构更清楚一些没有使用lambda表达式,如果使用lambda表达式可以使这一段代码更加简洁。之后使用上面定义的异步线程工具类在子线程中执行数据库的查询:
@RestController
public class TestController {
@Autowired
AsyncExecutorUtil executorUtil;
@GetMapping("users")
public void user() {
executorUtil.doMethodWithRequest(new FunctionInterface() {
@Override
public void doMethod() {
List<User> userList = userService.getUserList();
log.info(userList.toString());
}
});
}
}
查看执行结果,可以正常执行:
[User(id=2, name=trunks, phone=13788886666, address=beijing, tenantId=2)]
到这为止,不知道大家是不是记得之前提过的一个场景,有些时候第三方的系统在调用我们的接口时可能无法携带租户信息,之后的所有数据库查询都需要我们使用重新手写sql,并添加SqlParse的过滤。
举个例子,我们系统中创建订单,调用微信支付,在前端支付成功后微信会回调我们的接口。这个时候微信是肯定不会携带租户的信息的,按照之前的做法,我们就需要先根据回调信息的订单号先使用过滤过的sql语句查出这笔订单的信息,拿到订单中包含的租户id,在之后所有被过滤掉的手写sql中手动拼接这个租户id。
但是有了上面的结果 ,对我们执行这类的请求可以产生一些改变 。之前我们是向子线程传递真实的原始Request,但是当前的Request请求不满足我们的需求,没有包含租户信息,那么重新构建一个符合我们需求的Request,并传递给子线程,那么是不是就不用去进行sql的过滤和重写了呢?
按照上面的步骤,先进行第一步,手写一个过滤租户的sql:
public interface OrderMapper extends BaseMapper<Order> {
@SqlParser(filter = true)
@Select("select * from `order` where order_number= #{orderNumber}")
Order selectWithoutTenant(String orderNumber);
}
根据这个请求,能够查询出订单的全部信息,这里面就包含了租户的id:
Order(id=3, orderNumber=6be2e3e10493454781a8c334275f126a, money=100.0, tenantId=3)
接下来重头戏来了,既然拿到了租户id,我们就来重新伪造一个Request,让这个新的Request中携带租户id,并使用这个Request执行后续的逻辑。
@AllArgsConstructor
public class FakeTenantRequest {
private String tenantId;
public ServletRequestAttributes getFakeRequest(){
HttpServletRequest request = new HttpServletRequest() {
@Override
public String getHeader(String name) {
if (name.equals("tenantId")){
return tenantId;
}
return null;
}
//...这里省略了其他需要重写的方法,不重要,可不用重写
};
ServletRequestAttributes servletRequestAttributes=new ServletRequestAttributes(request);
return servletRequestAttributes;
}
}
构造一个HttpServletRequest的过程比较复杂,里面需要重写的方法非常多,好在我们暂时都用不上所以不用重写,只重写对我们比较重要的getHeader方法即可。我们在构造方法中传进来租户id,并把这个租户id放在Request的请求头的tenantId字段,最终返回RequestAttributes。
在线程池工具类中添加一个方法,在子线程中使用我们伪造的RequestAttributes:
public void doMethodWithFakeRequest(ServletRequestAttributes fakeRequest,
FunctionInterface functionInterface) {
threadPoolTaskExecutor.execute(() -> {
RequestContextHolder.setRequestAttributes(fakeRequest, true);
functionInterface.doMethod();
});
}
模拟回调请求,这时候在请求的Header中不需要携带任何租户信息:
@GetMapping("callback")
public void callBack(String orderNumber){
Order order = orderMapper.selectWithoutTenant(orderNumber);
log.info(order.toString());
FakeTenantRequest fakeTenantRequest=new FakeTenantRequest(order.getTenantId().toString());
executorUtil.doMethodWithFakeRequest(fakeTenantRequest.getFakeRequest(),new FunctionInterface() {
@Override
public void doMethod() {
List<User> userList = userService.getUserList();
log.info(userList.toString());
}
});
}
查看执行结果:
- ==> Preparing: select * from `order` where order_number= ?
- ==> Parameters: 6be2e3e10493454781a8c334275f126a(String)
- <== Total: 1
- Order(id=3, orderNumber=6be2e3e10493454781a8c334275f126a, money=100.0, tenantId=3)
- ==> Preparing: SELECT id, name, phone, address, tenant_id FROM user WHERE (id IS NOT NULL) AND tenant_id = '3'
- ==> Parameters:
- <== Total: 1
- [User(id=1, name=hydra, phone=13699990000, address=qingdao, tenantId=3)]
在子线程中执行的sql会经过mybatis-plus的租户过滤器,在sql中添加租户id条件。这样,就实现了通过伪造Request的方式极大程度的简化了改造sql的过程。
来源:https://juejin.cn/post/7061755343784640548
猜你喜欢
- Beanutils.copyProperties()用法及重写提高效率特别说明本文介绍的是Spring(import org.springf
- 文件上传在web应用中非常普遍,要在jsp环境中实现文件上传功能是非常容易的,因为网上有许多用java开发的文件上传组件,本文以common
- 利用TCP传输数据,编写客户端和服务器的程序,实现两个程序间的实时通信。在每个程序中实现了实时的发送与接收数据的功能。客户端的io界面服务器
- 简介方案对比本处列举表示类型或状态的常用方法的对比。法1:使用数字表示(不推荐)//1:支付宝支付;2:微信支付;3:银行卡支付privat
- 提到锁,大家肯定想到的是sychronized关键字。是用它可以解决一切并发问题,但是,对于系统吞吐量要求更高的话,我们这提供几个小技巧。帮
- Java调用接口获取json数据保存到数据库今天给大家带来一个调用接口,来获取数据解析后再保存到数据库中的业务,业务中的Mapper和实体类
- 委托定义类型,类型指定特定方法签名。可将满足此签名的方法(静态或实例)分配给该类型的变量,然后(使用适当参数)直接调用该方法,或将其作为参数
- 此篇文章内容仅限于 描述springboot与 thy 自定义标签的说明,所以你在看之前,请先会使用springboot和thymeleaf
- 本文的目的是用springboot整合mybatis实现一个简单的一对多查询。(查询一个用户有多少件衣服)第一步:数据库中,可以直接在nav
- /// <summary> /// 遍历Co
- 本文实例为大家分享了Unity实现手势识别的具体代码,供大家参考,具体内容如下代码很简单没有难度,都有注解,随便 看一看 就会了。
- 网络中数据传输经常是xml或者json,现在做的一个项目之前调其他系统接口都是返回的xml格式,刚刚遇到一个返回json格式数据的接口,通过
- 这篇文章主要介绍了Java使用Collections工具类对List集合进行排序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一
- 一、登录认证基于过滤器链Spring Security的登录验证流程核心就是过滤器链。当一个请求到达时按照过滤器链的顺序依次进行处理,通过所
- 链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表、循环链表、双向链表,下面将逐一介绍。链表在数据结构中是基础
- 需求场景:当数据库中保存的部分数据需要加密,页面需要正常显示时。这是就需要我们自定义类型转换器,在Mybatis执行SQL得到结果时,通过自
- 本文实例讲述了Android编程将Activity背景设置为墙纸的简单实现方法。分享给大家供大家参考,具体如下:1)代码方式Drawable
- JPA自定义VO类型转换(EntityUtils工具类)在JPA查询中,如果需要返回自定义的类,可以使用EntityUtils工具类,该类源
- 1 pom.xml文件注:热部署功能spring-boot-1.3开始有的<!--添加依赖--><dependency&g
- jrebelJRebel是一套JavaEE开发工具。JRebel允许开发团队在有限的时间内完成更多的任务修正更多的问题,发布更高质量的软件产