mybatis 拦截器实现分表 操作具体如下:
1、准备一张数据库表 demo_2019
2、搭建一套基本的ssm框架
3、采用策略设计模式来实现分表操作
下面是策略设计模式核心类图
4、具体观看下面的核心代码:
自定义分表注解:TableSplit 类
package com.lmdsoft.util.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: lmdsoft
* @Date: 2019/6/4 18:07
* @Version V1.0
* @Description: 自定义分表注解
**/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface TableSplit {
//是否分表
public boolean split() default true;
public String value() default "";
public String field() default "";
//获取分表策略
public String strategy();
}
定义策略接口:Strategy
package com.lmdsoft.util.split;
import java.util.Map;
/**
* @Author: lmdsoft
* @Date: 2019/6/4 19:03
* @Version V1.0
* @Description: 定义策略接口
**/
public interface Strategy {
public static final String TABLE_NAME="table_name";
public static final String SPLIT_FIELD="split_field";
public static final String EXECUTE_PARAM_DECLARE="execute_param_declare";
public static final String EXECUTE_PARAM_VALUES="execute_param_values";
/**
* 传入一个需要分表的表名,返回一个处理后的表名
* Strategy必须包含一个无参构造器
* @param params
* @return
*/
public String convert(Map<String, Object> params) throws Exception;
}
按年分表策略实现
package com.lmdsoft.util.split.impl;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import com.lmdsoft.util.split.Strategy;
/**
* @Author: lmdsoft
* @Date: 2019/6/4 18:03
* @Version V1.0
* @Description: 按年分表策略
**/
public class YYYYStrategy implements Strategy {
@Override
public String convert(Map<String, Object> params) {
SimpleDateFormat sdf = new SimpleDateFormat("YYYY");
StringBuilder sb=new StringBuilder(params.get(Strategy.TABLE_NAME).toString());
sb.append("_");
sb.append(sdf.format(new Date()));
return sb.toString();
}
}
策略管理者类
package com.lmdsoft.util.split;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @Author: lmdsoft
* @Date: 2019/6/4 19:03
* @Version V1.0
* @Description: 策略管理者
**/
public class StrategyManager{
private Log log= LogFactory.getLog(StrategyManager.class);
private Map<String,Strategy> strategies = new ConcurrentHashMap<String,Strategy>(10);
public Strategy getStrategy(String key){
return strategies.get(key);
}
public Map<String, Strategy> getStrategies() {
return strategies;
}
public void setStrategies(Map<String, String> strategies) {
for(Entry<String, String> entry : strategies.entrySet()){
try {
this.strategies.put(entry.getKey(),(Strategy)Class.forName(entry.getValue()).newInstance());
} catch (Exception e) {
log.error("实例化策略出错", e);
}
}
printDebugInfo();
}
private void printDebugInfo(){
StringBuffer msg= new StringBuffer("初始化了"+strategies.size()+"策略");
for(String key: strategies.keySet()){
msg.append("\n");
msg.append(key);
msg.append(" ---> ");
msg.append(strategies.get(key));
}
log.debug(msg.toString());
}
}
分表拦截器
package com.lmdsoft.util.split;
import com.lmdsoft.util.ContextHelper;
import com.lmdsoft.util.JsonUtil;
import com.lmdsoft.util.annotation.TableSplit;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMap;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* @Author: lmdsoft
* @Date: 2019/6/4 19:03
* @Version V1.0
* @Description: 分表拦截器
**/
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) })
public class TableSplitInterceptor implements Interceptor {
private Log log = LogFactory.getLog(getClass());
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
@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, new DefaultReflectorFactory());
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject");
doSplitTable(metaStatementHandler,parameterObject);
// 传递给下一个拦截器处理
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
}
private void doSplitTable(MetaObject metaStatementHandler,Object param ) throws Exception {
String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
if (originalSql != null && !originalSql.equals("")) {
log.info("分表前的SQL:\n" + 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);
ParameterMap paramMap = mappedStatement.getParameterMap();
Method method = findMethod(clazz.getDeclaredMethods(), methodName);
// 根据配置自动生成分表SQL
TableSplit tableSplit = null;
if (method != null) {
tableSplit = method.getAnnotation(TableSplit.class);
}
if (tableSplit == null) {
tableSplit = clazz.getAnnotation(TableSplit.class);
}
System.out.printf(JsonUtil.toString(paramMap));
if (tableSplit != null && tableSplit.split() && StringUtils.isNotBlank(tableSplit.strategy())) {
StrategyManager strategyManager = ContextHelper.getBean(StrategyManager.class);
String convertedSql = "";
String[] strategies = tableSplit.strategy().split(",");
for (String str : strategies) {
Strategy strategy = strategyManager.getStrategy(str);
Map<String,Object> params =new HashMap<String,Object>();
params.put(Strategy.TABLE_NAME, tableSplit.value());
params.put(Strategy.SPLIT_FIELD, tableSplit.field());
params.put(Strategy.EXECUTE_PARAM_DECLARE, paramMap);
params.put(Strategy.EXECUTE_PARAM_VALUES, param);
convertedSql = originalSql.replaceAll(tableSplit.value(), strategy.convert(params));
}
metaStatementHandler.setValue("delegate.boundSql.sql", convertedSql);
log.info("分表后的SQL:\n" + convertedSql);
}
}
}
private Method findMethod(Method[] methods, String methodName) {
for (Method method : methods) {
if (method.getName().equals(methodName)) {
return method;
}
}
return null;
}
}
本文只是简单的介绍了一下采用策略设计模式来用拦截器实现分表的操作,完整的代码可以来下载:
https://github.com/lmdsoft/TableSplit.git
https://download.csdn.net/download/lmdsoft/11275331