Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
总体概括为:
拦截执行器的方法
拦截参数的处理
拦截结果集的处理
拦截Sql语法构建的处理
这4各方法在MyBatis的一个操作(新增,删除,修改,查询)中都会被执行到,执行的先后顺序是Executor,ParameterHandler,ResultSetHandler,StatementHandler。
package org.apache.ibatis.plugin; import java.util.Properties; public interface Interceptor { //intercept方法就是要进行拦截的时候要执行的方法。 Object intercept(Invocation invocation) throws Throwable; //plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法。 Object plugin(Object target); //setProperties方法是用于在Mybatis配置文件中指定一些属性的。 void setProperties(Properties properties); }
分表的表结构已经预设完毕,所以现在我们只需要在进行增删改查的时候直接一次锁定目标表,然后替换目标sql。
对于拦截器Mybatis为我们提供了一个Interceptor接口,前面有提到,通过实现该接口就可以定义我们自己的拦截器。自定义的拦截器需要交给Mybatis管理,这样才能使得Mybatis的执行与拦截器的执行结合在一起,即,利用springboot把自定义拦截器注入。
package com.shinemo.insurance.common.config; import org.apache.ibatis.plugin.Interceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class TableShardConfig { /** * 注册插件 */ @Bean public Interceptor tableShardInterceptor() { return new TableShardInterceptor(); } }
因为拦截器是全局拦截的,我们只需要拦截我们需要拦截的mapper,故需要用注解进行标识
package com.shinemo.insurance.common.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(value = { ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface TableShard { // 表前缀名 String tableNamePrefix(); // 值 String value() default ""; // 是否是字段名,如果是需要解析请求参数改字段名的值(默认否) boolean fieldFlag() default false; }
我们只需要把这个注解标识在我们要拦截的mapper上
@Mapper @TableShard(tableNamePrefix = "t_insurance_video_people_", value = "deviceId", fieldFlag = true) public interface InsuranceVideoPeopleMapper { //VideoPeople对象中包含deviceId字段 int insert(VideoPeople videoPeople); }
package com.shinemo.insurance.common.config; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.sql.Connection; import java.util.Map; import com.shinemo.insurance.common.annotation.TableShard; import com.shinemo.insurance.common.util.HashUtil; import org.apache.ibatis.binding.MapperMethod; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.reflection.DefaultReflectorFactory; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.ReflectorFactory; import org.apache.ibatis.reflection.SystemMetaObject; @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) }) public class TableShardInterceptor implements Interceptor { private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory(); @Override public Object intercept(Invocation invocation) throws Throwable { // MetaObject是mybatis里面提供的一个工具类,类似反射的效果 MetaObject metaObject = getMetaObject(invocation); BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql"); MappedStatement mappedStatement = (MappedStatement) metaObject .getValue("delegate.mappedStatement"); // 获取Mapper执行方法 Method method = invocation.getMethod(); // 获取分表注解 TableShard tableShard = getTableShard(method, mappedStatement); // 如果method与class都没有TableShard注解或执行方法不存在,执行下一个插件逻辑 if (tableShard == null) { return invocation.proceed(); } //获取值,此值就是拿的注解上value值,注解上value设定的值,并在传入对象中获取,根据业务可以选择适当的值即可,我选取此值的目的是同一台设备的值存入一张表中,有hash冲突的值也存在一张表中 String value = tableShard.value(); //value是否字段名,如果是,需要解析请求参数字段名的值 boolean fieldFlag = tableShard.fieldFlag(); if (fieldFlag) { //获取请求参数 Object parameterObject = boundSql.getParameterObject(); if (parameterObject instanceof MapperMethod.ParamMap) { // ParamMap类型逻辑处理 MapperMethod.ParamMap parameterMap = (MapperMethod.ParamMap) parameterObject; // 根据字段名获取参数值 Object valueObject = parameterMap.get(value); if (valueObject == null) { throw new RuntimeException(String.format("入参字段%s无匹配", value)); } //替换sql replaceSql(tableShard, valueObject, metaObject, boundSql); } else { // 单参数逻辑 //如果是基础类型抛出异常 if (isBaseType(parameterObject)) { throw new RuntimeException("单参数非法,请使用@Param注解"); } if (parameterObject instanceof Map) { Map<String, Object> parameterMap = (Map<String, Object>) parameterObject; Object valueObject = parameterMap.get(value); //替换sql replaceSql(tableShard, valueObject, metaObject, boundSql); } else { //非基础类型对象 Class<?> parameterObjectClass = parameterObject.getClass(); Field declaredField = parameterObjectClass.getDeclaredField(value); declaredField.setAccessible(true); Object valueObject = declaredField.get(parameterObject); //替换sql replaceSql(tableShard, valueObject, metaObject, boundSql); } } } else {//无需处理parameterField //替换sql replaceSql(tableShard, value, metaObject, boundSql); } //把原有的简单查询语句替换为分表查询语句了,现在是时候将程序的控制权交还给Mybatis下一个拦截器处理 return invocation.proceed(); } /** * @description: * @param target * @return: Object */ @Override public Object plugin(Object target) { // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身, 减少目标被代理的次数 if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; } } /** * @description: 基本数据类型验证,true是,false否 * @param object * @return: boolean */ private boolean isBaseType(Object object) { if (object.getClass().isPrimitive() || object instanceof String || object instanceof Integer || object instanceof Double || object instanceof Float || object instanceof Long || object instanceof Boolean || object instanceof Byte || object instanceof Short) { return true; } else { return false; } } /** * @description: 替换sql * @param tableShard 分表注解 * @param value 值 * @param metaObject mybatis反射对象 * @param boundSql sql信息对象 * @return: void */ private void replaceSql(TableShard tableShard, Object value, MetaObject metaObject, BoundSql boundSql) { String tableNamePrefix = tableShard.tableNamePrefix(); // // 获取策略class // Class<? extends ITableShardStrategy> strategyClazz = tableShard.shardStrategy(); // // 从spring ioc容器获取策略类 // ITableShardStrategy tableShardStrategy = SpringBeanUtil.getBean(strategyClazz); // 生成分表名 String shardTableName = generateTableName(tableNamePrefix, (String) value); // 获取sql String sql = boundSql.getSql(); // 完成表名替换 metaObject.setValue("delegate.boundSql.sql", sql.replaceAll(tableNamePrefix, shardTableName)); } /** * 生成表名 * * @param tableNamePrefix 表名前缀 * @param value 价值 * @return {@link String} */ private String generateTableName(String tableNamePrefix, String value) { //我们分了1024张表 int prime = 1024; //hash取模运算过后,锁定目标表 int rotatingHash = HashUtil.rotatingHash(value, prime); return tableNamePrefix + rotatingHash; } /** * @description: 获取MetaObject对象-mybatis里面提供的一个工具类,类似反射的效果 * @param invocation * @return: MetaObject */ private MetaObject getMetaObject(Invocation invocation) { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); // MetaObject是mybatis里面提供的一个工具类,类似反射的效果 MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY); return metaObject; } /** * @description: 获取分表注解 * @param method * @param mappedStatement * @return: TableShard */ private TableShard getTableShard(Method method, MappedStatement mappedStatement) throws ClassNotFoundException { String id = mappedStatement.getId(); // 获取Class final String className = id.substring(0, id.lastIndexOf(".")); // 分表注解 TableShard tableShard = null; // 获取Mapper执行方法的TableShard注解 tableShard = method.getAnnotation(TableShard.class); // 如果方法没有设置注解,从Mapper接口上面获取TableShard注解 if (tableShard == null) { // 获取TableShard注解 tableShard = Class.forName(className).getAnnotation(TableShard.class); } return tableShard; } }
以上是怎么使用springboot+mybatis拦截器实现水平分表的详细内容。更多信息请关注PHP中文网其他相关文章!