利用Mybatis拦截器实现业务(sql 参数 或 自定义规则)分表

代码参考,特此鸣谢


既然使用拦截器做的,那么明确几个问题:

  • 如何定义和加载拦截器?
  • 如何实现拦截?
  • 如何定义拦截的规则?
  • 拦截规则是如何管理的,如果有多个拦截规则怎么操作?
  • 哪些该拦哪些不该拦?

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();
}



原代码在此,想点就点

你可能感兴趣的:(java)