玩转SpringBoot之整合Mybatis拦截器对数据库水平分表

利用Mybatis拦截器对数据库水平分表

需求描述

当数据量比较多时,放在一个表中的时候会影响查询效率;或者数据的时效性只是当月有效的时候;这时我们就会涉及到数据库的分表操作了。当然,你也可以使用比较完善的第三方组件:sharding-jdbc来实现;但是你使用后会发现,貌似对oracle的兼容性不是很好。所以最后我还是决定使用Mybatis拦截器对数据库进行水平分表。

为什么要选用Mybatis拦截器

  • 拦截器:我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。
  • Mybatis拦截器:就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。

    MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。对于拦截器Mybatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。

    MyBatis默认调用四种类型的方法:

  • 1.Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • 2.ParameterHandler (getParameterObject, setParameters)
  • 3.ResultSetHandler (handleResultSets, handleOutputParameters)
  • 4.StatementHandler (prepare, parameterize, batch, update, query)

以上4个都是Configuration的方法,这些方法在MyBatis的一个操作(新增,删除,修改,查询)中都会被执行到,执行的先后顺序是Executor,ParameterHandler,ResultSetHandler,StatementHandler。

开始构建项目

0、导入依赖包

<dependencies>
    <!-- SpringBoot启动依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.0.4.RELEASE</version>
    </dependency>
    <!--添加Web依赖:有前后端交互就需要用到,即有controller中的请求 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion><!-- 采用的SLf4J 去除冲突 -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- 添加日志框架依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    <!--添加MySql依赖 -->
    <!--
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.42</version>
    </dependency>
    -->

    <!-- 添加Oracle依赖 -->
    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc6</artifactId>
        <version>11.2.0.3</version>
    </dependency>
    <!-- 添加druid依赖: 一个用来连接数据库的链接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <!--添加Mybatis依赖 配置mybatis的一些初始化的东西-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
    </dependency>
    <!-- 添加lombok依赖 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.codehaus.plexus</groupId>
        <artifactId>plexus-classworlds</artifactId>
        <version>2.5.1</version>
    </dependency>
    <!--生成代码插件-->
    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.3.2</version>
        <type>jar</type>
    </dependency>

</dependencies>

1、自定义分表规则和分表策略拦截注解类

package com.java.mmzsit.framework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 分表规则
 * @author mmzsit
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface TableSplitRule {

    public String tableName();

    //暂时只支持单参数
    public String paramName();

    public String targetName();
}
package com.java.mmzsit.framework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 分表策略拦截
 * @author tianwei
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface TableSplitTarget {

    boolean interFale() default true;
    //分表规则
    public TableSplitRule[] rules();
}

2、实现策略分表拦截器

package com.java.mmzsit.framework.interceptor;

import com.java.mmzsit.framework.annotation.TableSplitRule;
import com.java.mmzsit.framework.annotation.TableSplitTarget;
import com.java.mmzsit.framework.mybatisStrategy.strategy.Strategy;
import com.java.mmzsit.framework.mybatisStrategy.StrategyManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Map;
import java.util.Properties;

/**
 * @author :mmzsit
 * @description:
 * @date :2019/6/14 10:10
 */
@Slf4j(topic="策略分表拦截器【TableSplitInterceptor】")

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class,Integer.class }) })
public class TableSplitInterceptor implements Interceptor {

    @Autowired
    StrategyManager strategyManager;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("进入mybatisSql拦截器:====================");
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler =
                MetaObject.forObject(statementHandler,SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
        Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject");
        doSplitTable(metaStatementHandler,parameterObject);
        // 传递给下一个拦截器处理
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object arg0) {
        //System.err.println(arg0.getClass());
        if (arg0 instanceof StatementHandler) {
            return Plugin.wrap(arg0, this);
        } else {
            return arg0;
        }
    }

    @Override
    public void setProperties(Properties arg0) {

    }

    private void doSplitTable(MetaObject metaStatementHandler,Object param) throws ClassNotFoundException{
        String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        if (originalSql != null && !originalSql.equals("")) {
            log.info("分表前的SQL:"+originalSql);
            MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
            String id = mappedStatement.getId();
            String className = id.substring(0, id.lastIndexOf("."));
            Class<?> classObj = Class.forName(className);
            // 根据配置自动生成分表SQL
            TableSplitTarget tableSplit = classObj.getAnnotation(TableSplitTarget.class);
            if(tableSplit==null||!tableSplit.interFale()) {
                return ;
            }
            TableSplitRule[] rules = tableSplit.rules();
            if (rules != null && rules.length>0) {

                String convertedSql= null;
                // StrategyManager可以使用ContextHelper策略帮助类获取,本次使用注入
                for(TableSplitRule rule : rules) {
                    Strategy strategy = null;

                    if(rule.targetName()!=null&&!rule.targetName().isEmpty()) {
                        strategy = strategyManager.getStrategy(rule.targetName());
                    }
                    if(!rule.paramName().isEmpty()&&!rule.tableName().isEmpty()) {

                        String paramValue = getParamValue(param, rule.paramName());
                        //System.err.println("paramValue:"+paramValue);
                        //获取 参数
                        String newTableName = strategy.returnTableName(rule.tableName(), paramValue);
                        try {
                            convertedSql = originalSql.replaceAll(rule.tableName(),newTableName );
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

                }
                log.info("新sql是:" + convertedSql);
                metaStatementHandler.setValue("delegate.boundSql.sql",convertedSql);
            }
        }
    }

    public String getParamValue(Object obj,String paramName) {
        if(obj instanceof Map) {
            return (String) ((Map) obj).get(paramName);
        }
        Field[] fields = obj.getClass().getDeclaredFields();
        for(Field field : fields) {
            field.setAccessible(true);
            //System.err.println(field.getName());
            if(field.getName().equalsIgnoreCase(paramName)) {
                try {
                    return (String) field.get(obj);
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
        }
        return null;
    }

}

3、编写策略服务接口

package com.java.mmzsit.framework.mybatisStrategy.strategy;

/**
 * 分表策略服务接口
 * @author mmzsit
 *
 */
public interface Strategy {

    /**
     * 传入表名 和分表参数
     * @param tableName
     * @param splitParam
     * @return
     */
    String returnTableName(String tableName,String splitParam);

}

4、实现一个策略服务接口

package com.java.mmzsit.framework.mybatisStrategy.strategy.impl;

import com.java.mmzsit.framework.mybatisStrategy.framework.util.DateUtil;
import com.java.mmzsit.framework.mybatisStrategy.strategy.Strategy;

import java.text.ParseException;
/**
 * @author :mmzsit
 * @description:按月分表策略
 * @date :2019/6/13 10:29
 */
public class YYYYMM01Strategy implements Strategy {

    @Override
    public String returnTableName(String tableName, String param) {
        try {
            // 结果类似 20190601
            return tableName+"_"+ DateUtil.get_MM01Str(param);
        } catch (ParseException e) {
            e.printStackTrace();
            return tableName;
        }
    }

}

5、编写策略管理类

package com.java.mmzsit.framework.mybatisStrategy;

import com.java.mmzsit.framework.mybatisStrategy.strategy.Strategy;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * @author :mmzsit
 * @description:
 * @date :2019/6/13 10:28
 */
public class StrategyManager {

    public static final String _YYYYMM01 = "YYYYMM01"; //策略名称

    public static final String _YYYYMMDD = "YYYYMMDD";

    public static final String _YYYYMM = "YYYYMM";

    private Map<String,Strategy> strategies = new ConcurrentHashMap<String,Strategy>(10);

    /**
     * 向管理器中添加策略
     * @param strategyName
     * @param strategy
     */
    public void addStrategy(String strategyName,Strategy strategy) {
        strategies.put(strategyName, strategy);
    }

    public  Strategy getStrategy(String key){
        return strategies.get(key);
    }

    public Map<String, Strategy> getStrategies() {
        return strategies;
    }

    public void setStrategies(Map<String, String> strategies) {
        for(Map.Entry<String, String> entry : strategies.entrySet()){
            try {
                this.strategies.put(entry.getKey(),(Strategy)Class.forName(entry.getValue()).newInstance());
            } catch (Exception e) {
                System.out.println("实例化策略出错"+e);
            }
        }
    }

}

6、最后,也是最重要的一点:拦截器已经写好了,但是如何使用呢?

很简单,在你需要进行分表的dao层添加如下注解即可:

@TableSplitTarget(rules={@TableSplitRule(tableName="TESTDATAS",paramName="updatedate",targetName=StrategyManager._YYYYMM01)})

测试

0、建表语句

CREATE TABLE
    TESTDATAS_20190701
    (
        ID NUMBER(4) NOT NULL,
        NAME NVARCHAR2(30),
        AGE NVARCHAR2(2),
        INFORMATION NVARCHAR2(30),
        UPDATEDATE NVARCHAR2(14),
        PRIMARY KEY (ID)
    );

1、启动项目
2、请求地址:http://localhost:8001/add
3、控制打印信息:

2019-07-12 09:24:45.937  INFO 5548 --- [nio-8001-exec-1] 策略分表拦截器【TableSplitInterceptor】           : 进入mybatisSql拦截器:====================
2019-07-12 09:24:45.947  INFO 5548 --- [nio-8001-exec-1] 策略分表拦截器【TableSplitInterceptor】           : 分表前的SQL:insert into TESTDATAS (ID, NAME, AGE, INFORMATION,
      UPDATEDATE)
    values (?, ?, ?, ?, ?)
2019-07-12 09:24:45.964  INFO 5548 --- [nio-8001-exec-1] 策略分表拦截器【TableSplitInterceptor】           : 新sql是:insert into TESTDATAS_20190501 (ID, NAME, AGE, INFORMATION,
      UPDATEDATE)
    values (?, ?, ?, ?, ?)
2019-07-12 09:24:46.140  INFO 5548 --- [nio-8001-exec-1] 数据插入分表【AddDataImpl】                      : 插入数据成功
2019-07-12 09:24:46.141  INFO 5548 --- [nio-8001-exec-1] 策略分表拦截器【TableSplitInterceptor】           : 进入mybatisSql拦截器:====================
2019-07-12 09:24:46.149  INFO 5548 --- [nio-8001-exec-1] 策略分表拦截器【TableSplitInterceptor】           : 分表前的SQL:insert into TESTDATAS (ID, NAME, AGE, INFORMATION,
      UPDATEDATE)
    values (?, ?, ?, ?, ?)
2019-07-12 09:24:46.150  INFO 5548 --- [nio-8001-exec-1] 策略分表拦截器【TableSplitInterceptor】           : 新sql是:insert into TESTDATAS_20190601 (ID, NAME, AGE, INFORMATION,
      UPDATEDATE)
    values (?, ?, ?, ?, ?)
2019-07-12 09:24:46.190  INFO 5548 --- [nio-8001-exec-1] 数据插入分表【AddDataImpl】                      : 插入数据成功
2019-07-12 09:24:46.191  INFO 5548 --- [nio-8001-exec-1] 策略分表拦截器【TableSplitInterceptor】           : 进入mybatisSql拦截器:====================
2019-07-12 09:24:46.191  INFO 5548 --- [nio-8001-exec-1] 策略分表拦截器【TableSplitInterceptor】           : 分表前的SQL:insert into TESTDATAS (ID, NAME, AGE, INFORMATION,
      UPDATEDATE)
    values (?, ?, ?, ?, ?)
2019-07-12 09:24:46.192  INFO 5548 --- [nio-8001-exec-1] 策略分表拦截器【TableSplitInterceptor】           : 新sql是:insert into TESTDATAS_20190701 (ID, NAME, AGE, INFORMATION,
      UPDATEDATE)
    values (?, ?, ?, ?, ?)
2019-07-12 09:24:46.204  INFO 5548 --- [nio-8001-exec-1] 数据插入分表【AddDataImpl】                      : 插入数据成功

4、查看数据库信息

总结

其实这也算是对mybatis底层的一种使用了,因为对其需要执行的mysql语句进行了拦截,然后进行重新拼接后才继续执行操作的。

代码已经提交github:springboot-mybatisInterceptor

原文地址:https://www.cnblogs.com/mmzs/p/11174551.html

时间: 2024-10-08 08:12:24

玩转SpringBoot之整合Mybatis拦截器对数据库水平分表的相关文章

mybatis拦截器使用

[toc] mybatis拦截器入门 mybatis 拦截器接口Interceptor Interceptor接口,我们通过这个接口可以自定义我们自己的拦截器,从而实现在拦截的时候写上我们自己的一些逻辑.该接口提供了三个方法, 接口介绍: intercept方法: intercept方法是在进行拦截的时候要执行的方法. plugin方法: 该方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理. setProperties方法: mybatis配置文件中co

mybatis拦截器

业务需求:由于公司业务需要在所有的sql的增删改查中必须包含officeId,业务以officeId做隔离.因此做了一个Mybatis的的过去器.通过拦截sql处理的过程来判断接口sql是否包含officeId,如果不包含则添加officeId.@NoNeedOffice的注解可以添加在Dao的接口类或方法上.用于标识不需要处理的接口. package com.example.springcloud.provider; import com.example.springcloud.provide

Mybatis拦截器介绍及分页插件

1.1    目录 1.1 目录 1.2 前言 1.3 Interceptor接口 1.4 注册拦截器 1.5 Mybatis可拦截的方法 1.6 利用拦截器进行分页 1.2     前言 拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法.Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑.打个比方,对于Executor

Mybatis拦截器介绍

拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法.Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑.打个比方,对于Executor,Mybatis中有几种实现:BatchExecutor.ReuseExecutor.SimpleExecutor和CachingExecutor.这个时候如果你觉得这几种实现对于Execu

【Mybatis】1、Mybatis拦截器

MyBatis拦截器原理探究 http://www.cnblogs.com/fangjian0423/p/mybatis-interceptor.html MyBatis 拦截器 (实现分页功能) http://www.cnblogs.com/jethypc/p/5149183.html 由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理 http://zhangbo-peipei-163-com.iteye.com/blog/2033832 使用方法 https://github.

Mybatis拦截器实现分页

本文介绍使用Mybatis拦截器,实现分页:并且在dao层,直接返回自定义的分页对象. 最终dao层结果: public interface ModelMapper { Page<Model> pageByConditions(RowBounds rowBounds, Model record); } 接下来一步一步来实现分页. 一.创建Page对象: public class Page<T> extends PageList<T> { private int page

Mybatis拦截器 mysql load data local 内存流处理

Mybatis 拦截器不做解释了,用过的基本都知道,这里用load data local主要是应对大批量数据的处理,提高性能,也支持事务回滚,且不影响其他的DML操作,当然这个操作不要涉及到当前所load的数据,其中在使用的时候一定要local , 这个命令使用是mysql规定的,否则不加则会认为是服务器本地的文件.这里主要是以流的方式来做处理,这样可以使用内存流,这样就可以避免在某些时候需要生成文件才能导入的多余操作,和IO性能消耗.也可以是应用本地的文件. 注:该做法只试用于存入数据的表,不

【Mybatis】1、Mybatis拦截器学习资料汇总

MyBatis拦截器原理探究 http://www.cnblogs.com/fangjian0423/p/mybatis-interceptor.html [myBatis]Mybatis中的拦截器 http://blog.csdn.net/moshenglv/article/details/52699976 MyBatis 拦截器 (实现分页功能) http://www.cnblogs.com/jethypc/p/5149183.html 由浅入深分析mybatis通过动态代理实现拦截器(插件

Mybatis 拦截器(二)

Mybatis拦截器介绍 拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法.Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑.打个比方,对于Executor,Mybatis中有几种实现:BatchExecutor.ReuseExecutor.SimpleExecutor和CachingExecutor.这个时候如果你觉