SpringBoot3.4.3基于JSqlParser和MyBatis实现自定义数据权限
SpringBoot3.4.3基于JSqlParser和MyBatis实现自定义数据权限

本专栏前一篇文章SpringBoot3.4.3基于Resilience4j实现服务容错构建高可用系统。今天分享SpringBoot3.4.3基于JSqlParser和MyBatis实现自定义数据权限功能。通过在数据持久层对SQL进行动态修改,添加自定义数据权限过滤条件实现数据权限的控制,动态实现不同用户和机构间的数据隔离。
完整代码在文章最后,如果觉得本篇文章对你有用,记得点赞、关注、收藏哦。你的支持是我持续更新的动力!
文章最后可以加入免费的Java&AI技术和支付系统沟通社群,一起探讨Java/你的产品如何与AI结合,请按照要求加入。在群中可以聊开发、系统设计、架构、行业趋势、AI等等话题
SpringBoot3专栏软件环境
- JDK17.0.12
- SpringBoot3.4.3
- IDEA2024.3.4
- JSqlParser4.6
- mybatis-spring-boot-starter3.0.3
我们先看本篇文章对应的项目结构,请看下图

1 什么是JSqlParser
JSqlParser 是一个用于解析和操作 SQL 语句的 Java 库。它可以将 SQL 语句解析成抽象语法树(AST),从而允许开发者对 SQL 语句进行各种操作和分析。JSqlParser 支持多种 SQL 方言,包括 MySQL、PostgreSQL、Oracle、SQL Server 等。
JSqlParser 的主要功能包括:
- 解析 SQL 语句:将 SQL 语句解析成抽象语法树,方便开发者理解和操作 SQL 语句的结构。
- 生成 SQL 语句:根据抽象语法树生成 SQL 语句,方便开发者动态生成 SQL 语句。
- SQL 语句的验证:验证 SQL 语句的语法正确性。
- SQL 语句的修改:对 SQL 语句进行修改,例如添加、删除或修改表、列、条件等。
- SQL 语句的格式化:对 SQL 语句进行格式化,使其更易读。
JSqlParser 的应用场景包括:
- SQL 语句的生成和修改:在动态控制数据权限、数据迁移、数据同步、数据报表等场景中,需要动态生成和修改 SQL 语句。
- SQL 语句的优化:对 SQL 语句进行优化,提高查询性能。
- SQL 语句的审计和监控:对 SQL 语句进行审计和监控,及时发现和解决问题。
JSqlParser 是一个功能强大的 SQL 解析和操作库,可以帮助开发者更高效地处理 SQL 语句。
2 项目搭建
2.1 pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.itbeien</groupId>
<artifactId>springboot3-labs-master</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>springboot-jsqlparser</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mysql-connector-java-version>8.0.33</mysql-connector-java-version>
<jsqlparser-version>4.6</jsqlparser-version>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java-version}</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>${jsqlparser-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.2 动态添加数据权限条件
package cn.itbeien.jsqlparser.plugin;
import io.micrometer.common.util.StringUtils;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.RowConstructor;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.values.ValuesStatement;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author itbeien
* 项目网站:https://www.itbeien.cn
* 公众号:贝恩聊架构
* 全网同名,欢迎小伙伴们关注
* Java/AI学习社群
* Copyright© 2025 itbeien
*/
@Component
@Intercepts(
{
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
}
)
public class DataAuthInterceptor implements Interceptor {
/**
* 动态条件
* 该条件在实际的应用中通过参数动态传入,然后在代码中动态获取
*/
private static final String createUser ="admin";
/**
* 动态条件
* 该条件在实际的应用中通过参数动态传入,然后在代码中动态获取
*/
private static final List<Map<String,String>> columns = new ArrayList<Map<String,String>>();
static {
Map<String,String> values = new HashMap<>();
values.put("create_user","admin");
values.put("name","itbeien");
columns.add(values);
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
//确保只有拦截的目标对象是 StatementHandler 类型时才执行特定逻辑
if (target instanceof StatementHandler) {
StatementHandler statementHandler = (StatementHandler) target;
// 获取 BoundSql 对象,包含原始 SQL 语句
BoundSql boundSql = statementHandler.getBoundSql();
String originalSql = boundSql.getSql();
String newSql = setColumnToStatement(originalSql);
// 使用MetaObject对象将新的SQL语句设置到BoundSql对象中
MetaObject metaObject = SystemMetaObject.forObject(boundSql);
metaObject.setValue("sql", newSql);
}
// 执行SQL
return invocation.proceed();
}
private String setColumnToStatement(String originalSql) {
net.sf.jsqlparser.statement.Statement statement;
try {
statement = CCJSqlParserUtil.parse(originalSql);
} catch (JSQLParserException e) {
throw new RuntimeException("setColumnToStatement-SQL语句解析异常:"+originalSql);
}
if (statement instanceof Select) {
Select select = (Select) statement;
PlainSelect selectBody = select.getSelectBody(PlainSelect.class);
if (selectBody.getFromItem() instanceof Table) {
Expression newWhereExpression;
if (selectBody.getJoins() == null || selectBody.getJoins().isEmpty()) {
newWhereExpression = setColumnToWhereExpression(selectBody.getWhere(), null);
} else {
// 如果是多表关联查询,在关联查询中新增每个表的数据权限字段条件
newWhereExpression = multipleTableJoinWhereExpression(selectBody);
}
// 将新的where设置到Select中
selectBody.setWhere(newWhereExpression);
} else if (selectBody.getFromItem() instanceof SubSelect) {
// 如果是子查询,在子查询中新增数据权限字段条件
// 当前方法只能处理单层子查询,如果有多层级的子查询的场景需要通过递归设置数据权限字段
SubSelect subSelect = (SubSelect) selectBody.getFromItem();
PlainSelect subSelectBody = subSelect.getSelectBody(PlainSelect.class);
Expression newWhereExpression = setColumnToWhereExpression(subSelectBody.getWhere(), null);
subSelectBody.setWhere(newWhereExpression);
}
// 获得修改后的语句
return select.toString();
} else if (statement instanceof Insert) {
Insert insert = (Insert) statement;
setColumnToInsert(insert);
return insert.toString();
} else if (statement instanceof Update) {
Update update = (Update) statement;
Expression newWhereExpression = setColumnToWhereExpression(update.getWhere(),null);
// 将新的where设置到Update中
update.setWhere(newWhereExpression);
return update.toString();
} else if (statement instanceof Delete) {
Delete delete = (Delete) statement;
Expression newWhereExpression = setColumnToWhereExpression(delete.getWhere(),null);
// 将新的where设置到delete中
delete.setWhere(newWhereExpression);
return delete.toString();
}
return originalSql;
}
/**
* 将需要实现数据权限控制的字段加入到SQL的Where语法树中
* @param whereExpression SQL的Where语法树
* @param alias 表别名
* @return 新的SQL Where语法树
*/
private Expression setColumnToWhereExpression(Expression whereExpression, String alias) {
// 添加SQL语法树的一个where分支,并添加指定条件
for(Map<String,String> column : columns){
for(Map.Entry<String, String> entry : column.entrySet()){
EqualsTo columnEquals = new EqualsTo();
columnEquals.setLeftExpression(new Column(StringUtils.isNotBlank(alias) ? String.format("%s."+entry.getKey(), alias) : entry.getKey()));
columnEquals.setRightExpression(new StringValue(entry.getValue()));
if (whereExpression == null){
whereExpression = columnEquals;
} else {
AndExpression andExpression = new AndExpression();
// 将新的where条件加入到原where条件的右分支树
andExpression.setRightExpression(columnEquals);
andExpression.setLeftExpression(whereExpression);
whereExpression = andExpression;
}
}
}
return whereExpression;
}
/**
* 多表关联查询时,给关联的所有表加入数据权限控制条件
* @param selectBody select语法树
* @return 新的SQL Where语法树
*/
private Expression multipleTableJoinWhereExpression(PlainSelect selectBody){
Table mainTable = selectBody.getFromItem(Table.class);
String mainTableAlias = mainTable.getAlias().getName();
// 将 t1.create_user = createUser 的条件添加到where中
Expression newWhereExpression = setColumnToWhereExpression(selectBody.getWhere(), mainTableAlias);
List<Join> joins = selectBody.getJoins();
for (Join join : joins) {
FromItem joinRightItem = join.getRightItem();
if (joinRightItem instanceof Table) {
Table joinTable = (Table) joinRightItem;
String joinTableAlias = joinTable.getAlias().getName();
// 将每一个join的 tx.create_user = createUser 的条件添加到where中
newWhereExpression = setColumnToWhereExpression(newWhereExpression, joinTableAlias);
}
}
return newWhereExpression;
}
/**
* 新增数据时,插入指定字段
* @param insert Insert 语法树
*/
private void setColumnToInsert(Insert insert) {
// 添加create_user列
List<Column> columns = insert.getColumns();
columns.add(new Column("create_user"));
// values中添加指定字段变量值
List<SelectBody> selects = insert.getSelect().getSelectBody(SetOperationList.class).getSelects();
for (SelectBody select : selects) {
if (select instanceof ValuesStatement){
ValuesStatement valuesStatement = (ValuesStatement) select;
ExpressionList expressions = (ExpressionList) valuesStatement.getExpressions();
List<Expression> values = expressions.getExpressions();
for (Expression expression : values){
if (expression instanceof RowConstructor) {
RowConstructor rowConstructor = (RowConstructor) expression;
ExpressionList exprList = rowConstructor.getExprList();
exprList.addExpressions(new StringValue(createUser));
}
}
}
}
}
}
3 单元测试
我们通过MyBatis插件机制在数据持久层拦截执行的SQL,然后再根据动态条件实现查询条件的动态添加。该功能可以用于实现数据访问权限的控制。

以上就是今天SpringBoot3.4.3基于JSqlParser和MyBatis实现自定义数据权限全部内容,文章最后有源码下载地址
欢迎大家关注我的项目实战内容itbeien.cn,一起学习一起进步,在项目和业务中理解各种技术。

欢迎沟通交流技术和支付业务,一起探讨聚合支付/预付卡系统业务、技术、系统架构、微服务、容器化。并结合聚合支付系统深入技术框架/微服务原理及分布式事务原理。加入我的知识星球吧

AI专栏
01IDEA&VsCode集成DeepSeek-V3 API提高编程效率
02IntelliJ IDEA集成主流 AI 编程助手及特性介绍
03Spring AI快速入门-基于DeepSeek&智谱实现聊天应用
04Spring AI中流式对话API如何使用-基于DeepSeek
06SpringAI实现角色扮演(自定义人设)和Prompts模板语法-基于DeepSeek
07LangChain4j实战-Java AI应用开源框架之LangChain4j和Spring AI
SpringBoot3专栏
01SpringBoot3专栏-SpringBoot3.4.0整合Mybatis-plus和Mybatis
02SpringBoot3.4.0结合Mybatis-plus实现动态数据源
03mapstruct对象映射在Springboot3中这样用就对了
04RocketMQ5.3.1集成SpringBoot3.4.0就这样简单
05SpringBoot3.4.0整合Redisson实现分布式锁
06MySQL增量数据同步利器Canal1.1.7环境搭建流程
07SpringBoot3.4.0集成Canal1.1.7实现MySQL实时同步数据到Redis
08基于Docker-SpringBoot3.4.0集成Apache Pulsar4.0.1实现消息发布和订阅
09SpringBoot3.4.0整合消息中间件Kafka和RabbitMQ
10SpringBoot3.4.0整合ActiveMQ6.1.4
11SpringBoot3整合Spring Security6.4.2 安全认证框架实现简单身份认证
12SpringBoot3.4.1和Spring Security6.4.2实现基于内存和MySQL的用户认证
13SpringBoot3.4.1和Spring Security6.4.2结合OAuth2实现GitHub授权登录
14SpringBoot3.4.1和Spring Security6.4.2结合JWT实现用户登录
16SpringBoot3.4.1基于MySQL8和Quartz实现定时任务管理
17SpringBoot3.4.2基于MyBatis和MySQL8多数据源使用示例
18SpringBoot3.4.3实现(文本/附件/HTML/图片)类型邮件发送案例
19SpringBoot3.4.3实现文件上传和全局异常处理
20SpringBoot3.4.3集成Knife4j实现接口文档管理和调试
21SpringBoot3.4.3基于Caffeine实现本地缓存
22SpringBoot3.4.3基于Spring WebFlux实现SSE功能
23SpringBoot3.4.3基于SpringDoc2和Swagger3实现项目接口文档管理
24SpringBoot3.4.3基于Openfeign实现声明式http接口调用
25SpringBoot3.4.3基于Resilience4j实现服务容错构建高可用系统
跟着我学微服务系列
01跟着我学微服务,什么是微服务?微服务有哪些主流解决方案?
05SpringCloudAlibaba之图文搞懂微服务核心组件在企业级支付系统中的应用
06JDK17+SpringBoot3.4.0+Netty4.1.115搭建企业级支付系统POS网关
07JDK17+SpringCloud2023.0.3搭建企业级支付系统-预付卡支付交易微服务
08JDK17+Dubbo3.3.2搭建企业级支付系统-预付卡支付交易微服务
09JDK17+SpringBoot3.3.6+Netty4.1.115实现企业级支付系统POS网关签到功能
贝恩聊架构-项目实战地址
欢迎大家一起讨论学习,加我备注"Java/AI"拉你进入Java&AI技术讨论群,备注"聚合支付"拉你进入支付系统讨论群,在技术学习、成长、工作的路上不迷路!加我后不要急,每天下午6点左右通过!营销号免入

4 源码地址
贝恩聊架构-SpringBoot3专栏系列文章、资料和源代码会同步到以下地址,代码和资料每周都会同步更新
该仓库地址主要用于存放贝恩聊架构-SpringBoot3专栏、贝恩聊架构-AI专栏、基于企业级支付系统学习微服务整体技术栈所有资料和源码
