MyBatis自定义插件实现按日期分表功能
Posted 敲代码的小小酥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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配置文件中,注册插件:
<plugins>
<plugin interceptor="xxx.xxx.plugin.MyPlugin"/>
</plugins>
至此,分表插件开发完成,可以在需要分表的实体类中,加上自定义注解,就可以实现自动分表功能了。
以上是关于MyBatis自定义插件实现按日期分表功能的主要内容,如果未能解决你的问题,请参考以下文章
mybatis-plus小技能: 分表策略(按年分表和按月分表)
springboot~mybatis-plus的DynamicTableNameInnerInterceptor实现分表
Mybatis -- MyBatis核心配置文件深入: typeHandlers标签(自定义类型转换器)plugins标签(插件标签:扩展mybatis功能 分页助手)