关于java:springdatajpa使用oracle作为数据源时in查询在1000条以上-报ORA01795的错误

目标

解决我的项目中应用spring-data-jpa,采纳oracle 11g作为数据源时,当in查问前面的条件超过1000条后,oracleORA_01795的异样。

思路

问题在于以后版本的oracle不反对单个in查问超过1000的情景,思路是通过jpa提供给咱们的有实体类生成SQL后、未执行前的拦截器,对生成的SQL进行判断,如果存在上述的超过1000的情景,将SQL拆分成多个in的组合、通过or连贯。
例如:

# jpa转换的SQL通常是如下模式,如果in (?,?,?....)有超过1000个?
select id,name,gender from user where id in(? ,? ,? ,? ,? ,? ,? ,? ,? ,?, ?);
# 将其拆分
select id,name,gender from user where (id in(? ,? ,? ,? ,? ,?) or id in (,? ,? ,?)  or id in(?, ?));

代码

package com.orient.collab.provider.common.jpa.interceptor;

import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.context.annotation.Configuration;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * description:
 * when using oracle as data source, custom a sql statement interceptor to solve "select or delete with 'in' " limit 1000 cause !!!
 * !!!不反对not in
 * @author MorningSun
 * @version 1.0
 * @since JDK1.8
 * date 2022/4/27 19:16
 */
@Configuration
@ConditionalOnProperty(name = "spring.datasource.driver-class-name", havingValue = "oracle.jdbc.driver.OracleDriver")
public class JPAConfigurationOnUsingOracle implements HibernatePropertiesCustomizer {

    /**
     *         StringBuilder sql = new StringBuilder("select *.* from . . where *.* in(");
     *         for (int i = 0; i < 999; i++) {
     *             sql.append("? , ");
     *         }
     *         sql.append("?)");
     *         System.out.println(sql.length());//4033
     */
    private static final int SQL_THRESHOLD_LENGTH = 4033;//超过这个才去匹配
    private final static int IN_CAUSE_LIMIT = 1000;
    private final static Pattern pattern = Pattern.compile("[^(^\\()\\s]+\\s+in\\s*\\(+(\\?\\s*,\\s*){"+(IN_CAUSE_LIMIT - 2) +",}+\\?\\)", Pattern.CASE_INSENSITIVE);

    @Override
    public void customize(Map<String, Object> hibernateProperties) {
        hibernateProperties.put("hibernate.session_factory.statement_inspector", new StatementInspector(){

            @Override
            public String inspect(String sql) {
                if (sql.length() <= SQL_THRESHOLD_LENGTH){
                    return sql;
                }

                return this.rewriteSqlToAvoidORA_01795(sql);
            }

            private String rewriteSqlToAvoidORA_01795(String sql) {
//                long startTime = System.currentTimeMillis();
                Matcher matcher = pattern.matcher(sql);
                while (matcher.find()) {
                    String inExpression = matcher.group();
                    long countOfParameters = inExpression.chars().filter(ch -> ch == '?').count();
                    String fieldName = inExpression.substring(0, inExpression.indexOf(' '));
                    StringBuilder transformedInExpression = new StringBuilder(" ( ").append(fieldName).append(" in (");
                    for (int i = 0; i < countOfParameters; i++) {
                        if (i != 0 && i % IN_CAUSE_LIMIT == 0) {
                            transformedInExpression
                                    .deleteCharAt(transformedInExpression.length() - 1)
                                    .append(") or ").append(fieldName).append(" in (");
                        }
                        transformedInExpression.append("?,");
                    }
                    transformedInExpression.deleteCharAt(transformedInExpression.length() - 1).append("))");
                    sql = sql.replaceFirst(Pattern.quote(inExpression), transformedInExpression.toString());
                }
//                long endTime = System.currentTimeMillis();
//                System.out.println("拆分sql耗时:"+(endTime - startTime)+"ms");//2ms
                return sql;
            }
        });
    }

}

问题

  • 不反对not in,思路其实一样的,not in 须要通过and连贯,该场景更少,故不作思考;
  • 对于性能:

    • 但凡应用了jpadao层操作,均会走到该拦截器,所有基于jpa生成的sql的特色定义一个sql长度的阈值,生成的sqllength如果小于该阈值,间接返回(理论这个根本曾经排除了绝大部分的场景);
    • 对于正则匹配,可能有多种写法,因为不足对底层的相干机制如:回溯贪婪算法等理解,尚有优化的中央(本人试着写了几种不同的模式测了一下,耗时都在1、2ms,所以可能呈现的性能瓶颈可能不是sql的转化上,更多的是sql的查问执行上)

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理