Mybatis插件(plugins)
Mybatis允许在已映射的语句执行过程中某一点进行拦截。Mybatis允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
分别拦截以下方法调用:
- 拦截执行器的方法
- 拦截参数的处理
- 拦截结果集的处理
- 拦截Sql语法构建的处理
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Invocation; import java.util.Properties;
@Intercepts({@Signature( type= Executor.class,method = "update",args = {MappedStatement.class,Object.class}) }) public class ExamplePlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { return null; }
@Override public Object plugin(Object o) { return null; }
@Override public void setProperties(Properties properties) {
} }
|
上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行低层映射语句的内部对象。
水平分表实现
自定义插件
1 2 3 4 5 6
| <!-- mybatis-config.xml --> <plugins> <plugin interceptor="org.mybatis.example.ExamplePlugin"> <property name="someProperty" value="100"/> </plugin> </plugins>
|
SpringBoot中只需要使用@Component注解注册为bean即可
选择拦截方法
实现分表主要是通过在sql构建时,对表名进行替换,所以选择拦截StatementHandler
注解为:
1 2 3
| @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) })
|
定义分区表、分表字段
使用Mybatis实现分表我们期望分表灵活,即可以选择要分区的表,分区表分表字段,甚至指定哪些方法需要分表
自定义注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) public @interface TablesPartition {
boolean split() default true;
TablePartition[] value();
}
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface TablePartition {
String value() default "";
String field() default ""; }
|
在Mapper接口/方法上定义注解
1 2 3 4 5 6 7 8 9 10 11
| @TablesPartition({ @TablePartition(value = "table_0", field = "field_0"), @TablePartition(value = "table_1", field = "field_0") }) public interface NormalMapper {
@TablesPartition({ @TablePartition(value = "table_0", field = "field_0") }) List<NormalEntity> selectAll(); }
|
分表核心源码实现
源码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
| @Slf4j @Component @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) }) public class PartitionInterceptor implements Interceptor {
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory(); private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory(); private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
@Override public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,REFLECTOR_FACTORY);
Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject"); partitionTable(metaStatementHandler,parameterObject);
return invocation.proceed(); }
@Override public Object plugin(Object target) {
if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; } }
@Override public void setProperties(Properties properties) {
}
private void partitionTable(MetaObject metaStatementHandler, Object param ) throws Exception {
String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
if (StringUtils.isNotBlank(originalSql)) {
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); String id = mappedStatement.getId(); String className = id.substring(0, id.lastIndexOf(".")); String methodName = id.substring(id.lastIndexOf(".") + 1); Class<?> clazz = Class.forName(className); Method method = findMethod(clazz.getDeclaredMethods(), methodName);
TablesPartition tablesPartition = null;
if (method != null) { tablesPartition = method.getAnnotation(TablesPartition.class); }
if (tablesPartition == null) { tablesPartition = clazz.getAnnotation(TablesPartition.class); }
if (tablesPartition != null && tablesPartition.split()) {
TablePartition[] tablePartitionList = tablesPartition.value(); String convertedSql = originalSql;
for (TablePartition tablePartition:tablePartitionList) {
StringBuilder stringBuilder = new StringBuilder(tablePartition.value());
String resort = ""; if (param instanceof Map) { resort = (String)((Map) param).get(tablePartition.field()); } else if (param instanceof String) { resort = (String)param; }
if (!StringUtils.isEmpty(resort)) { stringBuilder.append("_"); stringBuilder.append(resort); }
convertedSql = convertedSql.replaceAll("(?i)" + tablePartition.value()+"_", "thisIsSpecialColumn");
convertedSql = convertedSql.replaceAll("(?i)" + tablePartition.value(), stringBuilder.toString());
convertedSql = convertedSql.replaceAll("thisIsSpecialColumn", tablePartition.value()+"_");
}
log.debug("分表后的SQL:\n" + convertedSql);
metaStatementHandler.setValue("delegate.boundSql.sql", convertedSql);
} } }
private Method findMethod(Method[] methods, String methodName) {
for (Method method : methods) { if (method.getName().equals(methodName)) { return method; } }
return null; } }
|