You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何在Hibernate Criteria查询中使用Oracle数组绑定(array bind)参数

好问题!针对你遇到的Oracle超长IN列表+Hibernate Criteria的痛点,确实可以通过Oracle的**数组绑定(array bind)**来解决,而且完全不需要放弃Criteria API。下面一步步给你讲怎么落地:

第一步:在Oracle数据库创建自定义数组类型

首先需要让DBA帮你在数据库里创建一个对应ID类型的数组(比如你的ID是数字型):

CREATE OR REPLACE TYPE NUMBER_ARRAY AS TABLE OF NUMBER;

如果是字符串ID,就改成VARCHAR2_ARRAY AS TABLE OF VARCHAR2(100)(长度根据你的ID实际情况调整)。

第二步:给Hibernate注册自定义数组类型

Hibernate默认不认识Oracle的自定义数组,所以需要写一个自定义类型来处理Java列表和Oracle数组的转换,同时注册到Hibernate中。

1. 实现自定义Hibernate类型

这里以Long类型ID为例,写一个OracleLongArrayType

import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.descriptor.java.AbstractTypeDescriptor;
import org.hibernate.type.descriptor.sql.ArraySqlTypeDescriptor;
import java.sql.Array;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import oracle.jdbc.OracleConnection;

public class OracleLongArrayType extends AbstractSingleColumnStandardBasicType<List<Long>> {

    public OracleLongArrayType() {
        super(ArraySqlTypeDescriptor.INSTANCE, new LongArrayJavaTypeDescriptor());
    }

    @Override
    public String getName() {
        return "oracle-long-array";
    }

    public static class LongArrayJavaTypeDescriptor extends AbstractTypeDescriptor<List<Long>> {

        public LongArrayJavaTypeDescriptor() {
            super(List.class, new MutabilityPlan<List<Long>>() {
                @Override
                public List<Long> deepCopy(List<Long> value) {
                    return value != null ? new ArrayList<>(value) : null;
                }

                @Override
                public boolean isMutable() {
                    return true;
                }
            });
        }

        @Override
        public String toString(List<Long> value) {
            return value != null ? value.toString() : null;
        }

        @Override
        public List<Long> fromString(String string) {
            throw new UnsupportedOperationException("直接字符串转列表暂不支持");
        }

        @Override
        public <X> X unwrap(List<Long> value, Class<X> type, WrapperOptions options) {
            if (value == null) {
                return null;
            }
            if (Array.class.isAssignableFrom(type)) {
                try {
                    Connection conn = options.getSession().doReturningWork(Connection::get);
                    OracleConnection oracleConn = conn.unwrap(OracleConnection.class);
                    return (X) oracleConn.createARRAY("NUMBER_ARRAY", value.toArray());
                } catch (SQLException e) {
                    throw new RuntimeException("转换Oracle数组失败", e);
                }
            }
            throw unknownUnwrap(type);
        }

        @Override
        public <X> List<Long> wrap(X value, WrapperOptions options) {
            if (value == null) {
                return null;
            }
            if (value instanceof Array) {
                try {
                    Object[] array = (Object[]) ((Array) value).getArray();
                    return Arrays.stream(array).map(Long.class::cast).collect(Collectors.toList());
                } catch (SQLException e) {
                    throw new RuntimeException("转换Java列表失败", e);
                }
            }
            throw unknownWrap(value.getClass());
        }
    }
}

如果是字符串ID,只要把Long换成String,数组类型名改成VARCHAR2_ARRAY即可。

2. 注册自定义类型

可以用注解或者XML配置,推荐在实体类或者专门的配置类上用@TypeDef

import org.hibernate.annotations.TypeDef;
import javax.persistence.Entity;

@Entity
@TypeDef(name = "oracle-long-array", typeClass = OracleLongArrayType.class)
public class YourEntity {
    // 你的实体字段...
}

或者在hibernate.cfg.xml里加:

<typedef name="oracle-long-array" class="com.yourpackage.OracleLongArrayType"/>

第三步:在Criteria查询中使用数组绑定

现在可以抛弃原来拆分IN子句的写法,改用SQLRestriction来关联数组参数:

if (idList) {
    criteria.add(
        Restrictions.sqlRestriction(
            "id IN (SELECT column_value FROM TABLE(?))",
            idList,
            sessionFactory.getTypeHelper().custom(OracleLongArrayType.class)
        )
    )
}

如果已经通过@TypeDef注册了类型名,也可以直接用类型名简化:

criteria.add(
    Restrictions.sqlRestriction(
        "id IN (SELECT column_value FROM TABLE(?))",
        idList,
        org.hibernate.type.TypeHelper.custom("oracle-long-array")
    )
)

这段SQL里的TABLE(?)是Oracle的语法,会把传入的数组转换成临时表,然后用column_value取数组中的每个元素,效果和原来的IN子句完全一致,但只有1个绑定参数,完美解决DBA提到的执行计划不稳定问题。

关键注意事项

  • 确保数据库用户有访问自定义数组类型的权限,让DBA帮忙确认。
  • 如果是Hibernate 4及以下版本,自定义类型需要实现UserType接口(逻辑类似,只是写法略有不同)。
  • 测试时对比性能:这种方式和原来拆分IN子句的性能应该相当,但胜在执行计划稳定,DBA更容易调优。
  • 如果ID类型是字符串,一定要对应修改数组类型和自定义类型的处理逻辑,避免类型转换错误。

内容的提问来源于stack exchange,提问作者Kaypro II

火山引擎 最新活动