按日期分表

这种方式通常会在表名的最后加上年月日,主要适用于按日期划分的统计数据或操作记录。在线实时展示的只有最近表中的数据,其他数据用于离线统计等。

/**
 * 按天分表解析
 */
public class DaysTableNameParser implements TableNameHandler {
 
  @Override
  public String dynamicTableName(String sql, String tableName) {
    String dateDay = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
    return tableName + "_" + dateDay;
  }
}

按id取模分表 


这种方式需要一个id生成器,例如snowflake id或分布式id服务。它保证了相同id的数据都在一张表中,主要适用于保存用户基础信息,系统中的资源信息,购买记录等。当然这种分表方式扩展性较差,后期数据持续增多后需要按id大小分库再分表处理。

/**
 * 按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;
    }
  }
}

自定义分表

业务场景:

当前业务上需要把分表的表名存储再数据库中,既不使用时间分表,也不使用id取模方式。查询时都需要知道查的那种表。

@Configuration
public class MybatisPlusConfig {

    /**
     * 借助 ThreadLocal 讲数据库查询的表名存储起来
     */
    private static ThreadLocal<String> myTableName = new ThreadLocal<>();

    public static String getThreadLocal() {
        return myTableName.get();
    }

    public static void setThreadLocal(String str) {
        myTableName.set(str);
    }

    public static void removeThreadLocal() {
        myTableName.remove();
    }

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 取消MyBatis Plus的最大分页500条的限制
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setMaxLimit(100000L);

        DynamicTableNameInnerInterceptor myDynamicTableNameInterceptor = new DynamicTableNameInnerInterceptor();
        Map<String, TableNameHandler> tableNameHandlerMap = Maps.newHashMapWithExpectedSize(1);
        // 从 ThreadLocal 中取出设置进来的表名,设置到map中,其中key是基本表
        tableNameHandlerMap.put("user_", (sql, table) -> getThreadLocal());
        myDynamicTableNameInterceptor.setTableNameHandlerMap(tableNameHandlerMap);
        interceptor.addInnerInterceptor(myDynamicTableNameInterceptor);
        return interceptor;
    }
}

自定义拦截器逻辑:这里使用拦截器的作用是当代码中使用了threadLocal后,需要及时remove调threadLocal,不然可能导致内存泄漏。参考:ThreadLocal的内存泄露?什么原因?如何避免? - 知乎

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 新增自定义拦截件器用于方法结束删除threadLocal中值
        registry.addInterceptor(new MyInterception()).addPathPatterns("/**");
    }
}

public class MyInterception implements HandlerInterceptor{

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        MybatisPlusConfig.removeThreadLocal();
    }
}

通过查询数据库查询具体需要分表的表名并设置到threadLocal中

MybatisPlusConfig.setThreadLocal(tableName);

到此,可以任意使用mybatisPlus插件进行数据查询和修改,也可以自定义sql进行查询。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐