Mybatis拦截实现查询sql统一处理

1、实现拦截接口

Mybatis提供了拦截接口,可通过实现该接口,配合springMVC的配置,完成sql拦截。

 

import java.util.Properties;

import org.apache.ibatis.executor.Executor;
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.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

@Intercepts(@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class MybatisSqlInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object obj) {
        return Plugin.wrap(obj, this);
    }

    @Override
    public void setProperties(Properties arg0) {
        // doSomething
    }
}

2、xml配置

找到mybatis的配置,在configuration处如下配置:


	
  		
	
 

 

完成以上两步,则框架能对查询的sql进行拦截了。接下来,我们要在MybatisSqlInterceptor的intercept方法内实现sql的调整重置了。

 

1、获取sql

 

    private String getSqlByInvocation(Invocation invocation) {
        final Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        return boundSql.getSql();
    }

2、通过获取的springMVC中的处理类的bean名称,得到实例和处理方法,循环处理sql

 

    private String processSqlByInterceptor(Invocation invocation, String sql, String[] interceptorNames) {
        if (interceptorNames == null || interceptorNames.length == 0) {
            return sql;
        }

        String resultSql = sql;
        for (int i = 0; i < interceptorNames.length; i++) {
            Object interceptorObj = SpringContextUtil.getBean(interceptorNames[i]);//SpringContextUtil:spring获取bean的工具类
            if (interceptorObj == null) {
                continue;
            }
            resultSql = ((SqlInterceptor) interceptorObj).doInterceptor(invocation, resultSql);
        }

        return resultSql;
    }

3、包装sql后重置sql

 

    private void resetSql2Invocation(Invocation invocation, String sql) throws SQLException {
        final Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = statement.getBoundSql(parameterObject);
        MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSql));
        MetaObject msObject = MetaObject.forObject(newStatement);

        msObject.setValue("sqlSource.boundSql.sql", sql);
        args[0] = newStatement;
    }
    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder =
                new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());

        return builder.build();
    }

定义一个内部辅助类,作用是包装sql

 

    class BoundSqlSqlSource implements SqlSource {


        private BoundSql boundSql;


        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }


        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }

4、辅助:获取操作类型

 

    private String getOperateType(Invocation invocation) {
        final Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        SqlCommandType commondType = ms.getSqlCommandType();
        if (commondType.compareTo(SqlCommandType.SELECT) == 0) {
            return SQL_OPERATE_TYPE_SELECT;
        }
        if (commondType.compareTo(SqlCommandType.INSERT) == 0) {
            return SQL_OPERATE_TYPE_INSERT;
        }
        if (commondType.compareTo(SqlCommandType.UPDATE) == 0) {
            return SQL_OPERATE_TYPE_UPDATE;
        }
        if (commondType.compareTo(SqlCommandType.DELETE) == 0) {
            return SQL_OPERATE_TYPE_DELETE;
        }
        return null;
    }

5、辅助:获取properties中的常量配置

 

    private String[] getInterceptorNames(Invocation invocation) {
        String operateType = getOperateType(invocation);
        if (StringUtils.isBlank(operateType)) {
            return new String[] {};
        }

        String interceptorNameKey = "sql.interceptor.name.4." + operateType;
        String interceptorNameString = CfgFactory.getDefault().getFirst(interceptorNameKey);
        if (StringUtils.isBlank(interceptorNameString)) {
            return new String[] {};
        }

        return interceptorNameString.split(",");
    }

6、辅助:处理类接口

 

import org.apache.ibatis.plugin.Invocation;

/**
 * sql处理类接口,提供sql处理方法,并将处理后的sql返回
 * 
 * @版权:SINOSERVICES 版权所有 (c) 2013
 * @author:admin
 * @version Revision 1.0.0
 * @email:admin
 * @see:
 * @创建日期:2018年1月18日 
 * @功能说明: 
 * @begin
 * @修改记录:
 * @修改后版本 修改人 修改内容
 * @2018年1月18日 admin 创建
 * @end
 */
public interface SqlInterceptor{
    /**
     * @param invocation
     * @param sql
     * @return
     * @author:admin
     * @email:admin
     * @创建日期:2018年1月10日 
     * @功能说明: 拦截查询的接口方法,通过原始的sql,判断是否含有占位符。如果没有占位符,则直接返回。否则,根据数据权限查询得到数据权限的sql替换占位符后返回。
     */
    public String doInterceptor(Invocation invocation, String sql);
}

以上的方法,提供了interceptor的功能支持。现在我们组合起来,改造interceptor方法。

 

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        // 获取sql
        String sql = getSqlByInvocation(invocation);
        if (StringUtils.isBlank(sql)) {
            return invocation.proceed();
        }

        // 获取业务系统中配置的sql处理器(service)的名称
        String[] interceptorNames = getInterceptorNames(invocation);
        if (interceptorNames == null || interceptorNames.length == 0) {
            return invocation.proceed();
        }

        // sql交由处理类处理
        String sql2Reset = processSqlByInterceptor(invocation, sql, interceptorNames);

        // 包装sql后,重置到invocation中
        resetSql2Invocation(invocation, sql2Reset);

        // 返回,继续执行
        return invocation.proceed();
    }

这样,我们就实现了查询sql的统一调整了。业务系统内的所有查询,都将被此拦截器拦截,并做处理。

当然,处理的逻辑,因各业务而不一。

我们的MybatisSqlInterceptor,提供的是常量配置的地方配置好处理接口SqlInterceptor的实现类的bean名称

 

sql.interceptor.name.4.select=dataAuthInterceptor

再在业务系统中,实现该接口即可。

你可能感兴趣的:(事务管理,Mybatis,Java技巧)