数据库是 mysql,其中的浏览记录表数据量越来越大,导致按时间范围查询的时候非常慢,打算做按月分表,前端限制一次只能查询一个月内的数据,网上的例子很多,都是创建分表注解,标注了分表注解的 MapperDao 执行分表操作,但是我的项目很老,是直接用 xml 映射文件中的 namespace.id 来查询数据的,所以改用了 plugin 标签中的 property 来配置要按月分表的表名,且只提供了按月分表这一种策略。
Mybatis 的 plugin 是 Mybatis 的一个扩展点,它可以拦截一下接口:
- Executor(update、query、flushStatements、commit、rollback、getTransaction、close、isClosed)
- ParameterHandler(getParameterObject、setParameters)
- ResultSetHandler(handleResultSets、handleOutputParameters)
- StatementHandler(prepare、parameterize、batch、update、query)
而分表主要是修改需要分表的表名,而按月分表一般是在表名后边添加 _YYYYMM 的后缀,所以拦截 StatementHandler 接口中的 prepare 方法即可。
首先是 mybatis-config.xml 文件配置插件,假设要拦截的表名是 person,如果有多个以逗号分隔。
<plugins>
<plugin interceptor="com.niuma.config.interceptors.SimpleInterceptor">
<property name="tables" value="person"/>
</plugin>
</plugins>
然后是拦截类,里面还有一个知识点是用到了 MetaObject,它是 Mybatis 提供的一个用来操作 Java 对象属性的工具类,可以用它来获取和设置属性值,因为我们拦截的是 StatementHandler,所以从 invocation 入参中获取 StatementHandler 实例,StatementHandler 中有一个 delegate 属性值,它也是 StatementHandler 接口的实现类,然后 delegate 中有一个 BoundSql 对象,它封装了要执行的 sql 语句,BoundSql 里面的 sql 就是要执行的 sql,但是 delegate 和 sql 属性并没有 getter 和 setter 方法,所以我们需要通过反射来获取 sql 的值,如果我们自己写反射来获取 sql 的话,代码类似于下面这样:
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
Class<? extends StatementHandler> statementHandlerClass = statementHandler.getClass();
Field declaredField = statementHandlerClass.getDeclaredField("delegate");
declaredField.setAccessible(true);
StatementHandler delegate = (StatementHandler) declaredField.get(statementHandler);
Class<?> baseStatementClass = delegate.getClass().getSuperclass();
Field boundSqlField = baseStatementClass.getDeclaredField("boundSql");
boundSqlField.setAccessible(true);
BoundSql boundSql = (BoundSql) boundSqlField.get(delegate);
Class<? extends BoundSql> boundSqlClass = boundSql.getClass();
Field sqlField = boundSqlClass.getDeclaredField("sql");
sqlField.setAccessible(true);
String sql = (String) sqlField.get(boundSql);
System.out.println("sql: " + sql);
但是 MetaObject 类帮我们封装了这些样板代码,如下所示:
@Intercepts({
@Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class, Integer.class})
})
public class SimpleInterceptor implements Interceptor {
private static final Logger log = LoggerFactory.getLogger(SimpleInterceptor.class);
private static final String BOUNDSQL = "delegate.boundSql";
private static final String BOUNDSQL_SQL = "delegate.boundSql.sql";
private String[] oldTableNames = new String[0];
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
String oldSql = (String) metaStatementHandler.getValue(BOUNDSQL_SQL);
log.info("oldSql: " + oldSql);
String newSql = oldSql;
// 需要分表
if (oldTableNames.length != 0) {
String yyyymm = LocalDate.now().format(DateTimeFormatter.ofPattern("YYYYMM"));
for (int i = 0; i < oldTableNames.length ; i ++) {
String oldTableName = oldTableNames[i];
if (oldSql.contains(oldTableName)) {
String newTableName = oldTableName + "_" + yyyymm;
newSql = newSql.replace(oldTableName, newTableName);
}
}
}
log.info("newSql: " + newSql);
metaStatementHandler.setValue(BOUNDSQL_SQL, newSql);
// 传递给下一个拦截器处理
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
String tablesStr = properties.getProperty("tables");
String[] split = tablesStr.split(",");
oldTableNames = split;
}
}
参考: blog.csdn.net/cckevincyh/… mybatis.org/mybatis-3/z… www.cnblogs.com/selinamee/p… www.jianshu.com/p/d576cf56b… juejin.cn/post/684490…