溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何在Mybatis-plus中利用TableNameHandler實(shí)現(xiàn)一個(gè)分表功能

發(fā)布時(shí)間:2021-01-26 14:21:23 來(lái)源:億速云 閱讀:2892 作者:Leah 欄目:開(kāi)發(fā)技術(shù)

本篇文章給大家分享的是有關(guān)如何在Mybatis-plus中利用TableNameHandler實(shí)現(xiàn)一個(gè)分表功能,小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說(shuō),跟著小編一起來(lái)看看吧。

為什么要分表

Mysql是當(dāng)前互聯(lián)網(wǎng)系統(tǒng)中使用非常廣泛的關(guān)系數(shù)據(jù)庫(kù),具有ACID的特性。

但是mysql的單表性能會(huì)受到表中數(shù)據(jù)量的限制,主要原因是B+樹(shù)索引過(guò)大導(dǎo)致查詢時(shí)索引無(wú)法全部加載到內(nèi)存。讀取磁盤(pán)的次數(shù)變多,而磁盤(pán)的每次讀取對(duì)性能都有很大的影響。

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

兩種基礎(chǔ)分表邏輯

說(shuō)完了為什么要分表,下面聊聊業(yè)務(wù)開(kāi)發(fā)中常見(jiàn)的兩種基礎(chǔ)的分表邏輯。

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

按id取模分表
這種方式需要一個(gè)id生成器,例如snowflake id或分布式id服務(wù)。它保證了相同id的數(shù)據(jù)都在一張表中,主要適用于保存用戶基礎(chǔ)信息,系統(tǒng)中的資源信息,購(gòu)買(mǎi)記錄等。當(dāng)然這種分表方式擴(kuò)展性較差,后期數(shù)據(jù)持續(xù)增多后需要按id大小分庫(kù)再分表處理。

下面看下這兩種分表邏輯在mybatis-plus中的實(shí)現(xiàn)。

Mybatis-plus中的分表實(shí)現(xiàn)

說(shuō)到j(luò)ava的分表中間件,可能有人會(huì)想到sharding-jdbc,作為使用很廣泛的一個(gè)分表中間件,功能也比較完善,但是使用它需要引入額外的jar包和增加學(xué)習(xí)成本。

實(shí)際上mybatis-plus本身就提供了一個(gè)分表的解決方案,配置使用都很簡(jiǎn)單,適合快速開(kāi)發(fā)系統(tǒng)。

動(dòng)態(tài)表名處理器

沒(méi)錯(cuò),mybatis-plus提供了動(dòng)態(tài)表名處理器接口TableNameHandler,只需要在系統(tǒng)中實(shí)現(xiàn)該接口,并作為插件加載到mybatis-plus中就可以使用,下面來(lái)看下詳細(xì)的步驟。

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

假設(shè)我們的系統(tǒng)中有兩種分表方式,按日期分表和按id取模分表。通過(guò)四個(gè)步驟來(lái)看下具體的使用示例。

1.創(chuàng)建日期表名處理器

先來(lái)看下日期處理的表名處理器,實(shí)現(xiàn)TableNameHandler接口后,在dynamicTableName方法中實(shí)現(xiàn)動(dòng)態(tài)生成表名的邏輯,方法的返回值就是查詢時(shí)要使用的表名。

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

再來(lái)看下按id取模表名處理器的實(shí)現(xiàn),這個(gè)處理器相對(duì)日期處理就要復(fù)雜一些,主要原因?yàn)樾枰獎(jiǎng)討B(tài)傳入用于分表的id值。

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

需要注意的是,表名處理器是作為mybatis-plus的插件,在項(xiàng)目啟動(dòng)時(shí)實(shí)例化的。這意味著,在運(yùn)行過(guò)程中只有一個(gè)對(duì)象,多線程處理過(guò)程中,一個(gè)線程對(duì)參數(shù)的修改,會(huì)影響到其他線程。為了解決這個(gè)問(wèn)題,可以使用ThreadLocal來(lái)定義參數(shù)。

由于現(xiàn)在的框架中大部分會(huì)使用線程池,例如springboot web項(xiàng)目中的tomcat。所以在每次使用后,需要手動(dòng)清除本次數(shù)據(jù),防止線程復(fù)用時(shí)的影響。

具體實(shí)現(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("請(qǐng)?jiān)O(shè)置id值");
    } else {
      String suffix = String.valueOf(idValue % mod);
      //這里清除ThreadLocal的值,防止線程復(fù)用出現(xiàn)問(wèn)題
      id.set(null);
      return tableName + "_" + suffix;
    }
  }
}

3.加載表名處理器

表名處理器實(shí)際是mybatis-plus的插件,需要在初始化時(shí)創(chuàng)建實(shí)例并加載。因?yàn)橄到y(tǒng)中存在兩種分表類型,在初始化時(shí)可以指定每張表使用的表名處理器。具體實(shí)現(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è)置對(duì)應(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中使用

下面通過(guò)controller中的三個(gè)接口,展示下使用方式:

@RestController
public class TableTestController {
  @Resource
  IUserDailyRecordService userDailyRecordService;

  @Resource
  IUserConsumeFlowService userConsumeFlowService;

  @GetMapping("user/record/today")
  public CommonResVo<UserDailyRecord> getRecordToday(Integer userId) throws Exception {
    //這里在查詢時(shí),會(huì)根據(jù)系統(tǒng)當(dāng)前時(shí)間,自動(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中利用TableNameHandler實(shí)現(xiàn)一個(gè)分表功能,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI