Mybatis-plus使用TableNameHandler分表詳解(附完整示例源碼)
為什么要分表
Mysql是當(dāng)前互聯(lián)網(wǎng)系統(tǒng)中使用非常廣泛的關(guān)系數(shù)據(jù)庫,具有ACID的特性。
但是mysql的單表性能會受到表中數(shù)據(jù)量的限制,主要原因是B+樹索引過大導(dǎo)致查詢時索引無法全部加載到內(nèi)存。讀取磁盤的次數(shù)變多,而磁盤的每次讀取對性能都有很大的影響。
這時一個簡單可行的方案就是分表(當(dāng)然土豪也可以堆硬件),將一張數(shù)據(jù)量龐大的表的數(shù)據(jù),拆分到多個表中,這同時也減少了B+樹索引的大小,減少磁盤讀取次數(shù),提高性能。
兩種基礎(chǔ)分表邏輯
說完了為什么要分表,下面聊聊業(yè)務(wù)開發(fā)中常見的兩種基礎(chǔ)的分表邏輯。
按日期分表
這種方式通常會在表名的最后加上年月日,主要適用于按日期劃分的統(tǒng)計數(shù)據(jù)或操作記錄。在線實時展示的只有最近表中的數(shù)據(jù),其他數(shù)據(jù)用于離線統(tǒng)計等。
按id取模分表
這種方式需要一個id生成器,例如snowflake id或分布式id服務(wù)。它保證了相同id的數(shù)據(jù)都在一張表中,主要適用于保存用戶基礎(chǔ)信息,系統(tǒng)中的資源信息,購買記錄等。當(dāng)然這種分表方式擴展性較差,后期數(shù)據(jù)持續(xù)增多后需要按id大小分庫再分表處理。
下面看下這兩種分表邏輯在mybatis-plus中的實現(xiàn)。
Mybatis-plus中的分表實現(xiàn)
說到j(luò)ava的分表中間件,可能有人會想到sharding-jdbc,作為使用很廣泛的一個分表中間件,功能也比較完善,但是使用它需要引入額外的jar包和增加學(xué)習(xí)成本。
實際上mybatis-plus本身就提供了一個分表的解決方案,配置使用都很簡單,適合快速開發(fā)系統(tǒng)。
動態(tài)表名處理器
沒錯,mybatis-plus提供了動態(tài)表名處理器接口TableNameHandler,只需要在系統(tǒng)中實現(xiàn)該接口,并作為插件加載到mybatis-plus中就可以使用,下面來看下詳細(xì)的步驟。
3.4版本之前的動態(tài)表名接口是ITableNameHandler,需要和分頁插件配合使用。
3.4版本新增了TableNameHandler,在方法參數(shù)上取消了MetaObject。這里用最新的版本為例,使用方式差別不大。
假設(shè)我們的系統(tǒng)中有兩種分表方式,按日期分表和按id取模分表。通過四個步驟來看下具體的使用示例。
1.創(chuàng)建日期表名處理器
先來看下日期處理的表名處理器,實現(xiàn)TableNameHandler接口后,在dynamicTableName方法中實現(xiàn)動態(tài)生成表名的邏輯,方法的返回值就是查詢時要使用的表名。
/** * 按天分表解析 */ public class DaysTableNameParser implements TableNameHandler { @Override public String dynamicTableName(String sql, String tableName) { String dateDay = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); return tableName + "_" + dateDay; } }
2.創(chuàng)建id取模表名處理器
再來看下按id取模表名處理器的實現(xiàn),這個處理器相對日期處理就要復(fù)雜一些,主要原因為需要動態(tài)傳入用于分表的id值。
在之前的版本中可以在方法中通過解析MetaObject中帶有的sql查詢信息,獲取分表使用的值。但是這種方式比較復(fù)雜,對于不同的QueryMapper分析的方式不同,比較容易出錯。新版本中的方法取消了MetaObject參數(shù),需要使用其他方式傳入。
需要注意的是,表名處理器是作為mybatis-plus的插件,在項目啟動時實例化的。這意味著,在運行過程中只有一個對象,多線程處理過程中,一個線程對參數(shù)的修改,會影響到其他線程。為了解決這個問題,可以使用ThreadLocal來定義參數(shù)。
由于現(xiàn)在的框架中大部分會使用線程池,例如springboot web項目中的tomcat。所以在每次使用后,需要手動清除本次數(shù)據(jù),防止線程復(fù)用時的影響。
具體實現(xiàn)如下:
/** * 按id取模分表處理器 */ public class IdModTableNameParser implements TableNameHandler { private Integer mod; //使用ThreadLocal防止多線程相互影響 private static ThreadLocal<Integer> id = new ThreadLocal<Integer>(); public static void setId(Integer idValue) { id.set(idValue); } IdModTableNameParser(Integer modValue) { mod = modValue; } @Override public String dynamicTableName(String sql, String tableName) { Integer idValue = id.get(); if (idValue == null) { throw new RuntimeException("請設(shè)置id值"); } else { String suffix = String.valueOf(idValue % mod); //這里清除ThreadLocal的值,防止線程復(fù)用出現(xiàn)問題 id.set(null); return tableName + "_" + suffix; } } }
3.加載表名處理器
表名處理器實際是mybatis-plus的插件,需要在初始化時創(chuàng)建實例并加載。因為系統(tǒng)中存在兩種分表類型,在初始化時可以指定每張表使用的表名處理器。具體實現(xiàn)如下:
@Configuration @MapperScan(basePackages = "com.yourcom.proname.repository.mapper.mainDb*", sqlSessionFactoryRef = "mainSqlSessionFactory") public class MainDb { @Bean(name = "mainDataSource") @ConfigurationProperties(prefix = "dbconfig.maindb") public DataSource druidDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "mainTransactionManager") public DataSourceTransactionManager masterTransactionManager(@Qualifier(value = "mainDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "mainSqlSessionFactory") @ConfigurationPropertiesBinding() public SqlSessionFactory sqlSessionFactory(@Qualifier(value = "mainDataSource") DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); //加載插件 factoryBean.setPlugins(mybatisPlusInterceptor()); return factoryBean.getObject(); } @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor(); HashMap<String, TableNameHandler> map = new HashMap<String, TableNameHandler>(); //這里為不同的表設(shè)置對應(yīng)表名處理器 map.put("user_daily_record", new DaysTableNameParser()); map.put("user_consume_flow", new IdModTableNameParser(10)); dynamicTableNameInnerInterceptor.setTableNameHandlerMap(map); interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor); return interceptor; } }
4.在controller中使用
下面通過controller中的三個接口,展示下使用方式:
@RestController public class TableTestController { @Resource IUserDailyRecordService userDailyRecordService; @Resource IUserConsumeFlowService userConsumeFlowService; @GetMapping("user/record/today") public CommonResVo<UserDailyRecord> getRecordToday(Integer userId) throws Exception { //這里在查詢時,會根據(jù)系統(tǒng)當(dāng)前時間,自動生成當(dāng)天的表名 UserDailyRecord userDailyRecord = userDailyRecordService.getOne(new LambdaQueryWrapper<UserDailyRecord>().eq(UserDailyRecord::getUserId, userId)); return CommonResVo.success(userDailyRecord); } @GetMapping("user/consume/flow") public CommonResVo<List<UserConsumeFlow>> getConsumeFlow(Integer userId) throws Exception { //設(shè)置用于分表的id值 IdModTableNameParser.setId(userId); List<UserConsumeFlow> userConsumeFlowList = userConsumeFlowService.list(new LambdaQueryWrapper<UserConsumeFlow>().eq(UserConsumeFlow::getUserId, userId)); return CommonResVo.success(userConsumeFlowList); } /** * 新增數(shù)據(jù) */ @PostMapping("user/consume/flow") public CommonResVo<Boolean> addConsumeFlow(@RequestBody UserConsumeFlow userConsumeFlow) throws Exception { Integer userId = userConsumeFlow.getUserId(); //設(shè)置用于分表的id值 IdModTableNameParser.setId(userId); userConsumeFlowService.save(userConsumeFlow); return CommonResVo.success(true); } }
這篇對mybatis-plus動態(tài)表名處理器的介紹,通過實現(xiàn)TableNameHandler接口,可以按實際情況靈活定義表名的生成規(guī)則,希望對大家有幫助。
項目完整示例地址:https://gitee.com/dothetrick/web-demo/tree/tabel-shading
到此這篇關(guān)于Mybatis-plus使用TableNameHandler分表詳解(附完整示例源碼)的文章就介紹到這了,更多相關(guān)Mybatis-plus TableNameHandler分表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于elasticsearch的match_phrase_prefix查詢詳解
這篇文章主要介紹了關(guān)于elasticsearch的match_phrase_prefix查詢問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03解決Springboot中@Async注解獲取不到上下文信息問題
實際開發(fā)中我們經(jīng)常需要通過spring上下文獲取一些配置信息,本文主要介紹了解決Springboot中@Async注解獲取不到上下文信息問題,具有一定的參考價值,感興趣的可以了解一下2024-01-01全面解析Spring Security 過濾器鏈的機制和特性
這篇文章主要介紹了Spring Security 過濾器鏈的機制和特性,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07JavaWeb三大組件之監(jiān)聽器Listener詳解
這篇文章主要介紹了JavaWeb三大組件之監(jiān)聽器Listener詳解,在JavaWeb應(yīng)用程序中,Listener監(jiān)聽器是一種機制,用于監(jiān)聽和響應(yīng)特定的事件,它可以感知并響應(yīng)與應(yīng)用程序相關(guān)的事件,從而執(zhí)行相應(yīng)的邏輯處理,需要的朋友可以參考下2023-10-10