在项目中,某个业务数据,每天都产生几百万条数据,所以选择对这个表按日期分表,每天的数据,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配置文件中,注册插件:
至此,分表插件开发完成,可以在需要分表的实体类中,加上自定义注解,就可以实现自动分表功能了。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)