温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么使用springboot+mybatis拦截器实现水平分表

发布时间:2022-08-10 16:27:30 来源:亿速云 阅读:237 作者:iii 栏目:开发技术

这篇文章主要介绍了怎么使用springboot+mybatis拦截器实现水平分表的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么使用springboot+mybatis拦截器实现水平分表文章都会有所收获,下面我们一起来看看吧。

MyBatis 允许使用插件来拦截的方法

  • 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。

怎么使用springboot+mybatis拦截器实现水平分表

Interceptor接口

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); }

分表实现

1、大体思路

分表的表结构已经预设完毕,所以现在我们只需要在进行增删改查的时候直接一次锁定目标表,然后替换目标sql。

2、逐步实现

Mybatis如何找到我们新增的拦截服务

对于拦截器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拦截器实现水平分表”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“怎么使用springboot+mybatis拦截器实现水平分表”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI