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
再在业务系统中,实现该接口即可。