如何在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(?))",