jpa hibernate sql语句in超过1000之后的出错解决方法
·
概要
` jpa生成的sql语句in在oracle数据库超过1000后会报错,现在用一个简单办法来解决这个问题
代码
hibernate拦截器
import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.util.JdbcUtils;
import lombok.CustomLog;
import lombok.Data;
import org.hibernate.EmptyInterceptor;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
public class SQLStatementInterceptor extends EmptyInterceptor {
/**
* 进行sql语句的预处理时进行转换处理
* @param sql
* @return
*/
@Override
public String onPrepareStatement(String sql)
{
sql = inClause(sql);
return sql;
}
/**
* 需要拆分sql中IN超过1000的情况
*
* @param sql 结构化查询语言
* @return {@link String }
* @author pengzhikang
* @date 2026/03/03 10:09
*/
public String inClause(String sql) {
// 简单测试是否包含in子句
if (StrUtil.isBlank(sql) || !StrUtil.containsIgnoreCase(sql, "in")) {
return sql;
}
// 获取当前数据库类型
DataSourceProperties properties = SpringContextHolder.getBean(DataSourceProperties.class);
DbType dbType = JdbcUtils.getDbTypeRaw(properties.getUrl(), properties.getDriverClassName());
// sql解析
SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType, false);
// 处理其中的IN子句
InClauseSplitVisitor visitor = new InClauseSplitVisitor();
sqlStatement.accept(visitor);
// 未执行过说明不存在in子句
if (!visitor.isExecutionFlag()) {
return sql;
}
// 将修改后的AST重新生成SQL字符串
SQLUtils.FormatOption option = new SQLUtils.FormatOption(false, false);
return SQLUtils.toSQLString(sqlStatement, dbType, option);
}
}
druid访问者工具
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLInListExpr;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitor;
import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitor;
import com.alibaba.druid.sql.dialect.postgresql.visitor.PGASTVisitor;
import com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerASTVisitor;
import com.alibaba.druid.sql.visitor.SQLASTVisitorAdapter;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 自定义AST访问者:遍历并拆分超长IN子句
*/
public class InClauseSplitVisitor extends SQLASTVisitorAdapter
implements OracleASTVisitor, MySqlASTVisitor, PGASTVisitor, SQLServerASTVisitor {
private static final int MAX_IN_SIZE = 1000;
/** 执行标志 */
private boolean executionFlag;
public boolean isExecutionFlag() {
return executionFlag;
}
/**
* 访问IN表达式节点,核心处理逻辑
*/
@Override
public boolean visit(SQLInListExpr inExpr) {
// 获取IN子句中的值列表(排除子查询场景,仅处理固定值列表)
List<SQLExpr> targetList = inExpr.getTargetList();
if (targetList == null || targetList.size() <= MAX_IN_SIZE) {
// 值数量未超过限制,无需处理
return true;
}
executionFlag = true;
// 1. 拆分值列表为多个子列表(每个最多1000个值)
List<List<SQLExpr>> splitLists = Lists.partition(targetList, MAX_IN_SIZE);
// 2. 为每个子列表创建新的IN表达式
List<SQLInListExpr> newInExprs = new ArrayList<>();
for (List<SQLExpr> subList : splitLists) {
SQLInListExpr newInExpr = new SQLInListExpr();
// 复制原IN子句的左值(如 id IN (1,2) 中的 id)
newInExpr.setExpr(inExpr.getExpr().clone());
// 设置拆分后的子值列表
newInExpr.setTargetList(subList.stream().map(SQLExpr::clone).collect(Collectors.toList()));
// 复制原IN子句的NOT标记(如 NOT IN 场景)
newInExpr.setNot(inExpr.isNot());
newInExprs.add(newInExpr);
}
// 3. 将多个新IN表达式用OR连接,替换原IN表达式
replaceOriginalInExpr(inExpr, newInExprs);
// 停止遍历当前IN节点的子节点(已处理完成)
return false;
}
/**
* 替换原IN表达式为多个OR连接的新IN表达式
*/
private void replaceOriginalInExpr(SQLInListExpr originalInExpr, List<SQLInListExpr> newInExprs) {
// 获取原IN表达式的父节点(通常是SQLBinaryOpExpr,如 WHERE 中的 AND/OR 表达式)
SQLObject parent = originalInExpr.getParent();
if (parent instanceof SQLBinaryOpExpr) {
SQLBinaryOpExpr parentBinaryExpr = (SQLBinaryOpExpr) parent;
// 判断原IN表达式是父节点的左操作数还是右操作数
if (parentBinaryExpr.getLeft() == originalInExpr) {
// 构建OR连接的表达式替换左操作数
parentBinaryExpr.setLeft(buildOrExpr(newInExprs));
} else if (parentBinaryExpr.getRight() == originalInExpr) {
// 构建OR连接的表达式替换右操作数
parentBinaryExpr.setRight(buildOrExpr(newInExprs));
}
} else if (parent instanceof SQLSelectQueryBlock) {
// 特殊场景:IN子句直接作为WHERE条件(无AND/OR父节点)
SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) parent;
queryBlock.setWhere(buildOrExpr(newInExprs));
}
}
/**
* 将多个IN表达式用OR运算符连接成一个表达式
*/
private SQLExpr buildOrExpr(List<SQLInListExpr> inExprs) {
if (inExprs.size() == 1) {
return inExprs.get(0);
}
// 初始化OR表达式:第一个IN子句作为左值
SQLBinaryOpExpr orExpr = new SQLBinaryOpExpr(
inExprs.get(0),
SQLBinaryOperator.BooleanOr,
inExprs.get(1)
);
// 依次拼接剩余的IN子句
for (int i = 2; i < inExprs.size(); i++) {
orExpr = new SQLBinaryOpExpr(
orExpr,
SQLBinaryOperator.BooleanOr,
inExprs.get(i)
);
}
return orExpr;
}
}
配置,要替换成你实际的类路径
spring:
jpa:
properties:
hibernate:
#使拦截器生效
ejb:
interceptor: com.thghh.interceptor.SQLStatementInterceptor
更多推荐




所有评论(0)