系统运行过程中难免会遇到一些数据量很大的业务,如果简单的将他们的数据全部放置到一张表中,那么经历一段时间之后表的查询会很慢,诚然可以通过索引解决慢的问题,但是当数据量达到一个量级之后索引还是会挂掉,所以必须有一个分表插件。
接下来就是要实现如何切表名,其实网上有很多帖子很详细的描述了如何切表名,但是由于Hibernate拦截器中的onPrepareStatement只为我们提供了一个sql参数,然后我们又要干一堆很操蛋的事情,传递参数。基于易于拓展(总有一些人有些奇怪的分表需求,所以此处面向接口编程),在onPrepareStatement修改sql的过程中 其实我们干了2件事情,1.如何分表,2. 依据什么分表,所以这个里面使用到了2个接口
分表策略
分表策略指的是如何将旧表名替换成为新表名,这里面不用考虑如何获取新表名
public interface WsdHibernateSqlInterceptorStrategy extends WsdHibernateSqlConstants{ /** 处理sql */ String handle(String sql, Map<String, Object> params); }参数解析器
参数解析器的作用是构造分表策略分表时候使用的参数
public interface WsdHibernateSqlInterceptorParameterResolver extends WsdHibernateSqlConstants { /** * 解析新增/删除/修改 * * @param entity */ void resolveCud(WsdBasicEntity entity); /** * 解析查询 * <p> * <code> * WsdCriteria wci = new WsdCriteria(); * Date date = new Date(); * wci.add(WsdRestrictions.between("createdDate", WyDateUtils.firstOfMonth(date),WyDateUtils.lastOfMonth(date))); * List<HikvisionDoorEntity> pos = wsdToolDao.list(HikvisionDoorEntity.class, wci); * </code> */ <ENTITY extends WsdBasicEntity> void resolveSelect(Class<ENTITY> entityClass, WsdCriteria wci); /** * 解析查询 * * @param sr */ <ENTITY extends WsdBasicEntity> void resolveSelect(Class<ENTITY> entityClass, SearchRequest sr); /** * 对数据进行分组 * * @param args * @param <ENTITY> * @return */ <ENTITY extends WsdBasicEntity> Map<String, List<ENTITY>> groupBy(List<ENTITY> args); /** * 获取sql中需要替换的旧表名 * * @param entityClass * @param <ENTITY> * @return */ <ENTITY extends WsdBasicEntity> String getOldTableName(Class<ENTITY> entityClass); /** * 获取实体在数据库中分表的所有表名 * * @param entityClass * @return */ List<String> getAllTableNames(Class entityClass); /** * 新增分表数据到缓存中 为定时器使用 * * @param entityClassName * @param newTableName * @return */ List<String> addNewTableNamesToCache(String entityClassName, String newTableName); }现在我们应该考虑一个问题,如何在开发的过程中感知不到分表的动作,也就是进行统一入口封装。通过分析框架原有代码,我们发现框架虽然有很多方法可以访问db层,但是究其根本,底层方法就那几个,所以我们可以使用aop增强这几个方法,aop使用起来比较麻烦的地方主要有2点:
需要分析原有代码 找到合适的切点在增强该切点的时候要考虑到能否向下兼容,也就是新加的切面是否会干扰到以前的正常业务, 是否支持随意插拔 /** * * <p> 主要需要切掉下面这几个方法 但是需要注意的一点是需要使用实体名才能获取到配置信息 * @see WsdToolDao#list(java.lang.String) * @see WsdToolDao#listByNameParams(java.lang.String, java.lang.String[], java.lang.Object[]) * @see WsdToolDao#list(java.lang.String, java.lang.Object[]) * @see WsdToolDao#createBatch(List) * @see WsdToolDao#updateBatch(List) * @see WsdToolDao#removeBatch(List) * <p> * 还需要切查询方法 * @see this#aroundDoSearch * <p> * 值得注意的是wsdToolDao 中的listByIds 和getById 2个方法,这2个方法需要查询所有的表 * 因为id上没有明确的标识属于哪个表或者使用另外的方法生成id ,而不是使用通常的uuid * @see this#aroundGetById(ProceedingJoinPoint) * @see this#aroundListByIds(ProceedingJoinPoint) **/ @Aspect @Component("wsdHibernateSqlInterceptorAdvice") public class WsdHibernateSqlInterceptorAdvice { } 我们该如何让切面方法知道当前业务需要开启插件,最简单的方式是使用注解 @Documented @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface WsdSplitTable { /** * 指定分表策略和参数解析器 * @return */ WsdHibernateSqlInterceptorHolder value() default WsdHibernateSqlInterceptorHolder.General; } // 分表策略和参数解析器持有对象 public enum WsdHibernateSqlInterceptorHolder { NONE(null,null), General(new WsdHibernateGeneralStrategy(),new WsdHibernateGeneralResolver()); public WsdHibernateSqlInterceptorStrategy strategy; public WsdHibernateSqlInterceptorParameterResolver resolver; WsdHibernateSqlInterceptorHolder(WsdHibernateSqlInterceptorStrategy strategy,WsdHibernateSqlInterceptorParameterResolver resolver) { this.strategy = strategy; this.resolver = resolver; } } 分表实现的逻辑至此大致思路已经结束,但是我们还需要想另一个问题,也就是表从哪里来, 本插件中定义2张配置表,然后通过定时器去提前创建表,即可解决这个问题SYS_SPLITS_TABLE_CONFIG 指定分表创建策略的表,由定时器每天执行一次,获取下次创建时间在当天的表
SYS_SPLITS_TABLE_INFO 分表信息 里面只有2个字段configId(分表配置id) tableName(表名)
代码下载地址,只提供一个思路,具体的实现逻辑还是要基于具体的业务开发
