Mybatis-plus多租戶項目實戰(zhàn)進階指南
在基于Mybatis-plus實現(xiàn)多租戶架構中,介紹了在多租戶項目中如果要開啟一個子線程,那么需要手動進行RequestAttributes的子線程共享。如果應用場景較少的話可能也不是特復雜,但是如果場景數量上來了,還是很容易忘記的,在測試的時候才會發(fā)現(xiàn)疏忽了這一塊。所以想了半天,決定抽取一個公共方法,用來執(zhí)行這些特定的子線程。
既然要復用這類線程的執(zhí)行方式,線程池是個不錯的選擇。這里省略創(chuàng)建線程池的步驟,選擇直接使用spring內已經初始化好的線程池ThreadPoolTaskExecutor。下面寫一個工具類,通過線程池啟動子線程,實現(xiàn)下面幾個內容:
- 使用線程池啟動子線程前獲取當前的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進行測試,發(fā)現(xiàn)這樣做確實可以實現(xiàn)Request的傳遞,那么下一個問題就來了,我怎么把要執(zhí)行的方法邏輯傳遞給這個線程呢?可能每次要實際執(zhí)行的邏輯都不一樣,所以這里使用函數式接口來傳遞具體方法的實現(xiàn):
@FunctionalInterface public interface FunctionInterface { void doMethod(); }
修改線程池的執(zhí)行方法,首先保存當前RequestAttributes,在啟動的子線程中實現(xiàn)對Request的繼承,最后執(zhí)行函數式接口的方法:
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請求中,在函數式接口中實現(xiàn)實際執(zhí)行的邏輯,這里為了使結構更清楚一些沒有使用lambda表達式,如果使用lambda表達式可以使這一段代碼更加簡潔。之后使用上面定義的異步線程工具類在子線程中執(zhí)行數據庫的查詢:
@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()); } }); } }
查看執(zhí)行結果,可以正常執(zhí)行:
[User(id=2, name=trunks, phone=13788886666, address=beijing, tenantId=2)]
到這為止,不知道大家是不是記得之前提過的一個場景,有些時候第三方的系統(tǒng)在調用我們的接口時可能無法攜帶租戶信息,之后的所有數據庫查詢都需要我們使用重新手寫sql,并添加SqlParse的過濾。
舉個例子,我們系統(tǒng)中創(chuàng)建訂單,調用微信支付,在前端支付成功后微信會回調我們的接口。這個時候微信是肯定不會攜帶租戶的信息的,按照之前的做法,我們就需要先根據回調信息的訂單號先使用過濾過的sql語句查出這筆訂單的信息,拿到訂單中包含的租戶id,在之后所有被過濾掉的手寫sql中手動拼接這個租戶id。
但是有了上面的結果 ,對我們執(zhí)行這類的請求可以產生一些改變 。之前我們是向子線程傳遞真實的原始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執(zhí)行后續(xù)的邏輯。
@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()); } }); }
查看執(zhí)行結果:
- ==> 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)]
在子線程中執(zhí)行的sql會經過mybatis-plus的租戶過濾器,在sql中添加租戶id條件。這樣,就實現(xiàn)了通過偽造Request的方式極大程度的簡化了改造sql的過程。
總結
到此這篇關于Mybatis-plus多租戶項目實戰(zhàn)進階指南的文章就介紹到這了,更多相關Mybatis-plus多租戶內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解Spring與Mybatis的整合方法(基于Eclipse的搭建)
這篇文章主要介紹了Spring與Mybatis的整合方法(基于Eclipse的搭建),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10使用JMeter進行接口高并發(fā)測試的實現(xiàn)
本文主要介紹了使用JMeter進行接口高并發(fā)測試的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-04-04教你如何使用google.zxing結合springboot生成二維碼功能
這篇文章主要介紹了使用google.zxing結合springboot生成二維碼功能,我們使用兩種方式,去生成二維碼,但是其實,二維碼的生成基礎,都是zxing包,這是Google開源的一個包,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2022-05-05