Mybatis自定義攔截器實(shí)現(xiàn)權(quán)限功能
1、Mybatis 攔截器介紹
1.1 Mybatis 執(zhí)行流程
- 首先讀取配置文件,然后加載映射文件,由SqlSessionFactory工廠對(duì)象去創(chuàng)建核心對(duì)象SqlSession,SqlSession對(duì)象會(huì)通過(guò)Executor執(zhí)行器對(duì)象執(zhí)行sql。然后Executor執(zhí)行器對(duì)象會(huì)調(diào)用StatementHandler對(duì)象去真正的訪問(wèn)數(shù)據(jù)庫(kù)執(zhí)行sql語(yǔ)句。
- 在執(zhí)行sql語(yǔ)句前MapperStatement會(huì)先對(duì)映射信息進(jìn)行封裝,然后StatementHandler調(diào)用ParameterHandler去設(shè)置編譯參數(shù)【#{},${}】,編譯在StatementHandler中進(jìn)行。然后StatementHandler調(diào)用JBDC原生API進(jìn)行處理,獲取執(zhí)行結(jié)果,這個(gè)執(zhí)行結(jié)果交給ResultSetHandler 來(lái)進(jìn)行結(jié)果集封裝,然后將結(jié)果返回給StatementHandler。
- 注意: 這里MapperStatement是對(duì)映射信息的封裝,用于存儲(chǔ)要映射的SQL語(yǔ)句的id、參數(shù)等信息。TypeHandler進(jìn)行數(shù)據(jù)庫(kù)類型和JavaBean類型映射處理。
1.2 Mybatis中可以被攔截的類型
- Executor:攔截執(zhí)行器的方法。
- ParameterHandler:攔截參數(shù)的處理。
- ResultHandler:攔截結(jié)果集的處理。
- StatementHandler:攔截Sql語(yǔ)法構(gòu)建的處理。
1.3 使用規(guī)則
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Intercepts { /** * 定義攔截點(diǎn) * 只有符合攔截點(diǎn)的條件才會(huì)進(jìn)入到攔截器 */ Signature[] value(); }
Signature來(lái)指定咱們需要攔截那個(gè)類對(duì)象的哪個(gè)方法
- type:上述四種類型中的一種;
- method:對(duì)應(yīng)接口中的哪類方法(因?yàn)榭赡艽嬖谥剌d方法);
- args:對(duì)應(yīng)哪一個(gè)方法的入?yún)ⅲ?/p>
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Signature { /** * 定義攔截的類 Executor、ParameterHandler、StatementHandler、ResultSetHandler當(dāng)中的一個(gè) */ Class<?> type(); /** * 在定義攔截類的基礎(chǔ)之上,在定義攔截的方法 */ String method(); /** * 在定義攔截方法的基礎(chǔ)之上在定義攔截的方法對(duì)應(yīng)的參數(shù), * JAVA里面方法可能重載,故注意參數(shù)的類型和順序 */ Class<?>[] args(); }
1.4 攔截器重寫的方法
public interface Interceptor { //起攔截作用,在此定義一些功能 Object intercept(Invocation var1) throws Throwable; //這個(gè)方法的作用是就是讓mybatis判斷,是否要進(jìn)行攔截,然后做出決定是否生成一個(gè)代理 Object plugin(Object var1); //攔截器需要一些變量對(duì)象,而且這個(gè)對(duì)象是支持可配置的。 void setProperties(Properties var1); }
2、實(shí)戰(zhàn)部分:攔截實(shí)現(xiàn)
自定義注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DepartAuth { /** * 添加查詢條件的字段名 * @return */ String field(); EnumDepartAuthType authType() default EnumDepartAuthType.DEPART_ID; }
在所在接口上添加注解
@DepartAuth(field = "xxx", authType = )
切面(AuthAspect)
@Slf4j @Aspect @Component public class DepartAuthAspect { @Autowired private DepartAuthHandler departAuthHandler; @Pointcut("@annotation(org.jeecg.common.auth.depart.annotation.DepartAuth)") public void departAuthPoint() { } @Before("departAuthPoint()") public void before(JoinPoint joinPoint) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Annotation[] annotations = methodSignature.getMethod().getAnnotations(); for (Annotation annotation : annotations) { if (annotation instanceof DepartAuth) { String field = ((DepartAuth) annotation).field(); departAuthHandler.beforeHandler((DepartAuth) annotation); } } } @After("departAuthPoint()") public void after(JoinPoint pjp) { departAuthHandler.afterHandler(); } }
DepartAuthHandler
將信息存儲(chǔ)到 TreadLocalhost 中,方便后續(xù)修改sql的時(shí)候可以讀取到需要修改的字段
@Slf4j @Component public class DepartAuthHandler { public static ThreadLocal<DepartAuth> DEPART_AUTH_CACHE = new ThreadLocal<>(); public static ThreadLocal<Integer> DEPART_AUTH_COUNT = new ThreadLocal<>(); public void beforeHandler(DepartAuth departAuth) { String field = departAuth.field(); if(StringUtils.isNotBlank(field)) { DEPART_AUTH_CACHE.set(departAuth); DEPART_AUTH_COUNT.remove(); } PriorityQueue queue = new PriorityQueue<>(); queue.peek(); } public void afterHandler() { DEPART_AUTH_CACHE.remove(); DEPART_AUTH_COUNT.remove(); } }
攔截器部分
- 獲取StatementHandler:通過(guò) invocation.getTarget() 獲取當(dāng)前被攔截的 StatementHandler 對(duì)象。由于 MyBatis 使用了代理模式,因此這里得到的是一個(gè)代理對(duì)象。接著,通過(guò)反射獲取其實(shí)際的代理對(duì)象 ,即最終執(zhí)行SQL的 StatementHandler 實(shí)例。
- 獲取MappedStatement:通過(guò)反射從 StatementHandler 中獲取 mappedStatement 對(duì)象。這個(gè)對(duì)象包含了關(guān)于即將執(zhí)行的SQL語(yǔ)句的所有信息,包括SQL類型、參數(shù)類型等。
- 判斷SQL類型:通過(guò)mappedStatement.getSqlCommandType()獲取SQL命令類型。如果類型是SELECT,說(shuō)明當(dāng)前是一個(gè)查詢操作,需要進(jìn)行權(quán)限檢查和處理。
- 獲取并解析原始SQL:通過(guò)delegate.getBoundSql()獲取BoundSql對(duì)象,它包含了實(shí)際執(zhí)行的SQL語(yǔ)句和相關(guān)的參數(shù)信息。然后使用CCJSqlParserUtil.parse()解析這個(gè)SQL語(yǔ)句,得到一個(gè)抽象語(yǔ)法樹(shù)(AST)。
- 修改SQL:從AST中提取出PlainSelect對(duì)象(即SELECT語(yǔ)句的主體)。然后調(diào)用自定義的buildWhereClause方法,根據(jù)departAuth中的權(quán)限信息構(gòu)建一個(gè)權(quán)限檢查條件,并將其注入到原始的SELECT語(yǔ)句中。這通常是通過(guò)在WHERE子句后追加額外的條件來(lái)實(shí)現(xiàn)的。
- 更新BoundSql對(duì)象:將修改后的SQL語(yǔ)句重新設(shè)置回BoundSql對(duì)象中,以便MyBatis在執(zhí)行時(shí)能夠使用修改后的SQL。
- 繼續(xù)執(zhí)行后續(xù)流程:在完成SQL修改后,調(diào)用invocation.proceed()繼續(xù)執(zhí)行MyBatis的后續(xù)處理流程,包括實(shí)際的SQL執(zhí)行、結(jié)果集處理等。
@Data @Slf4j @Component @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) }) public class DepartAuthMapperInterceptor implements Interceptor { private Properties properties; @Override public Object intercept(Invocation invocation) throws Throwable { DepartAuth departAuth = DepartAuthHandler.DEPART_AUTH_CACHE.get(); Integer count = DepartAuthHandler.DEPART_AUTH_COUNT.get(); if(departAuth != null && count == null) { // 說(shuō)明當(dāng)前線程已經(jīng)執(zhí)行了過(guò)濾條件,避免遞歸調(diào)用 DepartAuthHandler.DEPART_AUTH_COUNT.set(1); RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget(); //獲取StatementHandler構(gòu)造器 StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate"); // 通過(guò)反射獲取delegate父類BaseStatementHandler的mappedStatement屬性 MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement"); SqlCommandType commandType = mappedStatement.getSqlCommandType(); // 處理select對(duì)象 if (SqlCommandType.SELECT.equals(commandType)) { // 獲取原始sql BoundSql boundSql = delegate.getBoundSql(); Statement statement = CCJSqlParserUtil.parse(boundSql.getSql()); PlainSelect selectBody = (PlainSelect) ((Select) statement).getSelectBody(); log.info("原始 sql:{}", boundSql.getSql()); // 拼接新條件 buildWhereClause(selectBody, getSql(departAuth)); ReflectUtil.setFieldValue(boundSql, "sql", statement.toString()); } return invocation.proceed(); } return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { this.properties = properties; } /** * 添加查詢條件 * @param select * @param dataFilter * @throws JSQLParserException */ private void buildWhereClause(PlainSelect select, String dataFilter) throws JSQLParserException { if(StringUtils.isBlank(dataFilter)) { return; } if (select.getWhere() == null) { select.setWhere(CCJSqlParserUtil.parseCondExpression(dataFilter)); } else { AndExpression and = new AndExpression( CCJSqlParserUtil.parseCondExpression(dataFilter), select.getWhere()); select.setWhere(and); } } private String getSql(DepartAuth departAuth) { //結(jié)合自己的業(yè)務(wù),拼接相對(duì)應(yīng)的sql語(yǔ)句 } }
到此這篇關(guān)于Mybatis自定義攔截器實(shí)現(xiàn)權(quán)限功能的文章就介紹到這了,更多相關(guān)Mybatis 權(quán)限內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中MybatisX插件的簡(jiǎn)單使用教程(圖文)
MybatisX 是一款基于 IDEA 的快速開(kāi)發(fā)插件,方便在使用mybatis以及mybatis-plus開(kāi)始時(shí)簡(jiǎn)化繁瑣的重復(fù)操作,本文主要介紹了SpringBoot中MybatisX插件的簡(jiǎn)單使用教程,感興趣的可以了解一下2023-06-06使用Mybatis接收Integer參數(shù)的問(wèn)題
這篇文章主要介紹了使用Mybatis接收Integer參數(shù)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Java如何優(yōu)雅地關(guān)閉資源try-with-resource及其異常抑制
這篇文章主要介紹了Java如何優(yōu)雅地關(guān)閉資源try-with-resource及其異常抑制,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02詳解使用spring boot admin監(jiān)控spring cloud應(yīng)用程序
本篇文章主要介紹了詳解使用spring boot admin監(jiān)控spring cloud應(yīng)用程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11Java使用POI導(dǎo)出大數(shù)據(jù)量Excel的方法
今天需要寫一個(gè)導(dǎo)出的Excel的功能,但是發(fā)現(xiàn)當(dāng)數(shù)據(jù)量到3萬(wàn)條時(shí),列數(shù)在23列時(shí),內(nèi)存溢出,CPU使用100%,測(cè)試環(huán)境直接炸掉。小編給大家分享基于java使用POI導(dǎo)出大數(shù)據(jù)量Excel的方法,感興趣的朋友一起看看吧2019-11-11SpringApplicationRunListener監(jiān)聽(tīng)器源碼詳解
這篇文章主要介紹了SpringApplicationRunListener監(jiān)聽(tīng)器源碼詳解,springboot提供了兩個(gè)類SpringApplicationRunListeners、SpringApplicationRunListener(EventPublishingRunListener),spring框架還提供了一個(gè)ApplicationListener接口,需要的朋友可以參考下2023-11-11Java基礎(chǔ)教程之類數(shù)據(jù)與類方法
這篇文章主要介紹了Java基礎(chǔ)教程之類數(shù)據(jù)與類方法,本文是對(duì)類的深入探討,類數(shù)據(jù)指類的一些屬性、參數(shù)等,類方法就是類包含的功能方法,需要的朋友可以參考下2014-08-08PowerJob的AbstractScriptProcessor實(shí)現(xiàn)類工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob的AbstractScriptProcessor源碼流程解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01JDK SPI機(jī)制以及自定義SPI類加載問(wèn)題
這篇文章主要介紹了JDK SPI機(jī)制以及自定義SPI類加載問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11