开源一个对业务代码零侵入的透明数据治理框架 —— 【sangsang】

基于 MyBatis 拦截器,通过动态 SQL 解析引擎,一处标注,全局生效。告别满项目注解标注,让数据治理回归简单。

一、背景与动机

在日常开发中,我们经常会遇到以下几类需求:

  • 敏感字段加密:手机号、身份证号、银行卡号需要入库加密、查询解密
  • 数据权限隔离:不同部门/租户的数据需要隔离,查询时需要拼接对应的 WHERE 条件
  • 异构数据库迁移:项目从 MySQL 迁移到达梦等国产数据库,很多SQL 语法不兼容,需要重写
  • 字段自动填充:创建时间、修改人、租户 ID 等字段需要在插入和更新时频繁维护

市面上的方案,要么只能兼容部分写法,要么要求在 Controller、Service、Mapper、入参响应层进行大量改动。一张业务表的加密功能,可能需要修改项目中十几处甚至几十处代码。

sangsang 】 的目标就是:对业务代码零侵入,一处标注,全局生效。


二、【sangsang】 是什么

【sangsang】 是一个基于 MyBatis 拦截器的透明数据治理框架,通过动态 SQL 解析引擎(基于 JSQLParser)在 SQL 执行前后自动改写 SQL,实现对业务代码零侵入的数据治理。

核心设计理念:

  • 零侵入 :所有 Controller、Service、Mapper、入参响应层都不需要改动,只在 @TableName 标注的实体类字段上做标注
  • 全局生效:一处标注,全项目应用。不需要在每一个 Mapper 方法上加注解
  • 灵活扩展:支持自定义策略和算法
  • 性能优化:LRU 缓存 SQL 解析结果,避免重复解析

技术栈:Java 1.8+、MyBatis、JSQLParser

开源协议:Apache 2.0


三、核心功能

3.1 字段加解密

最常见的场景:用户表中存储了手机号、身份证号等敏感信息,需要在入库时加密、查询时解密。

sangsang 提供两种加密模式:

模式 原理 特点
db 模式(推荐) 利用数据库自身的加解密函数,框架自动改写 SQL 支持密文模糊查询,支持列运算
pojo 模式 在 Java 层对 SQL 入参和响应进行加解密处理 算法选择性强,无数据库计算开销

db 模式工作原理:

sql 复制代码
-- 原始 SQL
SELECT user_name, phone FROM tb_user WHERE phone = ?
​
-- 框架自动改写后
SELECT
    数据库解密函数(phone) AS phone,
    user_name
FROM tb_user
WHERE phone = 数据库加密函数(?)

pojo模式工作原理:

diff 复制代码
-- 不改变执行的sql
-- 在mybatis执行sql前后,将入参和响应的字段调用java代码自动加解密处理

快速接入

Step 1:引入依赖

xml 复制代码
<dependency>
    <groupId>io.gitee.tired-of-the-water</groupId>
    <artifactId>sangsang-core</artifactId>
    <version>3.7.0-alpha</version>
</dependency>

Step 2:开启配置

ini 复制代码
# 配置实体类扫描路径(框架从此路径缓存需要加密的字段)
sangsang.scanEntityPackage[0]=com.sangsang.*.entity
​
# 加密模式:db 或 pojo
sangsang.encryptor.patternType=db
​
# 自定义密钥(可选,有默认值)
sangsang.encryptor.secretKey=TIREDTHEWATER

Step 3:创建 @TableName 实体类并标注字段

kotlin 复制代码
@TableName("tb_user") //标识这个是tb_user表的实体类
public class User {
    @FieldEncryptor//标识这个字段需要密文存储,采用默认的算法
    private String phone;
​
    @FieldEncryptor//标识这个字段需要密文存储,采用默认的算法
    private String idCard;
}

若项目未使用 MyBatis-Plus 没有实体类,在扫描路径下创建一个 @TableName 标注的类即可,仅保留需要标注的字段。

完成以上三步后,项目中所有涉及 tb_userphoneidCard 字段的 SQL 均会自动处理加解密,包括 INSERT、UPDATE、SELECT 以及 WHERE 条件中的各种场景。

核心亮点:密文模糊查询

传统加密方案下,加密后的数据无法进行 LIKE 查询,而 db 模式利用数据库函数可以在密文状态下直接完成模糊匹配,解决了加密场景下的一个核心痛点。

更多能力

  • 多算法共存 :不同字段可以使用不同的加密算法,通过 @FieldEncryptor(算法类.class) 指定
  • 自定义算法 :实现 FieldEncryptorStrategy 接口即可扩展
  • 分库分表支持 :通过 @ShardingTableEncryptor 注解 + ShardingTableStrategy 接口,避免为每张分表重复创建实体类

3.2 异构数据库语法转换

信创改造、数据库迁移是很多企业正在面临的课题。从 MySQL 迁移到达梦(DM)、从 Oracle 切换到 MySQL,SQL 语法的差异往往让人头疼。

sangsang 基于 JSQLParser 解析 SQL 语法树,自动将源数据库语法转换为目标数据库语法:

  • MySQL -> 达梦:已完成,有项目生产验证
  • Oracle -> MySQL:开发中,目前仅实现了 SELECT 常见语法的转换(3.7.0 分支)

转换效果示例:

sql 复制代码
-- 原始 MySQL 语法
SELECT `user_name`, `phone` FROM `tb_user` WHERE `user_name` LIKE 'admin%'
SELECT DATE_FORMAT(create_time, '%Y-%m-%d') FROM tb_user
​
-- 自动转换为达梦语法
SELECT "user_name", "phone" FROM "tb_user" WHERE "user_name" LIKE 'admin%'
SELECT TO_CHAR(create_time, 'YYYY-MM-DD') FROM tb_user

框架自动处理:反引号转双引号、日期函数转换、分页语法转换、标识符转换等。

快速接入

Step 1:引入依赖

xml 复制代码
<dependency>
    <groupId>io.gitee.tired-of-the-water</groupId>
    <artifactId>sangsang-core</artifactId>
    <version>3.7.0-alpha</version>
</dependency>

Step 2:开启配置

ini 复制代码
# 选择语法转换模式
sangsang.transformation.patternType=mysql2dm

Step 3:切换数据库驱动和连接地址

复制代码

完成以上三步,所有 MySQL 语法的 SQL 会自动转换为达梦语法执行,业务代码和 Mapper XML 中的 SQL 完全不需要修改。

更多能力

  • 自定义语法转换器 :实现 ColumnTransformFunctionTransform 等接口即可扩展,参考 com.sangsang.transformation.mysql2dm 包下的实现
  • 扩展其他数据库:框架预留了扩展口,可借助大模型快速实现其他异构数据库的转换(如 PostgreSQL -> MySQL 等)

3.3 数据权限隔离

多租户系统、多部门系统中,不同用户只能看到属于自己的数据。传统方案需要在每一个查询 SQL 中手动拼接权限条件,维护成本极高。

sangsang 提供了零侵入的数据权限隔离方案,效果示例:

sql 复制代码
-- 原始 SQL
SELECT * FROM tb_order WHERE phone = 'xxx' OR name = 'yyy'
​
-- PC 端(按组织隔离)自动改写后
SELECT * FROM tb_order
WHERE (phone = 'xxx' OR name = 'yyy')
  AND org_id = 当前登录用户的组织id
​
-- APP 端(按个人隔离)自动改写后
SELECT * FROM tb_order
WHERE (phone = 'xxx' OR name = 'yyy')
  AND id = 当前登录用户的id

快速接入

Step 1:引入依赖

xml 复制代码
<dependency>
    <groupId>io.gitee.tired-of-the-water</groupId>
    <artifactId>sangsang-core</artifactId>
    <version>3.7.0-alpha</version>
</dependency>

Step 2:开启配置

ini 复制代码
# 配置实体类扫描路径
sangsang.scanEntityPackage[0]=com.sangsang.*.entity
​
# 开启数据权限隔离
sangsang.isolation.enable=true

Step 3:实现数据隔离策略

typescript 复制代码
@Component
public class OrgIsolationStrategy implements DataIsolationStrategy<Long> {
​
    @Override
    public String getIsolationField(String tableName) {
        // 返回隔离字段名,一般从 ThreadLocal 获取当前用户信息后决定
        return "org_id";
    }
​
    @Override
    public IsolationRelationEnum getIsolationRelation(String tableName) {
        // 支持 "="、"IN"、"LIKE" 三种模式
        return IsolationRelationEnum.EQUALS;
    }
​
    @Override
    public Long getIsolationData(String tableName) {
        // 从 ThreadLocal 获取当前登录用户的隔离字段值
        return UserContext.getOrgId();
    }
}

Step 4:创建 @TableName 实体类并标注

kotlin 复制代码
@TableName("tb_order") //标识这个是tb_order表的实体类
@DataIsolation(OrgIsolationStrategy.class)  // 指定隔离策略
public class Order {
    private Long id;
    private String orderNo;
    private Long orgId;  // 实体类中必须包含隔离字段
}

支持的隔离模式

模式 说明 示例
= 精确匹配 org_id = 123
IN 多值匹配 org_id IN (1, 2, 3)
LIKE 模糊前缀匹配 org_seq LIKE '123%'

更多能力

  • 复杂场景策略:PC 端按组织隔离,APP 端按个人隔离,在同一个 Strategy 中根据用户类型动态返回不同隔离字段和模式
  • DML 语句隔离 :默认仅对 SELECT 生效,配置 sangsang.isolation.supportDML=true 可对增删改也生效
  • IN 分段处理:自动分段避免 Oracle 1000 条 IN 限制
  • 禁用隔离 :通过 @ForbidIsolation 注解在特定 Mapper 方法或 Service 方法上跳过隔离

3.4 数据变更字段自动填充

几乎每个项目都有"创建时间"、"修改时间"、"操作人"这类需要自动维护的字段。sangsang 让你不用再在每个 Service 中手动设置这些字段。

效果示例:

sql 复制代码
-- 原始 SQL
INSERT INTO tb_order(order_no, amount) VALUES ('ORD001', 1000.00)
​
-- 自动填充后
INSERT INTO tb_order(create_time, create_by, order_no, amount)
VALUES ('2025-01-01 12:00:00', 'admin', 'ORD001', 1000.00)

快速接入

Step 1:引入依赖

xml 复制代码
<dependency>
    <groupId>io.gitee.tired-of-the-water</groupId>
    <artifactId>sangsang-core</artifactId>
    <version>3.7.0-alpha</version>
</dependency>

Step 2:开启配置

ini 复制代码
# 配置实体类扫描路径
sangsang.scanEntityPackage[0]=com.sangsang.*.entity
​
# 开启数据变更时自动填充
sangsang.fieldDefault.enable=true

Step 3:实现默认值策略

typescript 复制代码
@Component
public class CreateTimeStrategy implements FieldDefaultStrategy<LocalDateTime> {
​
    @Override
    public boolean whetherToHandle(SqlCommandEnum sqlCommandEnum) {
        // 指定在 INSERT 时生效
        return SqlCommandEnum.INSERT.equals(sqlCommandEnum);
    }
​
    @Override
    public LocalDateTime getDefaultValue() {
        return LocalDateTime.now();
    }
}

Step 4:在 @TableName 实体类中标注字段

kotlin 复制代码
@TableName("tb_order") //标识这个是tb_order表的实体类
public class Order {
​
    @FieldDefault(CreateTimeStrategy.class)     // 新增时自动填充创建时间
    private LocalDateTime createTime;
​
    @FieldDefault(CreateByStrategy.class)       // 新增时自动填充创建人
    private String createBy;
​
    @FieldDefault(UpdateTimeStrategy.class)     // 新增和修改时自动填充修改时间
    private LocalDateTime updateTime;
​
    @FieldDefault(UpdateByStrategy.class)       // 新增和修改时自动填充修改人
    private String updateBy;
}

支持在 @TableName 实体类中统一标注,子类自动继承。也可用于租户 ID、组织 ID 等字段的自动维护。

更多能力

  • 强制覆盖@FieldDefault(value = CreateTimeStrategy.class, mandatoryOverride = true) 强制使用策略值覆盖 SQL 中已有的值
  • 配合数据权限隔离 :结合 @DataIsolation 注解,可实现租户数据自动写入 + 查询隔离的完整方案

3.5 数据库字段查询脱敏

注意:此功能与框架"一处标注,全局生效"理念不一致(需要在每个查询处标注),后续版本考虑移除。

对于查询返回的敏感数据,框架支持在 SQL 执行后对结果集进行脱敏处理。与传统序列化脱敏不同,【sangsang 】的脱敏方案可以获取完整响应对象,天然支持业务差异化脱敏

效果示例:

css 复制代码
-- 查询结果
| 订单号 | 订单类型 | 物料名称     |
|--------|----------|-------------|
| 001     | A        | 敏感物料名字 |
| 002     | B        | 普通物料名字 |
​
-- 脱敏后(A 类型订单自动脱敏)
| 订单号 | 订单类型 | 物料名称       |
|--------|----------|--------------|
| 001     | A        | ***脱敏物料*** |
| 002     | B        | 普通物料名字   |

快速接入

Step 1:开启配置

ini 复制代码
sangsang.desensitize.enable=true

Step 2:实现脱敏策略

typescript 复制代码
public class BusinessDesensitizeStrategy implements DesensitizeStrategy {
​
    @Override
    public String desensitize(String s, Object o) {
        // s: 待脱敏的字符串
        // o: 整个返回对象,可获取业务信息进行差异化脱敏
        if (o instanceof OrderVo && "A".equals(((OrderVo) o).getOrderType())) {
            return s.substring(0, 2) + "***";
        }
        return s;  // 其他情况不脱敏
    }
}

Step 3:字段标注

less 复制代码
// 响应是实体类 - 在 SQL 返回类字段上标注
public class OrderVo {
    private String orderType;
​
    @FieldDesensitize(BusinessDesensitizeStrategy.class)
    private String materialName;
}
​
// 响应是 String - 在 Mapper 方法上标注
@MapperDesensitize(@FieldDesensitize(BusinessDesensitizeStrategy.class))
List<String> getOrderNames(String orderId);
​
// 响应是 Map - 指定字段名脱敏
@MapperDesensitize({
    @FieldDesensitize(value = BusinessDesensitizeStrategy.class, fieldName = "material_name"),
    @FieldDesensitize(value = PhoneDesensitizeStrategy.class, fieldName = "phone")
})
Map<String, Object> getOrderDetail(String orderId);

四、与其他方案的对比

对比项 sangsang 常见注解方案 AOP/拦截器方案
业务代码侵入 零侵入 需在多处加注解 需修改 Service 层
标注范围 实体类字段(一处标注) 每个 Mapper/方法 每个 Service 方法
SQL 改写能力 支持复杂 SQL 改写 有限 不支持
密文模糊查询 支持(db 模式) 通常不支持 不支持
异构数据库转换 支持 不支持 不支持
维护成本

五、快速体验

项目提供了在线演示平台,可以直观地看到框架改写前后的 SQL:

功能 演示地址
DB 模式加解密 http://43.133.199.2:8001/
数据权限自动隔离 http://43.133.199.2:8003/
异构数据库语法转换 http://43.133.199.2:8004/

也可以拉取源码本地运行 demo 模块,再使用演示平台,有个更为直观的感受

项目提供了详细的调试指南和测试用例,方便深入了解


六、适用场景

【sangsang】 适合以下场景:

  • 中小型项目:并发数据量不是很大,不介意拦截器自动处理的性能损耗
  • 敏感数据加密:金融、医疗、政务等对数据安全有要求的行业
  • 信创改造:从 MySQL/Oracle 迁移到国产数据库(达梦等)
  • 多租户 SaaS:需要数据权限自动隔离的 SaaS 平台
  • 遗留系统改造:在不修改大量业务代码的前提下增加数据治理能力
  • 新项目启动:从一开始就建立规范的数据治理体系

七、总结

【sangsang】 的核心价值在于 "零侵入" 三个字。它不是在业务代码中到处添加注解和逻辑,而是在 MyBatis 层面通过 SQL 改写来透明地完成数据治理工作。这种设计方式带来的好处是显而易见的:

  1. 接入成本极低:只需要引入依赖 + 开启配置+标注注解
  2. 维护成本低:不需要在项目中到处维护注解和逻辑
  3. 风险可控:不触碰业务代码,引入风险小
  4. 功能全面:加密、权限隔离、数据库迁移、自动填充,一个框架覆盖多个场景

如果你正在寻找一个轻量、零侵入、开箱即用的数据治理框架,不妨试试 【sangsang】


项目地址

git中有详细的使用文档和本地调试文档,方便你快速了解项目

欢迎 Star、提 Issue、交流讨论。如果觉得有帮助,点个 Star 是对作者最大的肯定。

技术交流:QQ 群 1072901252(群文件有 MySQL SM4 国密算法扩展包,可以让mysql也用上SM4加密算法)

相关推荐
Nyarlathotep01131 小时前
JUC工具(3):StampedLock的基础和原理
java·后端
呱牛do it1 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 7)
java·vue
NE_STOP1 小时前
Redis--SDS字符串与集合的底层实现原理
java
直奔標竿2 小时前
Java开发者AI转型第二十二课!Spring AI 个人知识库实战(一)——架构搭建与核心契约落地
java·人工智能·后端·spring·架构
身如柳絮随风扬2 小时前
深入理解Java IO与NIO的区别:从BIO到NIO的演进
java·nio
A-Jie-Y2 小时前
JAVA设计模式-抽象工厂模式
java·设计模式
@insist1232 小时前
信息安全工程师-密码学专题(下):构建可信网络空间的核心机制
java·大数据·密码学·软考·信息安全工程师·软件水平考试
摇滚侠2 小时前
Java 零基础全套视频教程,面向对象(高级),笔记 105-120
java·开发语言·笔记
叶落阁主2 小时前
Spring Boot 4 实战:Jackson 2.x 升级到 3.x 踩坑全记录
java·后端·架构