Mybatis使用拦截器实现数据权限

文章介绍了Java自定义注解的关键元注解@Documented、@Target和@Retention的用途和生命周期选择。@Documented用于影响JavaDoc生成,@Target定义注解使用范围,@Retention控制注解的生命周期。此外,文章还展示了如何在Mybatis中实现一个拦截器PermissionInterceptor,用于拦截SQL执行,动态增强SQL语句。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.自定义注解

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {
	
}

@Documented@Deprecated注解长得有点像,@Deprecated是用来标注某个类或者方法不建议再继续使用,@Documented只能用在注解上,如果一个注解@B,被@Documented标注,那么被@B修饰的类,生成文档时,会显示@B。如果@B没有被@Documented标注,最终生成的文档中就不会显示@B。这里的生成文档指的JavaDoc文档!

@Deprecated注解基本上所有框架自定义的注解都会添加,所谓javadoc其实就是JDK给我们提供的一个生成文档的工具!

@Target注解自JDK1.5之后就有了,其作用是定义在注解的上方,表明其注解的作用范围。

ElementType枚举值作用范围
TYPE接口、类、枚举
FIELD字段、枚举的常量
METHOD方法
PARAMETER方法参数
CONSTRUCTOR构造函数
LOCAL_VARIABLE局部变量
ANNOTATION_TYPE注解
PACKAGE

@Retention是用来修饰注解的,注解的注解,也称为元注解。@Retention修饰注解,用来表示注解的生命周期,生命周期的长短取决于@Retention的属性RetentionPolicy指定的值,例如@Retention(RetentionPolicy.RUNTIME)

取值描述作用范围使用场景
RetentionPolicy.SOURCE表示注解只保留在源文件,当java文件编译成class文件,就会消失源文件只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings
RetentionPolicy.CLASS注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期class文件(默认)要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife)
RetentionPolicy.RUNTIME注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在运行时也存在需要在运行时去动态获取注解信息

生命周期选择:

首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要 在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife) ,就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和@SuppressWarnings,则 可选用 SOURCE 注解。

2.实现拦截器

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PermissionInterceptor implements Interceptor {

    private static final Log log = LogFactory.get(PermissionInterceptor.class);

    /**
     * 拦截sql
     *
     * @param invocation q
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        // 通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性;:MetaObject是Mybatis提供的一个用于方便、
        // 优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                new DefaultReflectorFactory());

        // 先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

        // id为执行的mapper方法的全路径名,如com.cq.UserMapper.insertUser, 便于后续使用反射
        String id = mappedStatement.getId();

        Permission permission = null;
        // 通过类全路径获取Class对象
        Class<?> classType = Class.forName(id.substring(0, id.lastIndexOf(".")));
        // 获取当前所拦截的方法名称
        String mName = id.substring(id.lastIndexOf(".") + 1);
        // 遍历类中所有方法名称,并if匹配上当前所拦截的方法
        for (Method method : classType.getDeclaredMethods()) {
            if (mName.equals(method.getName())) {
                // 判断方法上是否带有自定义@InterceptAnnotation注解
                permission = method.getAnnotation(Permission.class);
            }
        }

        if (permission == null) {
            return invocation.proceed();
        }

        BoundSql boundSql = statementHandler.getBoundSql();
        // 获取到原始sql语句
        String sql = boundSql.getSql().toLowerCase();
        log.info("SQL:{}", sql);


        // 增强sql
        // 通过反射,拦截方法上带有自定义@InterceptAnnotation注解的方法,并增强sql
        String mSql = sqlAnnotationEnhance(permission, sql);

        //通过反射修改sql语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, mSql);
        // 打印:增强后的SQL:select * from scenario_storage limit 2
        log.info("增强后的SQL:{}", mSql);
        return invocation.proceed();
    }


    /**
     * 通过反射,拦截方法上带有自定义@InterceptAnnotation注解的方法,并增强sql
     *
     * @param permission 自定义注解
     * @param sql        所执行的sql语句
     */
    private String sqlAnnotationEnhance( Permission permission, String sql) {
        
    	//todo 增强sql

        return sql;
    }

    
    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

### MyBatis-Plus 拦截器实现数据权限控制 #### 使用自定义注解与拦截器相结合的方式 为了实现MyBatis Plus中对查询结果的数据权限过滤,可以采用自定义注解配合拦截器的方式来完成。这种方式不仅能够简化开发流程,还能有效减少SQL解析的风险。 当开发者希望在不修改原有业务逻辑的前提下增加数据访问层的安全机制时,可以通过创建特定场景下的注解来标记需要应用此安全措施的服务接口或方法[^2]。例如: ```java @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface DataPermission { String value(); } ``` 接着,在配置类中注册一个实现了`Interceptor`接口的组件,并重写其内的`intercept()`函数用于实际处理逻辑。在此过程中,可以从上下文中获取由上述自定义注解传递过来的信息并据此调整最终发出给数据库引擎执行的SQL语句[^1]。 对于具体的应用实例来说,则是在目标Service层的方法上添加之前定义好的注解,指定相应的条件表达式或者其他必要的参数。之后每当触发带有该注解标注的操作时,系统便会自动调用对应的拦截器来进行额外校验工作,从而达到预期效果。 下面是一个简单的例子展示如何设置这样的环境以及编写相关代码片段: ```java // 定义数据权限注解 import java.lang.annotation.*; @Documented @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface DataScope {} // 创建拦截器类 @Component @Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})) class DynamicDataSourceInterceptor implements Interceptor { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class); @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms = (MappedStatement)invocation.getArgs()[0]; Object parameterObject = invocation.getArgs()[1]; Method currentMethod = ReflectUtil.getMethodByClass(ms.getId(), parameterObject.getClass()); if(null != currentMethod && currentMethod.isAnnotationPresent(DataScope.class)){ // 获取当前登录用户的部门id或其他信息 Long deptId = SecurityContextHolder.getContext().getAuthentication().getName(); BoundSql boundSql = ms.getBoundSql(parameterObject); String sql = boundSql.getSql(); // 动态拼接sql,这里只是简单示范,实际情况可能更复杂 StringBuilder newSqlBuilder = new StringBuilder(sql.length() + 100).append(sql); // 假设表中有dept_id字段用来存储所属部门编号 int index = newSqlBuilder.lastIndexOf("WHERE"); if(index >= 0){ newSqlBuilder.insert(index + 5," AND t.dept_id = #{deptId}"); }else{ newSqlBuilder.append(" WHERE t.dept_id = #{deptId}"); } // 更新boundSql对象中的原始sql字符串和其他属性... MetaObject metaObject = SystemMetaObject.forObject(boundSql); metaObject.setValue("sql",newSqlBuilder.toString()); // 将新的参数加入到原参数列表里以便后续使用 Map<String,Object> paramsMap = (Map<String,Object>)parameterObject; paramsMap.put("deptId",deptId); } return invocation.proceed(); } } // 应用至服务端点处 @Service public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements IUserService { @DataScope @Override public List<UserEntity> listUsers(){ return baseMapper.selectList(new QueryWrapper<>()); } } ``` 以上就是关于如何利用MyBatis Plus自带的拦截器特性结合自定义注解达成较为灵活高效的数据权限管理方案的一个基本介绍和实践案例说明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值