既然使用拦截器做的,那么明确几个问题:
- 如何定义和加载拦截器?
- 如何实现拦截?
- 如何定义拦截的规则?
- 拦截规则是如何管理的,如果有多个拦截规则怎么操作?
- 哪些该拦哪些不该拦?
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.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;
}
}
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 {
convertedSql = originalSql.replaceAll(tableSplit.value(), strategy.convert(tableSplit.value()));
} catch (Exception e) {
e.printStackTrace();
}
metaStatementHandler.setValue("delegate.boundSql.sql",convertedSql);
System.out.println("分表后的SQL:"+convertedSql);
}
}
}
@Override
public void setProperties(Properties properties) {
}
}
2、将拦截器加载到应用。配置文件
<!-- 在 Mybatis 配置文件中定义 Session 工厂时注入 -->
<!-- 拦截器全路径 -->
<bean id="TableSplitInterceptor" class="com.zxx.im.util.split.TableSplitInterceptor"></bean>
<!-- Session工厂 -->
<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>
<!-- mybatis配置 -->
<!-- 配置分表策略 -->
<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) 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) {
//这里是获取session存的值,进行取模操作
//具体代码根据需求自定义,总之返回值肯定是分表后的数据库表名
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
String userId = (String) session.getAttribute("userId");
Integer of = Integer.valueOf(userId);
of = of%20;
StringBuilder sb=new StringBuilder(params.toString());
sb.append("_");
sb.append(""+of); //拼接UserId%20
return sb.toString(); //返回拼接好的 表名
}
}
问题四:拦截规则是如何管理的,如果有多个拦截规则怎么操作?
ps:多个拦截规则只需要在配置文件分别注册就OK!
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
//可以理解为拦截规则的容器,即使一条拦截策略也要写这个类
public class StrategyManager{
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) {
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();
}