“This is the 15th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”
In the implementation of multi-tenant architecture based on Mybatis- Plus, it is introduced that if you want to start a sub-thread in a multi-tenant project, then you need to manually share RequestAttributes sub-threads. It may not be too complicated if there are fewer scenarios, but if the number of scenarios increases, it’s easy to forget, and you’ll find this missing area when testing. So after much thought, I decided to extract a public method that would execute these particular child threads.
Since you want to reuse the execution of such threads, thread pools are a good choice. Instead of creating a thread pool, you choose to use the thread pool ThreadPoolTaskExecutor that is already initialized in Spring. A utility class that starts a child thread from a thread pool does the following:
- Gets the current before starting the child thread using the thread pool
RequestAttributes
- Opened in a child thread
RequestAttributes
The inheritance of - Test whether it is available in the child thread
Request
Tenant information in
@Component
public class AsyncExecutorUtil {
@Autowired
ThreadPoolTaskExecutor threadPoolTaskExecutor;
public void doMethodWithRequest(a) {
ServletRequestAttributes sra = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
threadPoolTaskExecutor.execute(()->{
RequestContextHolder.setRequestAttributes(sra, true);
System.out.println(sra.getRequest().getHeader("tenantId")); }); }}Copy the code
Testing with Postman shows that this does work for Request delivery, so the next question is, how do I pass the method logic I want to execute to this thread? The actual logic may be different each time, so a functional interface is used to pass the implementation of a specific method:
@FunctionalInterface
public interface FunctionInterface {
void doMethod(a);
}
Copy the code
Modify the thread pool execution method, first save the current RequestAttributes, implement Request inheritance in the initiated child thread, and finally execute the functional interface method:
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();
});
}
Copy the code
In a Web request, the actual execution logic is implemented in a functional interface. Lambda expressions are not used here to make the structure clearer. Lambda expressions would make this code much more concise. Then execute the database query in the child thread using the asynchronous thread utility class defined above:
@RestController
public class TestController {
@Autowired
AsyncExecutorUtil executorUtil;
@GetMapping("users")
public void user(a) {
executorUtil.doMethodWithRequest(new FunctionInterface() {
@Override
public void doMethod(a) { List<User> userList = userService.getUserList(); log.info(userList.toString()); }}); }}Copy the code
Viewing the result, the command can be executed properly:
[User(id=2, name=trunks, phone=13788886666, address=beijing, tenantId=2)]
Copy the code
At this point, I don’t know if you remember from the previous scenario, sometimes a third party system may not be able to carry tenant information when calling our interface, and all subsequent database queries require us to use re-written SQL and add SqlParse filtering.
For example, when we create an order in our system and call wechat Payment, wechat will call back our interface after the front-end payment is successful. At this time, wechat will definitely not carry the information of tenants. According to the previous practice, we need to find out the information of this order by using filtered SQL statement according to the order number of callback information, get the tenant ID contained in the order, and then manually join this tenant ID in all filtered handwritten SQL.
But with the results above, it might make a difference for us to perform this kind of request. Before, we passed the original Request to the child thread, but the current Request does not meet our requirements, and does not contain tenant information, so rebuild a Request that meets our requirements, and pass to the child thread, then does not need to carry out SQL filtering and rewriting?
Create a SQL filter for the tenant by writing it as follows:
public interface OrderMapper extends BaseMapper<Order> {
@SqlParser(filter = true)
@Select("select * from `order` where order_number= #{orderNumber}")
Order selectWithoutTenant(String orderNumber);
}
Copy the code
From this request, you can query all the information about the order, which includes the tenant ID:
The Order (id = 3, orderNumber = 6 be2e3e10493454781a8c334275f126a, money = 100.0, tenantId = 3)Copy the code
Now that we have the tenant ID, we’ll forge a new Request that carries the tenant ID and uses the Request to execute the subsequent logic.
@AllArgsConstructor
public class FakeTenantRequest {
private String tenantId;
public ServletRequestAttributes getFakeRequest(a){
HttpServletRequest request = new HttpServletRequest() {
@Override
public String getHeader(String name) {
if (name.equals("tenantId")) {return tenantId;
}
return null;
}
/ /... I'm omitting all the other methods that I need to rewrite here, but it's not important, so I don't need to rewrite it
};
ServletRequestAttributes servletRequestAttributes=new ServletRequestAttributes(request);
returnservletRequestAttributes; }}Copy the code
The process of constructing an HttpServletRequest is quite complicated, and there are a lot of methods that need to be overridden. Fortunately, we don’t need any of them yet, so just rewrite the getHeader method that is important to us. We pass in the tenantId in the constructor and put the tenantId in the tenantId field of the Request header, which returns the RequestAttributes.
Add a method to the thread pool utility class that uses our bogus RequestAttributes in the child thread:
public void doMethodWithFakeRequest(ServletRequestAttributes fakeRequest, FunctionInterface functionInterface) {
threadPoolTaskExecutor.execute(() -> {
RequestContextHolder.setRequestAttributes(fakeRequest, true);
functionInterface.doMethod();
});
}
Copy the code
Simulate a callback request that does not need to carry any tenant information in the Header of the request:
@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(a) { List<User> userList = userService.getUserList(); log.info(userList.toString()); }}); }Copy the code
View the execution result:
- ==> 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)]
Copy the code
The SQL executed in the child thread passes through mybatis- Plus tenant filter to add the tenant ID condition to the SQL. This greatly simplifies the process of modifying SQL by faking the Request.
The last
If you feel helpful, you can click a “like” ah, thank you very much ~
Nongcanshang, an interesting, in-depth and direct public account that loves sharing, will talk to you about technology. Welcome to Hydra as a “like” friend.