JSqlParser:SQL 解析利器的使用与最佳实践

在日常开发中,我们或多或少都遇到过这样的场景:
- 动态审计:需要记录所有
UPDATE或DELETE操作影响了哪些表、哪些字段。 - 数据脱敏:在查询特定敏感字段时,自动在 SQL 中套上脱敏函数,如
SELECT DES_PHONE(phone) FROM user。 - 多租户改造:需要在所有查询语句中,无侵入地自动加上租户 ID 的过滤条件,如
WHERE tenant_id = 'xxx'。 - SQL 性能分析:提取 SQL 中的表名、字段、JOIN 关系,进行性能瓶颈分析。
- 自定义 ORM:想写一个自己的轻量级 ORM 框架,SQL 解析是第一步。
面对这些需求,如果只用字符串的 replace 或者正则表达式,代码会变得极其脆弱和难以维护。一个引号、一个换行都可能让整个逻辑崩溃。
这时,我们就需要一个专业的 SQL 解析器。而在 Java 世界里,JSqlParser 无疑是其中最闪亮的明星之一。
什么是 JSqlParser?
JSqlParser 是一个用 Java 编写的 SQL 语句解析器。它可以将一条 SQL 文本字符串,转换成一个可遍历的 Java 类层次结构(即抽象语法树 AST)。
简单来说,你给它一条 SQL:
SELECT id, name FROM users WHERE id > 10;
它还你一个对象世界:
Select
└── PlainSelect
├── SelectItems ([id, name])
├── FromItem (Table: users)
└── Where (GreaterThan)
├── LeftExpression (Column: id)
└── RightExpression (LongValue: 10)
有了这个结构化的对象树,我们就可以随心所欲地读取、分析、修改甚至重构 SQL,而无需关心复杂的字符串匹配。
它的核心优势:
- 轻量级:几乎没有第三方依赖,集成成本极低。
- 功能强大:支持
SELECT,INSERT,UPDATE,DELETE,CREATE,ALTER等绝大多数 SQL 语句。 - 多方言支持:对 Oracle, SQL Server, MySQL, PostgreSQL 等主流数据库的语法有很好的兼容性。
- 纯 Java 实现:与 Java 生态无缝集成。
快速上手:三步搞定 SQL 解析
1. 添加依赖
在你的 pom.xml 中加入 JSqlParser 的依赖:
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>4.6</version> <!-- 建议使用最新版本 -->
</dependency>
2. 解析你的第一条 SQL
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
public class JSqlParserDemo {
public static void main(String[] args) {
String sql = "SELECT id, name FROM users WHERE id > 10 ORDER BY name DESC";
printParser(sql);
System.out.println("------------------");
sql = "SELECT u.name, p.title, COUNT(c.id) as comment_count " +
"FROM users u " +
"LEFT JOIN posts p ON u.id = p.user_id " +
"LEFT JOIN comments c ON p.id = c.post_id " +
"WHERE u.status = 'active' AND p.published_at > '2024-01-01' " +
"GROUP BY u.name, p.title " +
"ORDER BY comment_count DESC";
printParser(sql);
}
private static void printParser(String sql) {
try {
// 1. 使用 CCJSqlParserUtil 解析 SQL 字符串
Statement statement = CCJSqlParserUtil.parse(sql);
// 2. 判断语句类型并强转
if (statement instanceof Select) {
Select select = (Select) statement;
// 3. 获取查询主体 (通常是 PlainSelect)
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
// --- 开始你的“解剖”表演 ---
// 获取 FROM 的表名
System.out.println("表名: " + ((net.sf.jsqlparser.schema.Table) plainSelect.getFromItem()).getName());
// 获取 SELECT 的字段
System.out.println("查询字段:");
for (SelectItem selectItem : plainSelect.getSelectItems()) {
SelectExpressionItem selectItem1 = (SelectExpressionItem) selectItem;
Alias alias = selectItem1.getAlias();
if (alias != null) {
System.out.println(" - " + alias.getName());
} else {
System.out.println(" - " + selectItem1.getExpression());
}
}
// 获取 WHERE 条件
System.out.println("WHERE 条件: " + plainSelect.getWhere());
// 获取 ORDER BY
System.out.println("排序: " + plainSelect.getOrderByElements());
}
} catch (JSQLParserException e) {
e.printStackTrace();
}
}
}
输出结果:
表名: users
查询字段:
- id
- name
WHERE 条件: id > 10
排序: [name DESC]
------------------
表名: users
查询字段:
- u.name
- p.title
- comment_count
WHERE 条件: u.status = 'active' AND p.published_at > '2024-01-01'
排序: [comment_count DESC]
看,是不是很简单?我们不再需要关心字符串的空格、大小写,而是直接通过对象的方法获取了我们需要的信息。
核心概念与 API 探秘
JSqlParser 的 API 设计非常清晰,主要围绕以下几个核心接口/类:
Statement:所有 SQL 语句的根接口,如Select,Insert,Update等。Expression:所有表达式的根接口,如Column(字段),LongValue(数字),StringValue(字符串),Function(函数), 以及各种运算表达式EqualsTo,GreaterThan,AndExpression等。SelectBody:SELECT语句的主体,通常是PlainSelect(简单查询)或SetOperationList(UNION,INTERSECT等集合操作)。Visitor模式:这是 JSqlParser 的精髓所在,也是我们接下来要讲的最佳实践的核心。
JSqlParser 最佳实践 🛠️
1. 拥抱 Visitor 模式,告别 if-else 地狱
当我们需要遍历整个语法树时,如果用大量的 instanceof 判断,代码会变得臃肿不堪。
反例(不推荐):
// 伪代码,展示 if-else 的复杂度
public void processExpression(Expression expr) {
if (expr instanceof AndExpression) {
// 处理 AND 逻辑
AndExpression and = (AndExpression) expr;
processExpression(and.getLeftExpression());
processExpression(and.getRightExpression());
} else if (expr instanceof OrExpression) {
// 处理 OR 逻辑
// ...
} else if (expr instanceof EqualsTo) {
// 处理等于
// ...
} else if (expr instanceof Function) {
// 处理函数
// ...
}
// ... 还有几十个 else if
}
正例(强烈推荐):使用 ExpressionVisitor 或 StatementVisitor
JSqlParser 提供了 ExpressionVisitor 和 StatementVisitor 接口(以及它们的 Adapter 适配器类),让我们可以优雅地遍历语法树。
场景:我们想找出 WHERE 条件中所有的 = 比较,并打印出它们。
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.TablesNamesFinder;
// 自定义一个 Visitor 来查找等于表达式
public class EqualsToFinder implements ExpressionVisitor {
@Override
public void visit(EqualsTo equalsTo) {
// 当遍历到一个 EqualsTo 节点时,这个方法会被调用
Expression left = equalsTo.getLeftExpression();
Expression right = equalsTo.getRightExpression();
System.out.println("找到等于条件: " + left + " = " + right);
}
// 为了能遍历到 AND/OR 的子节点,需要实现这些方法
@Override
public void visit(AndExpression andExpression) {
andExpression.getLeftExpression().accept(this);
andExpression.getRightExpression().accept(this);
}
@Override
public void visit(OrExpression orExpression) {
orExpression.getLeftExpression().accept(this);
orExpression.getRightExpression().accept(this);
}
// 其他需要实现的方法可以暂时留空,或者继承 ExpressionVisitorAdapter
@Override public void visit(NullValue nullValue) {}
@Override public void visit(Function function) {}
// ... 省略一堆空实现
}
public class VisitorDemo {
public static void main(String[] args) throws Exception {
String sql = "SELECT * FROM orders WHERE user_id = 1 AND (status = 'PAID' OR amount > 100)";
Statement statement = CCJSqlParserUtil.parse(sql);
Select select = (Select) statement;
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
System.out.println("开始分析 SQL: " + sql);
EqualsToFinder finder = new EqualsToFinder();
// 让 WHERE 条件接受我们的访问者
plainSelect.getWhere().accept(finder);
}
}
输出结果:
开始分析 SQL: SELECT * FROM orders WHERE user_id = 1 AND (status = 'PAID' OR amount > 100)
找到等于条件: user_id = 1
找到等于条件: status = 'PAID'
使用 Visitor 模式,代码逻辑清晰,扩展性极强。当需要处理新的表达式类型时,只需在 Visitor 中添加对应的 visit 方法即可,完全符合开闭原则。
2. 修改 SQL:不只是“读”,更是“写”
JSqlParser 的 AST 是可变的。这意味着我们可以修改它,然后再重新生成 SQL 字符串。
场景:为所有 SELECT 语句自动添加一个 LIMIT 0, 1000 的限制,防止查询过大。
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.expression.LongValue;
public class ModifySqlDemo {
public static void main(String[] args) throws Exception {
String originalSql = "SELECT id, product_name FROM products WHERE category = 'electronics'";
// 1. 解析
Statement statement = CCJSqlParserUtil.parse(originalSql);
Select selectStatement = (Select) statement;
PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody();
// 2. 修改:创建一个 LIMIT 对象
Limit limit = new Limit();
limit.setRowCount(new LongValue(1000)); // 设置限制行数
limit.setOffset(new LongValue(0)); // 设置偏移量
// 3. 将 LIMIT 对象设置到 PlainSelect 中
plainSelect.setLimit(limit);
// 4. 重新生成 SQL
String modifiedSql = selectStatement.toString();
System.out.println("原始 SQL: " + originalSql);
System.out.println("修改后 SQL: " + modifiedSql);
}
}
输出结果:
原始 SQL: SELECT id, product_name FROM products WHERE category = 'electronics'
修改后 SQL: SELECT id, product_name FROM products WHERE category = 'electronics' LIMIT 0, 1000
这个特性在实现多租户、数据权限隔离等功能时,简直是无价之宝。
3. 善用工具类
JSqlParser 提供了一些好用的工具类,如 TablesNamesFinder,可以快速提取 SQL 中的所有表名。
// ... 接上文
TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
List<String> tableList = tablesNamesFinder.getTableList(statement);
System.out.println("SQL 涉及的表: " + tableList); // 输出: [products]
// 将堆在一起的多条复杂sql解析为一条条 sql 去执行
Statements statements = CCJSqlParserUtil.parseStatements(sqls);
List<Statement> statementList = statements.getStatements();
4. 缓存提高解析效率
@Component
public class SqlParseCache {
private final LoadingCache<String, Statement> cache;
public SqlParseCache() {
this.cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(30))
.build(new CacheLoader<String, Statement>() {
@Override
public Statement load(String sql) throws Exception {
return CCJSqlParserUtil.parse(sql);
}
});
}
public Statement getParsedStatement(String sql) {
try {
return cache.get(sql);
} catch (ExecutionException e) {
throw new SqlParseException("缓存解析失败", e);
}
}
public void invalidate(String sql) {
cache.invalidate(sql);
}
public void invalidateAll() {
cache.invalidateAll();
}
}
5. 为任意SQL添加或修改分页
/**
* 为SQL添加或修改分页。这是一个总入口。
*
* @param sql 原始SQL
* @param pageNum 页码 (从1开始)
* @param pageSize 每页大小
* @param dbType 数据库类型
* @return 修改后的分页SQL
* @throws JSQLParserException SQL解析失败
*/
public static String addOrModifyPagination(String sql, DbType dbType, int pageNum, int pageSize)
throws JSQLParserException {
Statement statement = CCJSqlParserUtil.parse(sql);
if (!(statement instanceof Select)) {
log.warn("非SELECT语句,将返回原始SQL,sql:{}", sql);
return sql; // 非SELECT语句直接返回
}
Select select = (Select) statement;
SelectBody selectBody = select.getSelectBody();
long offset = (long) (pageNum - 1) * pageSize;
switch (dbType) {
case mysql:
case gbase:
case mariadb:
case oceanbase:
case hive:
case argo:
case sqlite:
handleLimit(selectBody, offset, pageSize);
break;
case postgresql:
case gaussdb:
case oscar:
case XCloud:
handleLimitOffset(selectBody, offset, pageSize);
break;
case sqlserver:
// case oracle:
// Oracle 12c+ and SQL Server 2012+ use the same syntax
handleSqlServerPagination(selectBody, offset, pageSize);
break;
case oracle:
case dm:
case oceanbase_oracle:
// 老版本Oracle和达梦Oracle模式使用ROWNUM
// 这种方式需要重写整个查询,所以我们返回一个新的Select对象
select = handleOracleRowNum(select, offset, pageSize);
break;
// case GBASE8S:
// handleSkipFirst(selectBody, offset, pageSize);
// break;
default:
throw new UnsupportedOperationException("Unsupported database type: " + dbType);
}
return select.toString();
}
/**
* 获取 sql 中分页大小,如果没有分页或者获取失败,则返回 -1,如果查询语句中包含临时表或不包含表名时,则返回 -2
*
* @param sql 原始SQL
* @param dbType 数据库类型
* @return 修改后的分页SQL
* @throws JSQLParserException SQL解析失败
*/
public static long getLimitByJSqlParser(String sql, DbType dbType) throws JSQLParserException {
Statement statement = CCJSqlParserUtil.parse(sql);
if (!(statement instanceof Select)) {
log.warn("非SELECT语句,将返回原始SQL,sql:{}", sql);
return -1; // 非SELECT语句直接返回
}
Select select = (Select) statement;
List<String> tables = getTables(select);
// 如果是临时表,则返回 -2
if (isTempTable(tables)) {
return -2;
}
SelectBody selectBody = select.getSelectBody();
switch (dbType) {
case mysql:
case gbase:
case mariadb:
case oceanbase:
case hive:
case argo:
case sqlite:
case postgresql:
case gaussdb:
case oscar:
case XCloud:
return getLimit(selectBody);
case sqlserver:
// case oracle:
// Oracle 12c+ and SQL Server 2012+ use the same syntax
return getSqlServerPagination(selectBody);
case oracle:
case dm:
case oceanbase_oracle:
// 老版本Oracle和达梦Oracle模式使用ROWNUM
// 这种方式需要重写整个查询,所以我们返回一个新的Select对象
// todo
return getOraclePagination(selectBody);
// case GBASE8S:
// handleSkipFirst(selectBody, offset, pageSize);
// break;
default:
throw new UnsupportedOperationException("Unsupported database type: " + dbType);
}
}
private static long getLimit(SelectBody selectBody) {
if (selectBody instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) selectBody;
Limit limit = plainSelect.getLimit();
return getLimitFromLimit(limit);
} else if (selectBody instanceof SetOperationList) {
SetOperationList setOpList = (SetOperationList) selectBody;
Limit limit = setOpList.getLimit();
return getLimitFromLimit(limit);
} else {
log.warn("不支持的SELECT语句类型,将返回原始SQL");
}
return -1;
}
private static long getLimitFromLimit(Limit limit) {
if (limit == null) {
return -1;
}
Expression rowCount = limit.getRowCount();
if (rowCount == null) {
return -1;
}
if (rowCount instanceof LongValue) {
return ((LongValue) rowCount).getValue();
}
// 其他占位符等情况,则返回 -1
return -1;
}
// 处理器1: LIMIT ... ...
private static void handleLimit(SelectBody selectBody, long offset, long pageSize) {
if (selectBody instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) selectBody;
Limit limit = plainSelect.getLimit();
if (limit == null) {
limit = new Limit();
plainSelect.setLimit(limit);
}
limit.setRowCount(new LongValue(pageSize));
limit.setOffset(new LongValue(offset));
} else if (selectBody instanceof SetOperationList) {
SetOperationList setOpList = (SetOperationList) selectBody;
Limit limit = setOpList.getLimit();
if (limit == null) {
limit = new Limit();
setOpList.setLimit(limit);
}
limit.setRowCount(new LongValue(pageSize));
limit.setOffset(new LongValue(offset));
}
}
// 处理器2: LIMIT ... OFFSET ...
private static void handleLimitOffset(SelectBody selectBody, long offset, long pageSize) {
if (selectBody instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) selectBody;
Limit limit = plainSelect.getLimit();
if (limit == null) {
limit = new Limit();
plainSelect.setLimit(limit);
}
limit.setRowCount(new LongValue(pageSize));
Offset offsetObject = plainSelect.getOffset();
if (offsetObject == null) {
offsetObject = new Offset();
plainSelect.setOffset(offsetObject);
}
setOffsetValue(offset, offsetObject);
} else if (selectBody instanceof SetOperationList) {
SetOperationList setOpList = (SetOperationList) selectBody;
Limit limit = setOpList.getLimit();
if (limit == null) {
limit = new Limit();
setOpList.setLimit(limit);
}
limit.setRowCount(new LongValue(pageSize));
Offset offsetObject = setOpList.getOffset();
if (offsetObject == null) {
offsetObject = new Offset();
setOpList.setOffset(offsetObject);
}
setOffsetValue(offset, offsetObject);
}
}
// 处理器2: OFFSET ... FETCH ...
private static void handleFetchOffset(SelectBody selectBody, long offset, long pageSize) {
if (selectBody instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) selectBody;
// JSqlParser支持在PlainSelect和SetOperationList上直接设置
Offset offsetObj = plainSelect.getOffset();
if (offsetObj == null) {
offsetObj = new Offset();
plainSelect.setOffset(offsetObj);
}
offsetObj.setOffsetParam("ROWS");
setOffsetValue(offset, offsetObj);
Fetch fetch = plainSelect.getFetch();
if (fetch == null) {
fetch = new Fetch();
plainSelect.setFetch(fetch);
}
fetch.setFetchParam("ROWS");
fetch.setRowCount(pageSize);
} else if (selectBody instanceof SetOperationList) {
SetOperationList setOpList = (SetOperationList) selectBody;
// JSqlParser支持在PlainSelect和SetOperationList上直接设置
Offset offsetObj = setOpList.getOffset();
if (offsetObj == null) {
offsetObj = new Offset();
offsetObj.setOffsetParam("ROWS");
setOpList.setOffset(offsetObj);
}
setOffsetValue(offset, offsetObj);
Fetch fetch = setOpList.getFetch();
if (fetch == null) {
fetch = new Fetch();
setOpList.setFetch(fetch);
}
fetch.setFetchParam("ROWS");
fetch.setRowCount(pageSize);
// JdbcParameter jdbcParameter = new JdbcParameter();
// jdbcParameter.setUseFixedIndex(false);
// fetch.setFetchJdbcParameter(jdbcParameter); // 使用 "ROWS" 而不是 "?"
}
}
/**
* 设置偏移值 , 对 JSqlParser 不同版本的适配
*
* @param offset 抵消
* @param offsetObject 补偿对象
*/
public static void setOffsetValue(long offset, Offset offsetObject) {
// 4.0 版本
// offsetObject.setOffset(offset);
// 4.6 版本
offsetObject.setOffset(new LongValue(offset));
}
// 处理器2: OFFSET ... FETCH ...
private static void handleSqlServerPagination(SelectBody selectBody, long offset, long pageSize) {
if (selectBody instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) selectBody;
if (offset == 0) {
Top top = plainSelect.getTop();
if (top == null) {
top = new Top();
plainSelect.setTop(top);
}
top.setExpression(new LongValue(pageSize));
return;
}
Top top = plainSelect.getTop();
if (top != null) {
plainSelect.setTop(null);
}
if (plainSelect.getOrderByElements() == null || plainSelect.getOrderByElements().isEmpty()) {
OrderByElement orderByElement = getOrderByNullElement();
plainSelect.setOrderByElements(Collections.singletonList(orderByElement));
log.warn("SQL Server 分页查询未添加ORDER BY子句,已添加'ORDER BY (SELECT NULL)'");
}
} else if (selectBody instanceof SetOperationList) {
SetOperationList setOpList = (SetOperationList) selectBody;
if (setOpList.getOrderByElements() == null || setOpList.getOrderByElements().isEmpty()) {
OrderByElement orderByElement = getOrderByNullElement();
setOpList.setOrderByElements(Collections.singletonList(orderByElement));
log.warn("SQL Server 分页查询未添加ORDER BY子句,已添加'ORDER BY (SELECT NULL)'");
}
}
handleFetchOffset(selectBody, offset, pageSize);
}
/**
* 获取 null元素排序 sql
*
* @return {@link OrderByElement }
*/
private static OrderByElement getOrderByNullElement() {
OrderByElement orderByElement = new OrderByElement();
SubSelect subSelect = new SubSelect();
PlainSelect select = new PlainSelect();
select.addSelectItems(new SelectExpressionItem(new NullValue()));
subSelect.setSelectBody(select);
orderByElement.setExpression(subSelect);
return orderByElement;
}
private static long getSqlServerPagination(SelectBody selectBody) {
if (selectBody instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) selectBody;
Top top = plainSelect.getTop();
if (top != null) {
Expression expression = top.getExpression();
if (expression instanceof LongValue) {
return ((LongValue) expression).getValue();
}
log.warn("不支持TOP表达式:{}", expression);
return -1;
}
Fetch fetch = plainSelect.getFetch();
if (fetch != null) {
return fetch.getRowCount();
}
} else if (selectBody instanceof SetOperationList) {
SetOperationList setOpList = (SetOperationList) selectBody;
Fetch fetch = setOpList.getFetch();
if (fetch != null) {
return fetch.getRowCount();
}
}
return -1;
}
/**
* 获取oracle分页大小
*
* @param selectBody 选择身体
* @return long
*/
private static long getOraclePagination(SelectBody selectBody) {
if (selectBody instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) selectBody;
Fetch fetch = plainSelect.getFetch();
if (fetch != null) {
return fetch.getRowCount();
}
// 主要用来兼容达梦数据库的 limit 语法
Limit limit = plainSelect.getLimit();
if (limit != null) {
Expression rowCount = limit.getRowCount();
if (rowCount instanceof LongValue) {
return ((LongValue) rowCount).getValue();
}
}
} else if (selectBody instanceof SetOperationList) {
SetOperationList setOpList = (SetOperationList) selectBody;
Fetch fetch = setOpList.getFetch();
if (fetch != null) {
return fetch.getRowCount();
}
}
Optional<Pager> optional = parseRowNum(selectBody);
if (optional.isPresent()) {
return optional.get().getPageSize();
}
return -1;
}
/**
* 解析传统的 ROWNUM 嵌套子查询语法
* 识别模式: SELECT * FROM (SELECT ..., ROWNUM rnum FROM (...) WHERE ROWNUM <= end) WHERE rnum > start
*/
private static Optional<Pager> parseRowNum(SelectBody selectBody) {
// ROWNUM 分页模式一定是一个 PlainSelect
if (!(selectBody instanceof PlainSelect)) {
return Optional.empty();
}
PlainSelect outerSelect = (PlainSelect) selectBody;
// 1. 检查外部查询结构:FROM子句必须是子查询,且有WHERE条件
if (!(outerSelect.getFromItem() instanceof SubSelect) || outerSelect.getWhere() == null) {
return Optional.empty();
}
// 2. 解析外部WHERE条件,获取 offset (e.g., WHERE rnum > 20)
long offset = -1;
String rowNumAlias;
if (outerSelect.getWhere() instanceof ComparisonOperator) {
ComparisonOperator where = (ComparisonOperator) outerSelect.getWhere();
// 支持 "rnum > 20" 或 "rnum >= 21"
if (where.getLeftExpression() instanceof Column && where.getRightExpression() instanceof LongValue) {
rowNumAlias = ((Column) where.getLeftExpression()).getColumnName();
long value = ((LongValue) where.getRightExpression()).getValue();
String op = where.getStringExpression();
if (">".equals(op)) {
offset = value;
} else if (">=".equals(op)) {
offset = value - 1;
}
} else {
rowNumAlias = null;
}
} else {
rowNumAlias = null;
}
if (offset == -1 || rowNumAlias == null) {
return Optional.empty();
}
// 3. 进入中间层子查询
SubSelect middleSubSelect = (SubSelect) outerSelect.getFromItem();
if (!(middleSubSelect.getSelectBody() instanceof PlainSelect)) {
return Optional.empty();
}
PlainSelect middleSelect = (PlainSelect) middleSubSelect.getSelectBody();
// 4. 检查中间层查询结构:必须有WHERE条件
if (middleSelect.getWhere() == null) {
return Optional.empty();
}
// 5. 解析中间层WHERE,获取 endRow (e.g., WHERE ROWNUM <= 30)
long endRow = -1;
if (middleSelect.getWhere() instanceof ComparisonOperator) {
ComparisonOperator where = (ComparisonOperator) middleSelect.getWhere();
// 支持 "ROWNUM <= 30" 或 "ROWNUM < 31"
if (where.getLeftExpression() instanceof Column &&
"ROWNUM".equalsIgnoreCase(((Column) where.getLeftExpression()).getColumnName())
&& where.getRightExpression() instanceof LongValue) {
long value = ((LongValue) where.getRightExpression()).getValue();
String op = where.getStringExpression();
if ("<=".equals(op)) {
endRow = value;
} else if ("<".equals(op)) {
endRow = value - 1;
}
}
}
if (endRow == -1) {
return Optional.empty();
}
// 6. 验证 ROWNUM 别名在中间层 SELECT 列表中
boolean aliasFound = middleSelect.getSelectItems().stream()
.filter(item -> item instanceof SelectExpressionItem)
.map(item -> (SelectExpressionItem) item)
.anyMatch(item -> item.getAlias() != null
&& rowNumAlias.equalsIgnoreCase(item.getAlias().getName())
&& item.getExpression() instanceof Column
&& "ROWNUM".equalsIgnoreCase(((Column) item.getExpression()).getColumnName()));
if (!aliasFound) {
return Optional.empty();
}
// 7. 计算 pageSize 并返回
long pageSize = endRow - offset;
if (pageSize > 0) {
int pageNo = pageSize <= 0 ? 1 : (int) (offset / pageSize + 1);
return Optional.of(Pager.of(pageNo, (int) pageSize));
}
return Optional.empty();
}
// 处理器3: SKIP ... FIRST ...
private static void handleSkipFirst(SelectBody selectBody, long offset, long pageSize) {
if (selectBody instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) selectBody;
Skip skip = plainSelect.getSkip();
if (skip == null) {
skip = new Skip();
plainSelect.setSkip(skip);
}
skip.setRowCount(offset);
First first = plainSelect.getFirst();
if (first == null) {
first = new First();
plainSelect.setFirst(first);
}
first.setRowCount(pageSize);
}
// 注意:UNION查询与SKIP/FIRST的组合在某些数据库中语法可能有限制,
// 这里只处理最常见的PlainSelect场景。
}
// 处理器4: Oracle ROWNUM (最复杂)
private static Select handleOracleRowNum(Select originalSelect, long offset, long pageSize) {
long endRow = offset + pageSize;
// 确保原始查询有ORDER BY,否则ROWNUM分页是无意义的
SelectBody selectBody = originalSelect.getSelectBody();
if (selectBody instanceof PlainSelect) {
if (((PlainSelect) selectBody).getOrderByElements() == null) {
log.warn("ROWNUM pagination without ORDER BY is not recommended.");
}
}
// 外部查询: SELECT * FROM ( ... ) WHERE rnum >= ?
PlainSelect outerSelect = new PlainSelect();
outerSelect.addSelectItems(new AllColumns()); // SELECT *
// 中间查询: SELECT t.*, ROWNUM AS rnum FROM ( original_sql ) t WHERE ROWNUM <= ?
PlainSelect middleSelect = new PlainSelect();
// SELECT t.*, ROWNUM as rnum
middleSelect.addSelectItems(new SelectExpressionItem(new Column("t.*")));
middleSelect.addSelectItems(new SelectExpressionItem(new Column("ROWNUM")).withAlias(new Alias("rnum")));
// FROM ( original_sql ) t
SubSelect originalSubSelect = new SubSelect();
originalSubSelect.setSelectBody(selectBody);
originalSubSelect.setAlias(new Alias("t"));
middleSelect.setFromItem(originalSubSelect);
// WHERE ROWNUM <= endRow
middleSelect.setWhere(new MinorThanEquals().withLeftExpression(new Column("ROWNUM"))
.withRightExpression(new LongValue(endRow)));
// 将中间查询包装成子查询,用于外部查询
SubSelect middleSubSelect = new SubSelect();
middleSubSelect.setSelectBody(middleSelect);
middleSubSelect.setAlias(new Alias("")); // Oracle子查询可以没有别名
outerSelect.setFromItem(middleSubSelect);
// WHERE rnum >= startRow
outerSelect.setWhere(new GreaterThan().withLeftExpression(new Column("rnum"))
.withRightExpression(new LongValue(offset)));
// 创建一个新的Select对象并返回
Select newSelect = new Select();
newSelect.setSelectBody(outerSelect);
return newSelect;
}
/**
* 是否是临时表
*
* @param tables 表
* @return boolean
*/
private static boolean isTempTable(List<String> tables) {
if (tables.isEmpty() || (tables.size() == 1 && "dual".equalsIgnoreCase(tables.get(0)))) {
return true;
}
return false;
}
JSqlParser vs. Alibaba Druid:强强对话,如何选择?💡
谈到 Java 世界的 SQL 解析,另一个无法绕开的名字就是 Alibaba Druid。Druid 不仅是一个强大的数据库连接池,其内置的 SQL Parser 也同样功能强大。那么,我们该如何选择呢?
| 特性维度 | JSqlParser | Alibaba Druid SQL Parser |
|---|---|---|
| 核心定位 | 纯粹的 SQL 解析器。专注、轻量,只做一件事并把它做好。 | 数据库连接池 的一个核心模块。为监控、防火墙、SQL 格式化等服务。 |
| 功能范围 | 解析、遍历、修改 SQL。 | 解析、遍历、修改 SQL,并提供 SQL 格式化、指纹计算、语法兼容性检查等高级功能。 |
| 依赖与体积 | 极简。几乎无外部依赖,JAR 包体积小(约 500KB)。 | 庞大。作为 Druid 的一部分,引入它意味着引入整个连接池生态,体积较大(约 3MB)。 |
| API 设计 | AST 导向。直接操作语法树对象,非常直观。 | AST + Visitor。同样支持 Visitor 模式,API 设计更偏向于其内部生态。 |
| 方言支持 | 良好。支持主流数据库,但对一些非常规或新特性的支持可能稍慢。 | 非常强大。阿里巴巴内部海量业务驱动,对 MySQL、Oracle 等的方言支持非常深入和及时。 |
| 性能 | 非常快。因为目标单一,优化做得很好。 | 同样很快。但作为连接池的一部分,其解析器设计需兼顾多种场景。 |
如何抉择?
一张图帮你决定:

总结一下:
-
选择 JSqlParser 的场景:
- 你的项目是一个工具、库或微服务,需要保持轻量。
- 你只需要 SQL 解析和修改功能,不想引入连接池等“重”组件。
- 你希望有一个专注、纯粹的解析器,API 更直接。
-
选择 Druid SQL Parser 的场景:
- 你的项目已经在使用 Druid 作为连接池。那么“就地取材”是最高效的选择。
- 你需要 Druid 提供的附加功能,如 SQL 监控、WallFilter(SQL 防火墙)、SQL 格式化等。
- 你需要处理极其复杂或特定的数据库方言,Druid 的实践可能更丰富。
两者都是顶级的开源项目,没有绝对的优劣,只有是否适合你的业务场景。
结语
JSqlParser 就像一把精巧的瑞士军刀,它赋予了 Java 应用前所未有的 SQL 处理能力。从简单的信息提取,到复杂的动态改写,它都能游刃有余。
掌握 JSqlParser,意味着你拥有了在数据层进行“元编程”的能力,能够构建出更加智能、灵活和强大的数据应用。希望今天的分享,能为你打开一扇通往 SQL 解析新世界的大门。
注意: duid 和 JSqlParser 并不是互斥的,druid 对特定数据库做了解析优化,可以在 druid 解析异常时,再使用 JSqlPaser去解析,这样既保证了效率又保证了sql解析的兼容性
Q.E.D.


