? ?最近一段時間公司搞新項目,數(shù)據(jù)庫orm選用了mybatis框架佑钾。使用一段時間mybaits后感覺比其他orm框架靈活好用,好處就不說了烦粒,網(wǎng)上一搜大把休溶。本次主要講下mybatis自定義攔截器功能的開發(fā)代赁,通過攔截器可以解決項目中蠻多的問題,雖然很多功能不用攔截器也可以實現(xiàn)兽掰,但使用自定義攔截器實現(xiàn)功能從我角度至少以下優(yōu)點(1)靈活芭碍,解耦(2)統(tǒng)一控制 ,減少開發(fā)工作量孽尽,不用散落到每個業(yè)務功能點去實現(xiàn)窖壕。
? ? 一般業(yè)務系統(tǒng)項目都涉及到數(shù)據(jù)權限的控制,此次結合本項目記錄下基于mybatis攔截器實現(xiàn)數(shù)據(jù)權限的過濾,因為項目用到mybatis-plus的分頁插件杉女,數(shù)據(jù)權限攔截過濾的時機也要控制好瞻讽,在分頁攔截器之前先攔截修改sql,不然會導致查詢出來的數(shù)據(jù)同分頁統(tǒng)計出來數(shù)量不一致。
攔截器基本知識
? ? Mybatis采用責任鏈模式熏挎,通過動態(tài)代理組織多個攔截器速勇,通過這些攔截器可以改變mybatis的默認行為,編寫自定義攔截器最好了解下它的原理烦磁,以便寫出安全高效的插件哼勇。
?(1)攔截器均需要實現(xiàn)org.apache.ibatis.plugin.Interceptor 接口,對于自定義攔截器必須使用mybatis 提供的注解來指明我們要攔截的是四類中的哪一個類接口积担。
具體規(guī)則如下:
?a:Intercepts?標識我的類是一個攔截器
?b:Signature 則是指明我們的攔截器需要攔截哪一個接口的哪一個方法陨晶;type對應四類接口中的某一個,比如是 Executor珍逸;method對應接口中的哪類方法聋溜,比如 Executor 的 update 方法;args?對應接口中的哪一個方法,比如 Executor 中 query 因為重載原因叭爱,方法有多個撮躁,args 就是指明參數(shù)類型买雾,從而確定是哪一個方法。
@Intercepts({
??? @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
(2)?mybatis 攔截器默認可攔截的類型四種漓穿,即四種接口類型 Executor、StatementHandler叙赚、ParameterHandler 和 ResultSetHandler,對于我們的自定義攔截器必須使用 mybatis 提供的注解來指明我們要攔截的是四類中的哪一個類接口震叮。
(3)攔截器順序:
?不同類型攔截器的順序Executor -> ParameterHandler -> StatementHandler ->ResultSetHandler
??同類型的攔截器的不同對象攔截順序則根據(jù) mybatis 核心配置文件的配置位置苇瓣,攔截順序是 從上往下,在mybatis 核心配置文件中需要配置我們的 plugin?
數(shù)據(jù)權限過濾
???1.實現(xiàn)業(yè)務需求的數(shù)據(jù)過濾哲嘲,在用戶訪問數(shù)據(jù)庫時進行權限判斷并改造sql,達到限制低權限用戶訪問數(shù)據(jù)的目的
?? 2.采用技術:mybatis攔截器媳禁,java自定義注解,反射侦啸,開源jsqlparser
?? 3.核心業(yè)務流程圖
4.代碼實現(xiàn)
(1)創(chuàng)建自定義注解
```
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 數(shù)據(jù)權限注解
*
*/
@Documented
@Target( value = { ElementType.TYPE, ElementType.METHOD } )
@Retention( RetentionPolicy.RUNTIME )
@Inherited
public @interface DataAuth
{
/**
* 追加sql的方法名
* @return
*/
public String method() default "whereSql";
/**
* 表別名
* @return
*/
public String tableAlias() default "";
}
```
(2)mapper方法增加權限注解
```
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface TestMapper extends BaseMapper<Test> {
? ? /**
? ? * 增加權限注解
? ? *
? ? */
? ? @DataAuth(tableAlias = "o")
? ? List<TestEntity> listData(TestQuery testQuery);
}
```
(3)創(chuàng)建自定義攔截器
```
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
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.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Properties;
/**
* 數(shù)據(jù)權限攔截器
* 根據(jù)各個微服務,繼承DataAuthService增加不同的where語句
*
*/
@Component
@Intercepts({@Signature(method = "query",type = Executor.class,args =? {
? ? ? ? MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
? ? )
})
public class MybatisDataAuthInterceptor implements Interceptor,
? ? ApplicationContextAware {
? ? private final static Logger logger = LoggerFactory.getLogger(MybatisDataAuthInterceptor.class);
? ? private static ApplicationContext context;
? ? @Override
? ? public void setApplicationContext(ApplicationContext applicationContext)
? ? ? ? throws BeansException {
? ? ? ? context = applicationContext;
? ? }
? ? @Override
? ? public Object intercept(Invocation arg0) throws Throwable {
? ? ? ? MappedStatement mappedStatement = (MappedStatement) arg0.getArgs()[0];
? ? ? ? // 只對查詢sql攔截
? ? ? ? if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
? ? ? ? ? ? return arg0.proceed();
? ? ? ? }
? ? ? ? // String mSql = sql;
? ? ? ? // 注解邏輯判斷 添加注解了才攔截追加
? ? ? ? Class<?> classType = Class.forName(mappedStatement.getId()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .substring(0,
? ? ? ? ? ? ? ? ? ? mappedStatement.getId().lastIndexOf(".")));
? ? ? ? String mName = mappedStatement.getId()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .substring(mappedStatement.getId()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .lastIndexOf(".") +
? ? ? ? ? ? ? ? 1, mappedStatement.getId().length()); //
? ? ? ? for (Method method : classType.getDeclaredMethods()) {
? ? ? ? ? ? if (method.isAnnotationPresent(DataAuth.class) &&
? ? ? ? ? ? ? ? ? ? mName.equals(method.getName())) {
? ? ? ? ? ? ? ? /**
? ? ? ? ? ? ? ? * 查找標識了該注解 的實現(xiàn) 類
? ? ? ? ? ? ? ? */
? ? ? ? ? ? ? ? Map<String, Object> beanMap = context.getBeansWithAnnotation(DataAuth.class);
? ? ? ? ? ? ? ? if ((beanMap != null) && (beanMap.entrySet().size() > 0)) {
? ? ? ? ? ? ? ? ? ? for (Map.Entry<String, Object> entry : beanMap.entrySet()) {
? ? ? ? ? ? ? ? ? ? ? ? DataAuth action = method.getAnnotation(DataAuth.class);
? ? ? ? ? ? ? ? ? ? ? ? if (StringUtils.isEmpty(action.method())) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? ? ? Method md = entry.getValue().getClass()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .getMethod(action.method(),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new Class[] { String.class });
? ? ? ? ? ? ? ? ? ? ? ? ? ? /**
? ? ? ? ? ? ? ? ? ? ? ? ? ? * 反射獲取業(yè)務 sql
? ? ? ? ? ? ? ? ? ? ? ? ? ? */
? ? ? ? ? ? ? ? ? ? ? ? ? ? String whereSql = (String) md.invoke(context.getBean(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? entry.getValue().getClass()),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new Object[] { action.tableAlias() });
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (!StringUtils.isEmpty(whereSql) &&
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? !"null".equalsIgnoreCase(whereSql)) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Object parameter = null;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (arg0.getArgs().length > 1) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? parameter = arg0.getArgs()[1];
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? BoundSql boundSql = mappedStatement.getBoundSql(parameter);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? MappedStatement newStatement = newMappedStatement(mappedStatement,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new BoundSqlSqlSource(boundSql));
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? MetaObject msObject = MetaObject.forObject(newStatement,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new DefaultObjectFactory(),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new DefaultObjectWrapperFactory(),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new DefaultReflectorFactor());
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /**
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? * 通過JSqlParser解析 原有sql,追加sql條件
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? */
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? CCJSqlParserManager parserManager = new CCJSqlParserManager();
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Select select = (Select) parserManager.parse(new StringReader(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? boundSql.getSql()));
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? PlainSelect selectBody = (PlainSelect) select.getSelectBody();
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Expression whereExpression = CCJSqlParserUtil.parseCondExpression(whereSql);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selectBody.setWhere(new AndExpression(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selectBody.getWhere(),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new Parenthesis(whereExpression)));
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /**
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? * 修改sql
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? */
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? msObject.setValue("sqlSource.boundSql.sql",
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selectBody.toString());
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? arg0.getArgs()[0] = newStatement;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? logger.info("Interceptor sql:" +
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selectBody.toString());
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? logger.error(null, e);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return arg0.proceed();
? ? }
? ? 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();
? ? }
? ? /**
? ? * 當目標類是Executor類型時,才包裝目標類齐佳,否者直接返回目標本身,減少目標被代理的次數(shù)
? ? */
? ? @Override
? ? public Object plugin(Object target) {
? ? ? ? if (target instanceof Executor) {
? ? ? ? ? ? return Plugin.wrap(target, this);
? ? ? ? }
? ? ? ? return target;
? ? }
? ? @Override
? ? public void setProperties(Properties arg0) {
? ? ? ? // TODO Auto-generated method stub
? ? }
? ? class BoundSqlSqlSource implements SqlSource {
? ? ? ? private BoundSql boundSql;
? ? ? ? public BoundSqlSqlSource(BoundSql boundSql) {
? ? ? ? ? ? this.boundSql = boundSql;
? ? ? ? }
? ? ? ? @Override
? ? ? ? public BoundSql getBoundSql(Object parameterObject) {
? ? ? ? ? ? return boundSql;
? ? ? ? }
? ? }
}
```
(4)增加業(yè)務邏輯
```
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.winhong.wincloud.constant.RoleTypeJudge;
import com.winhong.wincore.async.ThreadLocalHolder;
import com.winhong.wincore.user.LoginUserHolder;
import com.winhong.wincore.user.UserInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public abstract class AbstractDataAuthService {
? ? private static final Logger LOG = LoggerFactory.getLogger(AbstractDataAuthService.class);
? ? /**
? ? * 默認查詢sql,根據(jù)角色不同追加不同業(yè)務查詢條件
? ? *
? ? * @return
? ? */
? ? public String whereSql(String tableAlias) {
? ? ? ? if (!StringUtils.isEmpty(tableAlias)) {
? ? ? ? ? ? tableAlias = tableAlias + ".";
? ? ? ? }
? ? ? ? StringBuffer sql = new StringBuffer();
? ? ? ? //利用threadlocal獲取用戶角色信息
? ? ? ? UserInfo userInfo = LoginUserHolder.getUser();
? ? ? ? // 普通 用戶
? ? ? ? if (RoleTypeJudge.isNormalUser(userInfo.getRoleTypeCode())) {
? ? ? ? ? ? sql.append(nomalUserSql(userInfo.getUserUuid(), tableAlias));
? ? ? ? }
? ? ? ? // 管理員
? ? ? ? else if (RoleTypeJudge.isManager(userInfo.getRoleTypeCode())) {
? ? ? ? ? ? sql.append(managerSql(tableAlias));
? ? ? ? } else {
? ? ? ? }
? ? ? ? return sql.toString();
? ? }
}
```