Mybatis-plus多租戶(hù)項(xiàng)目實(shí)戰(zhàn)進(jìn)階指南
在基于Mybatis-plus實(shí)現(xiàn)多租戶(hù)架構(gòu)中,介紹了在多租戶(hù)項(xiàng)目中如果要開(kāi)啟一個(gè)子線程,那么需要手動(dòng)進(jìn)行RequestAttributes的子線程共享。如果應(yīng)用場(chǎng)景較少的話可能也不是特復(fù)雜,但是如果場(chǎng)景數(shù)量上來(lái)了,還是很容易忘記的,在測(cè)試的時(shí)候才會(huì)發(fā)現(xiàn)疏忽了這一塊。所以想了半天,決定抽取一個(gè)公共方法,用來(lái)執(zhí)行這些特定的子線程。
既然要復(fù)用這類(lèi)線程的執(zhí)行方式,線程池是個(gè)不錯(cuò)的選擇。這里省略創(chuàng)建線程池的步驟,選擇直接使用spring內(nèi)已經(jīng)初始化好的線程池ThreadPoolTaskExecutor。下面寫(xiě)一個(gè)工具類(lèi),通過(guò)線程池啟動(dòng)子線程,實(shí)現(xiàn)下面幾個(gè)內(nèi)容:
- 使用線程池啟動(dòng)子線程前獲取當(dāng)前的RequestAttributes
- 在子線程中開(kāi)啟RequestAttributes的繼承
- 測(cè)試在子線程中能否拿到Request中的租戶(hù)信息
@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進(jìn)行測(cè)試,發(fā)現(xiàn)這樣做確實(shí)可以實(shí)現(xiàn)Request的傳遞,那么下一個(gè)問(wèn)題就來(lái)了,我怎么把要執(zhí)行的方法邏輯傳遞給這個(gè)線程呢?可能每次要實(shí)際執(zhí)行的邏輯都不一樣,所以這里使用函數(shù)式接口來(lái)傳遞具體方法的實(shí)現(xiàn):
@FunctionalInterface
public interface FunctionInterface {
void doMethod();
}修改線程池的執(zhí)行方法,首先保存當(dāng)前RequestAttributes,在啟動(dòng)的子線程中實(shí)現(xiàn)對(duì)Request的繼承,最后執(zhí)行函數(shù)式接口的方法:
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請(qǐng)求中,在函數(shù)式接口中實(shí)現(xiàn)實(shí)際執(zhí)行的邏輯,這里為了使結(jié)構(gòu)更清楚一些沒(méi)有使用lambda表達(dá)式,如果使用lambda表達(dá)式可以使這一段代碼更加簡(jiǎn)潔。之后使用上面定義的異步線程工具類(lèi)在子線程中執(zhí)行數(shù)據(jù)庫(kù)的查詢(xún):
@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í)行結(jié)果,可以正常執(zhí)行:
[User(id=2, name=trunks, phone=13788886666, address=beijing, tenantId=2)]
到這為止,不知道大家是不是記得之前提過(guò)的一個(gè)場(chǎng)景,有些時(shí)候第三方的系統(tǒng)在調(diào)用我們的接口時(shí)可能無(wú)法攜帶租戶(hù)信息,之后的所有數(shù)據(jù)庫(kù)查詢(xún)都需要我們使用重新手寫(xiě)sql,并添加SqlParse的過(guò)濾。
舉個(gè)例子,我們系統(tǒng)中創(chuàng)建訂單,調(diào)用微信支付,在前端支付成功后微信會(huì)回調(diào)我們的接口。這個(gè)時(shí)候微信是肯定不會(huì)攜帶租戶(hù)的信息的,按照之前的做法,我們就需要先根據(jù)回調(diào)信息的訂單號(hào)先使用過(guò)濾過(guò)的sql語(yǔ)句查出這筆訂單的信息,拿到訂單中包含的租戶(hù)id,在之后所有被過(guò)濾掉的手寫(xiě)sql中手動(dòng)拼接這個(gè)租戶(hù)id。
但是有了上面的結(jié)果 ,對(duì)我們執(zhí)行這類(lèi)的請(qǐng)求可以產(chǎn)生一些改變 。之前我們是向子線程傳遞真實(shí)的原始Request,但是當(dāng)前的Request請(qǐng)求不滿(mǎn)足我們的需求,沒(méi)有包含租戶(hù)信息,那么重新構(gòu)建一個(gè)符合我們需求的Request,并傳遞給子線程,那么是不是就不用去進(jìn)行sql的過(guò)濾和重寫(xiě)了呢?
按照上面的步驟,先進(jìn)行第一步,手寫(xiě)一個(gè)過(guò)濾租戶(hù)的sql:
public interface OrderMapper extends BaseMapper<Order> {
@SqlParser(filter = true)
@Select("select * from `order` where order_number= #{orderNumber}")
Order selectWithoutTenant(String orderNumber);
}根據(jù)這個(gè)請(qǐng)求,能夠查詢(xún)出訂單的全部信息,這里面就包含了租戶(hù)的id:
Order(id=3, orderNumber=6be2e3e10493454781a8c334275f126a, money=100.0, tenantId=3)
接下來(lái)重頭戲來(lái)了,既然拿到了租戶(hù)id,我們就來(lái)重新偽造一個(gè)Request,讓這個(gè)新的Request中攜帶租戶(hù)id,并使用這個(gè)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;
}
//...這里省略了其他需要重寫(xiě)的方法,不重要,可不用重寫(xiě)
};
ServletRequestAttributes servletRequestAttributes=new ServletRequestAttributes(request);
return servletRequestAttributes;
}
}構(gòu)造一個(gè)HttpServletRequest的過(guò)程比較復(fù)雜,里面需要重寫(xiě)的方法非常多,好在我們暫時(shí)都用不上所以不用重寫(xiě),只重寫(xiě)對(duì)我們比較重要的getHeader方法即可。我們?cè)跇?gòu)造方法中傳進(jìn)來(lái)租戶(hù)id,并把這個(gè)租戶(hù)id放在Request的請(qǐng)求頭的tenantId字段,最終返回RequestAttributes。
在線程池工具類(lèi)中添加一個(gè)方法,在子線程中使用我們偽造的RequestAttributes:
public void doMethodWithFakeRequest(ServletRequestAttributes fakeRequest,
FunctionInterface functionInterface) {
threadPoolTaskExecutor.execute(() -> {
RequestContextHolder.setRequestAttributes(fakeRequest, true);
functionInterface.doMethod();
});
}模擬回調(diào)請(qǐng)求,這時(shí)候在請(qǐng)求的Header中不需要攜帶任何租戶(hù)信息:
@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í)行結(jié)果:
- ==> 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會(huì)經(jīng)過(guò)mybatis-plus的租戶(hù)過(guò)濾器,在sql中添加租戶(hù)id條件。這樣,就實(shí)現(xiàn)了通過(guò)偽造Request的方式極大程度的簡(jiǎn)化了改造sql的過(guò)程。
總結(jié)
到此這篇關(guān)于Mybatis-plus多租戶(hù)項(xiàng)目實(shí)戰(zhàn)進(jìn)階指南的文章就介紹到這了,更多相關(guān)Mybatis-plus多租戶(hù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Spring與Mybatis的整合方法(基于Eclipse的搭建)
這篇文章主要介紹了Spring與Mybatis的整合方法(基于Eclipse的搭建),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
淺談十個(gè)常見(jiàn)的Java異常出現(xiàn)原因
這篇文章主要介紹了十個(gè)常見(jiàn)的Java異常出現(xiàn)原因,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
java實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07
使用JMeter進(jìn)行接口高并發(fā)測(cè)試的實(shí)現(xiàn)
本文主要介紹了使用JMeter進(jìn)行接口高并發(fā)測(cè)試的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
Java中常見(jiàn)的查找算法與排序算法總結(jié)
數(shù)據(jù)結(jié)構(gòu)是數(shù)據(jù)存儲(chǔ)的方式,算法是數(shù)據(jù)計(jì)算的方式。所以在開(kāi)發(fā)中,算法和數(shù)據(jù)結(jié)構(gòu)息息相關(guān)。本文為大家整理了Java中常見(jiàn)的查找與排序算法的實(shí)現(xiàn),需要的可以參考一下2023-03-03
教你如何使用google.zxing結(jié)合springboot生成二維碼功能
這篇文章主要介紹了使用google.zxing結(jié)合springboot生成二維碼功能,我們使用兩種方式,去生成二維碼,但是其實(shí),二維碼的生成基礎(chǔ),都是zxing包,這是Google開(kāi)源的一個(gè)包,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05

