溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何在Mybatis-plus中利用TableNameHandler實現一個分表功能

發布時間:2021-01-26 14:21:23 來源:億速云 閱讀:2946 作者:Leah 欄目:開發技術

本篇文章給大家分享的是有關如何在Mybatis-plus中利用TableNameHandler實現一個分表功能,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

為什么要分表

Mysql是當前互聯網系統中使用非常廣泛的關系數據庫,具有ACID的特性。

但是mysql的單表性能會受到表中數據量的限制,主要原因是B+樹索引過大導致查詢時索引無法全部加載到內存。讀取磁盤的次數變多,而磁盤的每次讀取對性能都有很大的影響。

這時一個簡單可行的方案就是分表(當然土豪也可以堆硬件),將一張數據量龐大的表的數據,拆分到多個表中,這同時也減少了B+樹索引的大小,減少磁盤讀取次數,提高性能。

兩種基礎分表邏輯

說完了為什么要分表,下面聊聊業務開發中常見的兩種基礎的分表邏輯。

按日期分表
這種方式通常會在表名的最后加上年月日,主要適用于按日期劃分的統計數據或操作記錄。在線實時展示的只有最近表中的數據,其他數據用于離線統計等。

按id取模分表
這種方式需要一個id生成器,例如snowflake id或分布式id服務。它保證了相同id的數據都在一張表中,主要適用于保存用戶基礎信息,系統中的資源信息,購買記錄等。當然這種分表方式擴展性較差,后期數據持續增多后需要按id大小分庫再分表處理。

下面看下這兩種分表邏輯在mybatis-plus中的實現。

Mybatis-plus中的分表實現

說到java的分表中間件,可能有人會想到sharding-jdbc,作為使用很廣泛的一個分表中間件,功能也比較完善,但是使用它需要引入額外的jar包和增加學習成本。

實際上mybatis-plus本身就提供了一個分表的解決方案,配置使用都很簡單,適合快速開發系統。

動態表名處理器

沒錯,mybatis-plus提供了動態表名處理器接口TableNameHandler,只需要在系統中實現該接口,并作為插件加載到mybatis-plus中就可以使用,下面來看下詳細的步驟。

3.4版本之前的動態表名接口是ITableNameHandler,需要和分頁插件配合使用。
3.4版本新增了TableNameHandler,在方法參數上取消了MetaObject。這里用最新的版本為例,使用方式差別不大。

假設我們的系統中有兩種分表方式,按日期分表和按id取模分表。通過四個步驟來看下具體的使用示例。

1.創建日期表名處理器

先來看下日期處理的表名處理器,實現TableNameHandler接口后,在dynamicTableName方法中實現動態生成表名的邏輯,方法的返回值就是查詢時要使用的表名。

/**
 * 按天分表解析
 */
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.創建id取模表名處理器

再來看下按id取模表名處理器的實現,這個處理器相對日期處理就要復雜一些,主要原因為需要動態傳入用于分表的id值。

在之前的版本中可以在方法中通過解析MetaObject中帶有的sql查詢信息,獲取分表使用的值。但是這種方式比較復雜,對于不同的QueryMapper分析的方式不同,比較容易出錯。新版本中的方法取消了MetaObject參數,需要使用其他方式傳入。

需要注意的是,表名處理器是作為mybatis-plus的插件,在項目啟動時實例化的。這意味著,在運行過程中只有一個對象,多線程處理過程中,一個線程對參數的修改,會影響到其他線程。為了解決這個問題,可以使用ThreadLocal來定義參數。

由于現在的框架中大部分會使用線程池,例如springboot web項目中的tomcat。所以在每次使用后,需要手動清除本次數據,防止線程復用時的影響。

具體實現如下:

/**
 * 按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("請設置id值");
    } else {
      String suffix = String.valueOf(idValue % mod);
      //這里清除ThreadLocal的值,防止線程復用出現問題
      id.set(null);
      return tableName + "_" + suffix;
    }
  }
}

3.加載表名處理器

表名處理器實際是mybatis-plus的插件,需要在初始化時創建實例并加載。因為系統中存在兩種分表類型,在初始化時可以指定每張表使用的表名處理器。具體實現如下:

@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>();

    //這里為不同的表設置對應表名處理器
    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 {
    //這里在查詢時,會根據系統當前時間,自動生成當天的表名
    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 {
    //設置用于分表的id值
    IdModTableNameParser.setId(userId);
    List<UserConsumeFlow> userConsumeFlowList = userConsumeFlowService.list(new LambdaQueryWrapper<UserConsumeFlow>().eq(UserConsumeFlow::getUserId, userId));
    return CommonResVo.success(userConsumeFlowList);
  }

  /**
   * 新增數據
   */
  @PostMapping("user/consume/flow")
  public CommonResVo<Boolean> addConsumeFlow(@RequestBody UserConsumeFlow userConsumeFlow) throws Exception {
    Integer userId = userConsumeFlow.getUserId();
    //設置用于分表的id值
    IdModTableNameParser.setId(userId);
    userConsumeFlowService.save(userConsumeFlow);
    return CommonResVo.success(true);
  }
}

以上就是如何在Mybatis-plus中利用TableNameHandler實現一個分表功能,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女