實(shí)際上Mybatis-plus官方本來就有提供對動態(tài)表名的擴(kuò)展支持以及給出了具體的Sample。
具體參考官方的sample github。從github的提交歷史可以追溯到最開始給出動態(tài)表名實(shí)現(xiàn)方案的版本是3.1.1,奈何我司所用的版本剛好卡在3.1.0,差那么一個小版本,直接拿官方的例子用是會報錯的。
@Configuration
@MapperScan("com.baomidou.mybatisplus.samples.dytablename.mapper")
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
DynamicTableNameParser dynamicTableNameParser = new DynamicTableNameParser();
dynamicTableNameParser.setTableNameHandlerMap(new HashMap<String, ITableNameHandler>(2) {{
put("user", (metaObject, sql, tableName) -> {
String year = "_2018";
int random = new Random().nextInt(1);
if (random == 1) {
year = "_2019";
}
return tableName + year;
});
}});
paginationInterceptor.setSqlParserList(Collections.singletonList(dynamicTableNameParser));
return paginationInterceptor;
}
}
因為相關(guān)的支持,都是在3.1.1加進(jìn)去的,所幸的是,3.1.1加進(jìn)去的擴(kuò)展所依賴的核心接口并沒有改變,所以理論上是可以把3.1.1的擴(kuò)展類代碼copy到自己的項目,這樣在3.1.0的版本就可與你實(shí)現(xiàn)動態(tài)表名了。具體涉及到的擴(kuò)展類包括:
import com.baomidou.mybatisplus.extension.parsers.DynamicTableNameParser;
import com.baomidou.mybatisplus.extension.parsers.ITableNameHandler;
官方給出的只是一個隨機(jī)表名,實(shí)際業(yè)務(wù)往往還有很多問題:
-
如何在調(diào)用mapper方法時傳遞我到底要決定使用什么表名?
最開始我琢磨了那個metaObject半天,期望從它入手,但并沒有一個很通用的方式,而且在后續(xù)的版本中,這個metaObject甚至被移出了這個接口。后來才想到,這里最優(yōu)的實(shí)現(xiàn)只能使用ThreadLocal了。
image.png 如果使用ThreadLocal,那就意味著每次調(diào)用mapper方法之前都要去set一下ThreadLocal,代碼很累贅,能不能更優(yōu)雅地實(shí)現(xiàn)呢?
很容易想到,使用AOP或者手寫InvocationHandler動態(tài)代理的方式來解決。難得有機(jī)會手寫一次AOP,終于下決心認(rèn)真去學(xué)習(xí)了一下AOP那些什么切面,連接點(diǎn),增強(qiáng)各種亂七八糟的概念,好不容易學(xué)完了,發(fā)現(xiàn)其實(shí)我根本不太用的上。用不上AOP是因為下面的原因3導(dǎo)致代理的不是bean。(有空寫寫AOP那些概念,其實(shí)很簡單就能講清楚)-
使用動態(tài)代理,意味著代理的是mapper類,直接AOP代理mapper還是沒辦法拿到動態(tài)表名,怎么解決?
我想到了先用包裝模式,把mapper和dynamicTableName包裝起來,再進(jìn)行AOP,這樣包裝類實(shí)例就得手動new沒法用AOP了(也可能有別的辦法讓實(shí)例變成bean),只能自己搞InvocationHandler了。
image.png
image.png
-
因為業(yè)務(wù)上,dynamicTableName每次都是查表獲得的,mapper+dynamicTableName的組合包裝類實(shí)際上每次都new的話屬實(shí)有點(diǎn)不優(yōu)雅,應(yīng)該搞個緩存。
這里就千萬要注意我們的mapper方法調(diào)用是每個request多線程并發(fā)調(diào)用的,緩存必須使用ConcurrentHashMap。
image.png
神奇吧,一下子,把面試常用的包裝模式,動態(tài)代理,并發(fā)集合,ThreadLocal都玩了一遍,所以還是很有必要記錄一下的。
具體實(shí)現(xiàn)代碼,我已經(jīng)放在github上了。
使用@Scope("prototype") + AOP + @Cacheable實(shí)現(xiàn)
今天閑著沒事,決定嘗試完全使用spring提供的機(jī)制來實(shí)現(xiàn)。修改包括一下幾個方面:
-
使用@Scope("prototype")+@Lookup注入替代手動new MapperWrapper()。
image.png -
使用@Cacheable+ConcurrentMapCacheManager替代手動使用ConcurrentHashMap。
image.png
image.png -
使用AOP的@Aspect+@Around("within(MapperWrapper)")注解自動設(shè)置threadlocal。
image.png







