代码参考,特此鸣谢
既然使用拦截器做的,那么明确几个问题:
ps:问题顺序不代表编码顺序。
问题一:如何定义和加载拦截器?
1、先定义拦截器。
import java.sql.Connection;
import java.util.Properties;
import com.zxx.im.util.annotation.TableSplit;
import org.apache.ibatis.executor.statement.StatementHandler;
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.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 org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class TableSplitInterceptor implements Interceptor {
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);
Object parameterObject = metaStatementHandler.getValue("delegate.boundSql");
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;
}
}
private void doSplitTable(MetaObject metaStatementHandler,Object param) throws ClassNotFoundException{
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-mybatis.xml");
String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
if (originalSql != null && !originalSql.equals("")) {
System.out.println("分表前的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
TableSplit tableSplit = classObj.getAnnotation(TableSplit.class);
if (tableSplit != null && tableSplit.split()) {
StrategyManager strategyManager = (StrategyManager) ctx.getBean("strategyManager");
Strategy strategy=strategyManager.getStrategy(tableSplit.strategy());//获取分表策略来处理分表
String convertedSql= null;
try {
BoundSql sql = (BoundSql)param;
convertedSql = originalSql.replaceAll(tableSplit.value(), strategy.convert(tableSplit.value(),sql.getParameterMappings(),sql.getParameterObject()));
} catch (Exception e) {
e.printStackTrace();
}
metaStatementHandler.setValue("delegate.boundSql.sql",convertedSql);
System.out.println("分表后的SQL:"+convertedSql);
}
}
}
@Override
public void setProperties(Properties properties) {
}
}
2、将拦截器加载到应用。配置文件
<bean id="TableSplitInterceptor" class="com.zxx.im.util.split.TableSplitInterceptor">bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="ds">property>
<property name="typeAliasesPackage" value="com.zxx.entitys">property>
<property name="plugins">
<list>
<ref bean="TableSplitInterceptor"/>
list>
property>
bean>
<bean id="strategyManager" class="com.zxx.im.util.split.StrategyManager">
<property name="strategies">
<map>
<entry key="YYYY" value="com.zxx.im.util.split.impl.YYYYStrategy" />
map>
property>
bean>
问题二:如何实现拦截?
//这里是配合自定义注解实现的
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface TableSplit {
//是否分表
public boolean split() default true;
//需要分表的 表名前缀
public String value() default "";
//获取分表策略
public String strategy();
}
问题三:如何定义拦截的规则?
//用接口定义拦截后返回新的表名
public interface Strategy {
/**
* 传入一个需要分表的表名,返回一个处理后的表名
* @param params
* @return
*/
public String convert(String params,List parameterMapping,Object val) throws Exception;
}
//接口具体实现
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class YYYYStrategy implements Strategy {
/**
* @param params 表名
* @return
*/
@Override
public String convert(String params,List parameterMapping,Object val) {
//这里特别注意一下,用mybatis逆向工程生成代码时,使用生成的update语句执行会报错,
//所以建议手写update语句,更删改没问题,具体原因debug一下看看传过来的值就知道了,如有解决方案还请不吝赐教
//根据sql定义分表规则
// 因为sql的参数有对象和变量两种,这里为了方便统一转成json处理
JSONObject obj = null;
if(val.toString().contains("=")){ //用变量传值
String replaceAll = val.toString().replaceAll("=",":");
obj = (JSONObject) JSONObject.parse(replaceAll);
}else{ //用对象传值
obj = (JSONObject) JSONObject.parse(JSON.toJSONString(val));
}
for(ParameterMapping para : parameterMapping){
if(para.getProperty().endsWith("xxx")){
String str = obj.getString("xxx"); //获取制定sql参数
return params + str.substring(4,6); //自定义规则
}
}
return sb.toString(); //返回拼接好的 表名
}
}
问题四:拦截规则是如何管理的,如果有多个拦截规则怎么操作?
ps:多个拦截规则只需要在配置文件分别注册就OK!
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
//可以理解为拦截规则的容器,即使一条拦截策略也要写这个类
public class StrategyManager{
private Map strategies = new ConcurrentHashMap(10);
public Strategy getStrategy(String key){
return strategies.get(key);
}
public Map getStrategies() {
return strategies;
}
public void setStrategies(Map strategies) {
for(Entry entry : strategies.entrySet()){
try {
this.strategies.put(entry.getKey(),(Strategy)Class.forName(entry.getValue()).newInstance());
} catch (Exception e) {
System.out.println("实例化策略出错"+e);
}
}
printDebugInfo();
}
//该size显示的就是初始化了几条策略,如果不想看完全可以不写
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));
}
}
}
问题五:哪些该拦哪些不该拦?
//只需要在需要分表操作的Dao接口添加注解即可
//假设数据库有 test_01,test_02,test_03 三张表, value为 test
//strategy 为 配置文件中配置分表策略的key
@TableSplit(value="student", strategy="YYYY")
public interface TestDao {
String GetData();
}
原代码在此,想点就点