利用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

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

  1. 基于Spring和Mybatis拦截器实现数据库操作读写分离

    首先需要配置好数据库的主从同步: 上一篇文章中有写到:https://www.cnblogs.com/xuyiqing/p/10647133.html 为什么要进行读写分离呢? 通常的Web应用大多数 ...

  2. 通过spring抽象路由数据源+MyBatis拦截器实现数据库自动读写分离

    前言 之前使用的读写分离的方案是在mybatis中配置两个数据源,然后生成两个不同的SqlSessionTemplate然后手动去识别执行sql语句是操作主库还是从库.如下图所示: 好处是,你可以人为 ...

  3. spring boot 实现mybatis拦截器

    spring boot 实现mybatis拦截器 项目是个报表系统,服务端是简单的Java web架构,直接在请求参数里面加了个query id参数,就是mybatis mapper的query id ...

  4. mybatis拦截器使用

    目录 mybatis 拦截器接口Interceptor spring boot + mybatis整合 创建自己的拦截器MyInterceptor @Intercepts注解 mybatis拦截器入门 ...

  5. "犯罪心理"解读Mybatis拦截器

    原文链接:"犯罪心理"解读Mybatis拦截器 Mybatis拦截器执行过程解析 文章写过之后,我觉得 "Mybatis 拦截器案件"背后一定还隐藏着某种设计动 ...

  6. 详解Mybatis拦截器(从使用到源码)

    详解Mybatis拦截器(从使用到源码) MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能. 本文从配置到源码进行分析. 一.拦截器介绍 MyBatis 允许你在 ...

  7. 解决mybatis拦截器无法注入spring bean的问题

    公司要整合rabbitmq与mybatis拦截器做一个数据同步功能. 整合过程中大部分环节都没什么问题,就是遇到了mybatis拦截器 @Intercepts(@Signature(type = Ex ...

  8. Mybatis拦截器

    Mybatis拦截器

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

    Mybatis 拦截器不做解释了,用过的基本都知道,这里用load data local主要是应对大批量数据的处理,提高性能,也支持事务回滚,且不影响其他的DML操作,当然这个操作不要涉及到当前所lo ...

随机推荐

  1. WPF Binding妙处-既无Path也无Source

    <Window x:Class="XamlTest.Window12"        xmlns="http://schemas.microsoft.com/win ...

  2. DateTime格式转换结果

    Console.WriteLine(string.Format("ToLongDateString:{0}", DateTime.Now.ToLongDateString())); ...

  3. ubuntu 16.04 安装 openjdk 1.7

    由于编译Android源码需要openjdk1.7.X版本.ubuntu 16.04自带openjdk为1.8.X版本. sudo apt-get install openjdk-7-jre 或者su ...

  4. Redis简介和安装

    Redis介绍 Redis是一种Key-Value存储系统(数据库),其提供了一组丰富的数据结构,如List,Sets,Hashes和Ordered Sets Redis安装 wget <Red ...

  5. 地球坐标-火星坐标-百度坐标及之间的转换算法 C#

    美国GPS使用的是WGS84的坐标系统,以经纬度的形式来表示地球平面上的某一个位置.但在我国,出于国家安全考虑,国内所有导航电子地图必须使 用国家测绘局制定的加密坐标系统,即将一个真实的经纬度坐标加密 ...

  6. asp.net mvc下实现微信公众号(JsApi)支付介绍

    本文主要讲解asp.net mvc框架下公众号支付如何实现,公众号支付主要包括三个核心代码,前台调起支付js代码.对应js调用参数参数生成代码.支付成功处理代码. 一.微信支付方式介绍 微信提供了各种 ...

  7. java-mysql(2) Prepared statement

    上一篇学习了java如何链接配置mysql,这篇学习下java如何处理sql预处理语句(PreparedStatement),首先是一个sql预处理的例子: package core; import ...

  8. django自带的cache

    cache语法 from django.core.cache import cache #存入内存 cache.set("aaa",123) #从内存中获取 cache.get(& ...

  9. "犯罪心理"解读Mybatis拦截器

    原文链接:"犯罪心理"解读Mybatis拦截器 Mybatis拦截器执行过程解析 文章写过之后,我觉得 "Mybatis 拦截器案件"背后一定还隐藏着某种设计动 ...

  10. echarts 中国地图标注所在点

    达到的效果: 1.本身是个中国地图‘ 2.直接通过经纬度标注 3.标注点可以是其他样子(比如:五角星) 4.标注点具有提示框并且鼠标可以进入 5.提示框里的链接可点击(可以添加为链接事件): 所需要技 ...