Mybatis拦截器如何实现数据权限过滤

 更新时间:2024年12月31日 14:14:29   作者:CodeLemon·  
本文介绍了MyBatis拦截器的使用,通过实现Interceptor接口对SQL进行处理,实现数据权限过滤功能,通过在本地线程变量中存储数据权限相关信息,并在拦截器的intercept方法中进行SQL增强处理

背景

现在的项目负责人去年年底离职,导致前期规划的数据过滤功能一直没有去实现。

现在项目马上进入试运行时期了,需要根据用户数据权限的配置对数据进行过滤处理。

如果一个个手动去Mapper.xml文件中修改SQL工作量太大了,后面我考虑通过Mybatis对查询的SQL进行处理。

基础知识

Mybatis 拦截器介绍

Interceptor接口源码解析

package org.apache.ibatis.plugin;  
  
import java.util.Properties;  
  
public interface Interceptor {  
	
    Object intercept(Invocation invocation) throws Throwable;  

    default Object plugin(Object target) {  
        return Plugin.wrap(target, this);  
    }  
    
    default void setProperties(Properties properties) {  
    }  
}
  • intercept 方法
    这个方法是核心,当拦截到调用时会执行。Invocation 对象包含了被拦截方法的所有信息,包括方法本身、参数、目标对象等。在这个方法中,你可以做任何预处理或后处理逻辑,然后通过调用 invocation.proceed() 来继续执行原方法,或者直接返回自定义的结果。
  • plugin方法
    这个方法用于决定是否对某个对象应用拦截器。如果返回 target,则表示不进行拦截;如果返回一个新的对象,则表示将使用这个新对象替代原有的对象,通常是在这里返回一个代理对象。
  • setProperties 方法
    用于设置拦截器的属性,这些属性可以在 MyBatis 的配置文件中定义。

Signature 注解源码解析

@Documented  
@Retention(RetentionPolicy.RUNTIME)  
@Target({})  
public @interface Signature {  
	Class<?> type();

	String method();

	Class<?>[] args();
}
  • type:表示目标对象的类型,
  • method:表示要拦截的目标方法的名字。
  • args:表示目标方法的参数类型列表。不同的 @Signature 注解可能有不同的参数类型列表,这取决于具体的方法签名。

代码实战

实现一个类似与PageHelper的一个工具类,在本地线程变量中存储数据权限相关信息

public class DataAccessMethod {  
    private static final ThreadLocal<DataAccessType[]> ACCESS_LOCAL = new ThreadLocal<>();  
  
  
    public DataAccessMethod() {  
  
    }  
    public static void setLocalAccess(DataAccessType... accessType) {  
        ACCESS_LOCAL.set(accessType);  
    }  
  
    public static DataAccessType[] getLocalAccess() {  
        return ACCESS_LOCAL.get();  
    }  
  
    public static void clearLocalAccess() {  
        ACCESS_LOCAL.remove();  
    }  
  
    public static void accessData(DataAccessType... accessType) {  
        setLocalAccess(accessType);  
    }  
}

实现 Interceptor接口对SQL进行增强处理

@Intercepts({@Signature(  
        type = Executor.class,  
        method = "query",  
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}  
), @Signature(  
        type = Executor.class,  
        method = "query",  
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}  
)})  
@Slf4j  
@Component  
public class DataAccessFilterInterceptor implements Interceptor {  
  
  
    @Override  
    public Object intercept(Invocation invocation) throws Throwable {  
        if (skip()) {  
            return invocation.proceed();  
        }  
        MappedStatement statement = (MappedStatement) invocation.getArgs()[0];  
        Object parameter = invocation.getArgs()[1];  
        BoundSql boundSql = statement.getBoundSql(parameter);  
        String originalSql = boundSql.getSql();  
        Object parameterObject = boundSql.getParameterObject();  
        String sql = addTenantCondition(originalSql, "1", "222");  
        log.info("原SQL:{}, 数据权限替换后的SQL:{}", originalSql, sql);  
  
        BoundSql newBoundSql = new BoundSql(statement.getConfiguration(), sql, boundSql.getParameterMappings(), parameterObject);  
        MappedStatement newStatement = copyFromMappedStatement(statement, new BoundSqlSqlSource(newBoundSql));  
        invocation.getArgs()[0] = newStatement;  
        return invocation.proceed();  
    }  
  
    /**  
     * 判断是否跳过  
     *  
     * @return 是否跳过  
     */  
    private boolean skip() {  
        DataAccessType[] localAccess = DataAccessMethod.getLocalAccess();  
        return localAccess == null;  
    }  
  
    private String addTenantCondition(String originalSql, String depId, String alias) {  
        String field = "id";  
        if (StringUtils.hasText(alias)) {  
            field = alias + "." + field;  
        }  
  
        StringBuilder sb = new StringBuilder(originalSql.toLowerCase());  
        int index = sb.indexOf("where");  
        sb = new StringBuilder(originalSql);  
        if (index < 0) {  
            sb.append(" where ").append(field).append(" = ").append(depId);  
        } else {  
            sb.insert(index + 5, " " + field + " = " + depId + " and ");  
        }  
        return sb.toString();  
    }  
  
    private MappedStatement copyFromMappedStatement(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());  
        builder.timeout(ms.getTimeout());  
        builder.parameterMap(ms.getParameterMap());  
        builder.resultMaps(ms.getResultMaps());  
        builder.cache(ms.getCache());  
        builder.useCache(ms.isUseCache());  
        return builder.build();  
    }  
  
    public static class BoundSqlSqlSource implements SqlSource {  
        private final BoundSql boundSql;  
  
        public BoundSqlSqlSource(BoundSql boundSql) {  
            this.boundSql = boundSql;  
        }  
  
        @Override  
        public BoundSql getBoundSql(Object parameterObject) {  
            return boundSql;  
        }  
    }  
  
  
}

总结

以上代码只是示例,在实际生产中还需要考虑多表查询、SQL注入等相关问题。

这些仅为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • JDK1.7中HashMap的死循环问题及解决方案

    JDK1.7中HashMap的死循环问题及解决方案

    这篇文章主要为大家介绍了JDK1.7中HashMap的死循环问题及解决方案,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • 通过实例解析传统jar包引用方式

    通过实例解析传统jar包引用方式

    这篇文章主要介绍了通过实例解析传统jar包引用方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • Java使用建造者模式实现办理手机套餐功能详解

    Java使用建造者模式实现办理手机套餐功能详解

    这篇文章主要介绍了Java使用建造者模式实现办理手机套餐功能,较为详细的描述了建造者模式的概念、原理并结合实例形式分析了Java使用建造者模式实现的办理手机套餐功能具体步骤与相关操作注意事项,需要的朋友可以参考下
    2018-05-05
  • SpringMVC体系分层模式原理图解

    SpringMVC体系分层模式原理图解

    这篇文章主要介绍了SpringMVC体系分层模式原理图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • 集成Spring Redis缓存的实现

    集成Spring Redis缓存的实现

    今天小编就为大家分享一篇关于集成Spring Redis缓存的实现,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • SpringBoot整合FTP实现文件传输的步骤

    SpringBoot整合FTP实现文件传输的步骤

    这篇文章主要给大家介绍了SpringBoot整合FTP实现文件传输的步骤,文中的流程步骤和代码示例介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2023-11-11
  • SpringCloud全局过虑器GlobalFilter的用法小结

    SpringCloud全局过虑器GlobalFilter的用法小结

    这篇文章主要介绍了SpringCloud全局过虑器GlobalFilter的使用,全局过虑器使用非常广泛,比如验证是否登录,全局性的处理,黑名单或白名单的校验等,本文结合示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • Java VisualVM监控远程JVM(详解)

    Java VisualVM监控远程JVM(详解)

    下面小编就为大家带来一篇Java VisualVM监控远程JVM(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • Java中的WeakHashMap源码分析

    Java中的WeakHashMap源码分析

    这篇文章主要介绍了Java中的WeakHashMap源码分析,WeakHashMap可能平时使用的频率并不高,但是你可能听过WeakHashMap会进行自动回收吧,下面就对其原理进行分析,需要的朋友可以参考下
    2023-09-09
  • Java使用代码模拟高并发操作的示例

    Java使用代码模拟高并发操作的示例

    本篇文章主要介绍了Java使用代码模拟高并发操作的示例,Java通过代码模拟高并发可以以最快的方式发现我们系统中潜在的线程安全性问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05

最新评论