• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

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

武飞扬头像
敲代码的小小酥
帮助1

一、项目背景

在项目中,某个业务数据,每天都产生几百万条数据,所以选择对这个表按日期分表,每天的数据,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配置文件中,注册插件:

<plugins>
		<plugin interceptor="xxx.xxx.plugin.MyPlugin"/>
	</plugins>

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

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhghfiec
系列文章
更多 icon
同类精品
更多 icon
继续加载