MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?
我们进入官网看一看:
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。
总体概括为:
- 拦截执行器的方法
- 拦截参数的处理
- 拦截结果集的处理
- 拦截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(); }