mybatis分表拦截器实现

MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?

我们进入官网看一看:

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,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)

我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。

总体概括为:

  1. 拦截执行器的方法
  2. 拦截参数的处理
  3. 拦截结果集的处理
  4. 拦截Sql语法构建的处理

对于拦截器Mybatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。我们先来看一下这个接口的定义:

package org.apache.ibatis.plugin;
 
import java.util.Properties;
 
public interface Interceptor {
 
  Object intercept(Invocation invocation) throws Throwable;
 
  Object plugin(Object target);
 
  void setProperties(Properties properties);
 
}

我们可以看到在该接口中一共定义有三个方法,intercept、plugin和setProperties。plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法。setProperties方法是用于在Mybatis配置文件中指定一些属性的。

分表代码实现如下:

首先定义一个分表的注解

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 })
public @interface TableSharde {
    /**
     * 待分表的表名
     * @return
     */
    String tableName();

    /**
     * 分表策略
     * @return
     */
    Class<?> strategy();

    /**
     * 分表条件
     * @return
     */
    String[] shardeBy();
}

分表拦截器实现

import com.twsz.annotation.TableSharde;
import com.twsz.exception.BusinessException;
import com.twsz.tablesharde.TableStrategy;
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.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
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.util.StringUtils;

import java.sql.Connection;
import java.util.Properties;

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class}) })
public class TableShardeInterceptor implements Interceptor {

    private static Log log = LogFactory.getLog(TableShardeInterceptor.class);
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();

    @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, REFLECTOR_FACTORY);
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");//获取sql语句
        String originSql = boundSql.getSql();
        //log.info("boundSql:" + originSql);
        if (!StringUtils.isEmpty(originSql)) {
            MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
            String id = mappedStatement.getId();
            String className = id.substring(0, id.lastIndexOf("."));
            Class<?> clazz = Class.forName(className);
            TableSharde tableSharde = clazz.getAnnotation(TableSharde.class);
            String newSql = "";
            if (tableSharde != null) {
                String tableName = tableSharde.tableName();
                String[] shardeBy = tableSharde.shardeBy();
                Class<?> strategyClazz = tableSharde.strategy();
                TableStrategy tableStrategy = (TableStrategy)strategyClazz.newInstance();
                newSql = tableStrategy.doSharde(metaStatementHandler, tableName, shardeBy);
                //log.info("newSql:" + newSql);
                metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
            }

        }
        else {
            log.error("TableShardeInterceptor error,sql is empty");
            throw new BusinessException("TableShardeInterceptor error,sql is empty");
        }

        //Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.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) {

    }

分表策略实现,我这里是根据用户id实现分表的策略,大家也可以实现自己的策略

import com.twsz.tablesharde.TableStrategy;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.reflection.MetaObject;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;

public class ShardeByUserCodeStrategy implements TableStrategy {
    @Override
    public String doSharde(MetaObject metaStatementHandler, String tableName, String[] shardeBy) throws Exception{
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");//获取sql语句
        String originSql = boundSql.getSql();
        boundSql.getParameterMappings();
        String userCode = shardeBy[0];
        Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject");//获取参数
        if (parameterObject instanceof String) {
            originSql = originSql.replaceAll(tableName, tableName + "_" + parameterObject);
        }
        else if (parameterObject instanceof Map) {
            Map<String, Object> map = (Map<String, Object>)parameterObject;
            Set<String> set = map.keySet();
            String value = "";
            for (String key: set) {
                if (key.equals(userCode)) {
                    value = map.get(userCode).toString();
                    break;
                }
            }
            originSql = originSql.replaceAll(tableName, tableName + "_" + value);
        }
        else {
            Class<?> clazz = parameterObject.getClass();
            String value = "";
            Field[] fields = clazz.getDeclaredFields();
            for (Field field: fields) {
                field.setAccessible(true);
                String fieldName = field.getName();
                if (fieldName.equals(userCode)) {
                    value = field.get(parameterObject).toString();
                    break;
                }
            }
            originSql = originSql.replaceAll(tableName, tableName + "_" + value);
        }
        return originSql;
    }

最后需要在数据库会话工厂中配置插件

@Bean(name = "masterSqlSessionFactory")
    @Primary
    SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        sqlSessionFactoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));
        sqlSessionFactoryBean.setPlugins(new Interceptor[] {new TableShardeInterceptor()});
        return sqlSessionFactoryBean.getObject();
    }

         

猜你喜欢

转载自www.cnblogs.com/wang4856304/p/9645688.html