告别满项目打注解,mybatis数据隔离其实很简单

106 阅读5分钟

一、项目中绕不开的话题,“数据隔离”

在做管理系统时,一定绕不开“数据隔离”这个场景

  • 租户A只能看到A产生的数据
  • 订单数据,后台管理系统能看自己组织下的所有订单,但是到了C端,每个人只能看到自己的订单

二、常规做法

  • 自己sql中去拼筛选条件

    先把如果获取当前登录人的组织,租户等封装成一个工具类

    在每个需要的地方执行的sql中硬拼接上 where org_id = xxx 之类的条件

    难顶

  • 封装一个注解,每个需要的接口标注一下

    参考比较知名某框架,封装一个注解,在每个接口处打一个标记

    切面逻辑可以借助SPEL表达式,从中获取到入参的一些字段值用于 where后面拼接的值

    切面逻辑中也可以借助ThreadLocal,从当前登录人中获取到某些值,拼接到执行的where条件后

第二个比第一个方法要稍好一点,但是每个接口打一个注解,和每个sql执行前调用一个工具类,细想一下,好像差别不是很大

三、了解一下 field-encryptor

🚀 Field-Encryptor

一款专为 Java 开发者打造的透明数据中间层框架。

基于Mybatis拦截器。它的核心原则只有一条:让业务代码完全感知不到它的存在。

🚀理念

数据隔离应该是一个很单纯的场景,同一种数据(对应数据库的一张表)在全局的数据隔离规则都是一样的

比如说订单

  • 在管理端,根据同组织的人能互相看,上级可以看下级全部的订单
  • 在C端,每个用户只能看自己的订单

这个规则应该是适用于全局的,而不是需要在每个接口都要去实现一套这个逻辑

从这个角度出发,我们其实也不需要每个接口,每个sql进行标注了,仅需全局配置一次即可

🚀原理

创建业务表的人,配置好这张表是根据什么字段,怎么数据隔离的,配置好策略后

它在 SQL 发送到数据库之前,通过解析 AST(抽象语法树),自动在 WHERE 子句中织入隔离条件

后续使用这张表的开发人员不用关心“数据隔离”这个概念

四、快速体验一下

🚀 引入pom (这里暂时使用3.6.1-alpha版本,最新版本提测阶段,尚未发布)

 <dependency>  
    <groupId>io.gitee.tired-of-the-water</groupId>  
    <artifactId>encryptor-core</artifactId>  
    <version>3.6.1-alpha</version> 
</dependency>

🚀增加配置

配置的实体类路径,框架会去扫描指定的路径,去获取自己想要的配置信息

#配置@TableName标注的实体类路径
field.scanEntityPackage[0]=com.sangsang.*.entity
#开启数据隔离
field.isolation.enable=true

🚀 自定义隔离策略

这里举个简单的栗子,tb_test表根据当前登录人的org_id进行数据隔离

package xxx.strategy;
​
import com.sangsang.domain.enums.IsolationRelationEnum;
import com.sangsang.domain.strategy.isolation.DataIsolationStrategy;
import org.springframework.stereotype.Component;
​
/**
 * @author 看腻了那片水
 * @date 2025/7/4 17:12
 */
@Component
public class TIsolationBeanStrategy implements DataIsolationStrategy<Long> {
    //返回需要进行数据隔离的表字段名字 
    //这里入参的tableName可以获取到当前是哪张表,一般是表名
    //一般项目会将登录用户存threadlocal中,这里可以取出来,根据不同的登录用户选择不同的字段隔离
    @Override
    public String getIsolationField(String tableName) {
        return "org_id";
    }
​
    //目前支持 "="  "in"  "like 'xxx%'" 三种模式
    @Override
    public IsolationRelationEnum getIsolationRelation(String tableName) {
        return IsolationRelationEnum.EQUALS;
    }
​
    //从当前登录用户的theadlocal中获取到用于数据隔离的字段即可,比如当前登录用户组织id之类的
    @Override
    public Long getIsolationData(String tableName) {
    //根据自己业务从ThreadLocal中获取
        return 777777777L;
    }
}

🚀 标注

/**
 * @author 看腻了那片水
 * @date 2025/7/2 14:45
 */
//使用@TableName注解表示这个是一个实体类
@TableName("tb_test")
@Data
//指定tb_test这张表隔离策略用TIsolationBeanStrategy
@DataIsolation(TIsolationBeanStrategy.class)
public class TestEntity extends BaseEntity {
​
    //注意:隔离策略中出现的字段必须要求出现在这个实体类中
    @TableField("org_id")
    private Long orgId;
​
}

🚀验证

至此,关于tb_test的数据隔离你已经全局做好了,关于tb_test的所有select都会自动拼接上筛选条件

五、复杂场景

订单的场景

  • 管理端,可以看同组织的订单,上级组织可以看下级组织所有的
  • C端,只能看自己的单据

我们对tb_oder表的权限表结构设计如下

  • user_id 订单所属的C端用户id

  • org_seq 订单所属的组织权限路径(通过下面的结构可以达到查询下级所有组织的场景)

    • 一级组织的权限路径是aaa
    • 一级组织下所属的二级组织路径就是aaa-bbb
    • 二级组织下属的三级组织路径就是aaa-bbb-ccc
package xxx.strategy;
​
import com.sangsang.domain.enums.IsolationRelationEnum;
import com.sangsang.domain.strategy.isolation.DataIsolationStrategy;
import org.springframework.stereotype.Component;
​
/**
 * @author 看腻了那片水
 * @date 2025/7/4 17:12
 */
@Component
public class TIsolationBeanStrategy implements DataIsolationStrategy<Long> {
   
    @Override
    public String getIsolationField(String tableName) {
        //从ThreadLocal中判断当前登录数据管理端还是C端
        //管理端就返回 org_seq   C 端就返回 user_id
        return xxx;
    }
​
    //目前支持 "="  "in"  "like 'xxx%'" 三种模式
    @Override
    public IsolationRelationEnum getIsolationRelation(String tableName) {
        //管理端就返回 Like   C端就返回 equals
        return IsolationRelationEnum.EQUALS;
    }
​
    //从当前登录用户的theadlocal中获取到用于数据隔离的字段即可,比如当前登录用户组织id之类的
    @Override
    public Long getIsolationData(String tableName) {
        ////管理端就返回当前登录人的org_seq   C端就返回 当前登录人的user_id
        return 777777777L;
    }
}

某些极端场景

某些对权限管理特别严的项目,会要求表根据什么字段隔离支持可配置

这不巧了吗,策略里面动态取值就行了

更多进阶应用

更多进阶使用方法详见gitee

六、 写在最后

Field-Encryptor 的目标不是做一个大而全的中间件,而是精准解决开发者在数据治理中的每一个“痛点”。 目前项目已在 Gitee 开源,欢迎各位大佬来点个 Star 鼓励!如果你在使用中遇到任何等保或合规的刁钻需求,欢迎在评论区或 QQ 群交流,我们一起把它做得更好。