MyBatis自定义插件实现按日期分表功能

MyBatis自定义插件实现按日期分表功能,第1张

MyBatis自定义插件实现按日期分表功能 一、项目背景

在项目中,某个业务数据,每天都产生几百万条数据,所以选择对这个表按日期分表,每天的数据,insert进当天的表中。起初的解决方案有两种:
1.insert语句动态定义表名,进行数据的存入 *** 作。
2.使用mycat中间件进行数据负载 *** 作。
因为项目中大数据量的业务不多,只有个别的数据量大,且也还没有达到分库的体量,只是进行分表,所以使用mycat解决方案有点儿小题大做,所以最开始使用的是方案1进行的 *** 作。

二、缺点

当项目运行一段时间后,发现另一个业务数据,每天产生的数据量也很大,也需要按日期进行分表,于是,需要在这个模块的insert语句中,把表名改成动态的。所以,这种方式的缺点就是复用性很差,当再有业务数据需要分表时,还需要重新写一遍代码。

三、解决

学习了mybatis的插件原理后,发现这个问题,可以自定义mybatis插件进行解决。
思路:
首先,需要自定义一个注解,用来标识需要进行分表的实体类。然后,在mybatis执行sql的时候,根据注解标识,把有注解的类,动态修改其sql语句,改成当天日期的表名。

下面,我们来看一下具体实现:
首先,自定义注解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface PluginCS {
    String tablename();//表名前缀,在这个参数的基础上动态加当天的日期,作为当天的表名
}

接下来,就该分析,自定义插件,是要加强MyBatis的哪个组件了。因为要加强的是insert方法,所以,只考虑Executor组件和StatementHandler组件。因为只有这两个组件,涉及到了插入的方法。
Executor组件是一个总管的角色,其最终调用的是StatementHandler组件,执行的插入 *** 作,那么这俩到底加强谁呢,我们先研究明白其执行流程,再做决定。
通过debug,追其源码:
代码走到ReuseExecutor的doUpate方法,

 @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  }

重点看prepareStatement方法:

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    if (hasStatementFor(sql)) {
      stmt = getStatement(sql);
      applyTransactionTimeout(stmt);
    } else {
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection, transaction.getTimeout());
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }

可以看到,在prepareStatement,从BoundSql中获取到sql,然后传给了Statement对象。
然后看ReuseExecutor的doUpate方法的handler.update(stmt)方法:

 public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

这是PreparedStatementHandler的update实现方法,可以看到,这个方法里,就是单纯的执行JDBC的statement *** 作了,也就是说,在这里,sql已经定格了。
所以,现在的思路是,在StatementHandler的update方法里,修改Statement的sql语句。经过研究发现,Statement是一个接口,其实现类有很多,想获取到其sql熟悉,很难,放弃了这种想法。
那么只能在调用update方法之前,修改sql了。上面源码分析到,先是调用了prepareStatement方法,生成了Statement对象,然后在update方法里执行的Statement方法。所以,我们可以在prepareStatement方法中,来修改sql。通过研究发现,

stmt = handler.prepare(connection, transaction.getTimeout());

处生成了statement对象,且prepare方法也是StatementHandler的方法,所以,定位到对prepare方法进行加强。
所以,现在的思路是加强prepare方法,然后修改BoundSql中的sql,添加动态表名。代码如下:

@Component
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare",
                args = {Connection.class, Integer.class})
     })
public class MyPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql bsinstance = statementHandler.getBoundSql();
        Object param=bsinstance.getParameterObject();
        if(param==null){
            return invocation.proceed();
        }
        PluginCS annotation = param.getClass().getAnnotation(PluginCS.class);//分表自定义注解
        if(annotation!=null){//需要路由
            String tablename=annotation.tablename();
            //获取sql
         //   BoundSql boundSql = statement.getBoundSql(param);
            Field field = getField(bsinstance, "sql");
           String sql= field.get(bsinstance).toString();
           if(!sql.contains("insert")){//只对insert语句进行处理
               return invocation.proceed();
           }
           sql =sql.replace(tablename,"_cs");//模拟分表,动态添加表名
           field.set(bsinstance,sql);
        }
        return invocation.proceed();//修改完sql语句后,执行原方法
    }

    
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
private Field getField(Object o, String name) {
        Field field = ReflectionUtils.findField(o.getClass(), name);
        ReflectionUtils.makeAccessible(field);
        return field;
    }
    }

然后,进行插件的注册,在mybatis配置文件中,注册插件:


		
	

至此,分表插件开发完成,可以在需要分表的实体类中,加上自定义注解,就可以实现自动分表功能了。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5684615.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存