You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用UserType作为父实体ID加载集合时触发ArrayStoreException(PostgreSQL)

使用自定义UserType作为实体ID时PostgreSQL批量加载集合触发ArrayStoreException的问题

问题场景

使用自定义UserType(包括基于hypersistence-utils实现和自行实现的版本)作为实体Foo的ID类型,当Foo关联FooProgress集合并开启@BatchSize时,在PostgreSQL环境下(开启useArrayForMultiValuedParameters=true),加载Foo集合会触发ArrayStoreException。问题在Hibernate 6.6.18.Final和7.0.3.Final版本中均存在。

触发异常的查询语句:

select pe1_0.foo_id,pe1_0.id,pe1_0.name from foo_progress pe1_0 where pe1_0.foo_id = any (?)

核心栈追踪片段:

java.lang.ArrayStoreException: com.lesiak.test.usertypes.entities.FooId
    at org.hibernate.type.descriptor.java.ArrayJavaType.unwrap(ArrayJavaType.java:275)
    at org.hibernate.type.descriptor.java.ArrayJavaType.unwrap(ArrayJavaType.java:34)
    at org.hibernate.type.descriptor.jdbc.ArrayJdbcType.getArray(ArrayJdbcType.java:183)
    at org.hibernate.dialect.PostgreSQLArrayJdbcType$1.getArray(PostgreSQLArrayJdbcType.java:81)

原因分析

Hibernate使用CollectionBatchLoaderArrayParam处理批量集合加载时,会将实体ID集合转换为JDBC数组参数。但对于自定义UserType的ID类型FooId,Hibernate默认会创建FooId[]数组,而JDBC需要的是FooId unwrap后的String[]数组(因为FooId对应JDBC类型为VARCHAR)。当尝试将unwrap得到的String存入FooId[]数组时,就会抛出类型不匹配的ArrayStoreException。

本质是Hibernate未正确识别自定义ID类型的数组参数需要转换为unwrap后的目标类型数组。

解决方案

方案1:修改自定义UserType,指定数组参数的元素类型

在FooIdUserType中重写getArrayJavaType方法,明确指定数组的元素类型为String,让Hibernate创建String[]数组来存储unwrap后的ID值:

package com.lesiak.test.usertypes.usertypes;

import com.lesiak.test.usertypes.entities.FooId;
import io.hypersistence.utils.hibernate.type.DescriptorImmutableType;
import org.hibernate.HibernateException;
import org.hibernate.type.descriptor.java.ArrayJavaType;
import org.hibernate.type.descriptor.java.StringJavaType;
import org.hibernate.type.descriptor.jdbc.VarcharJdbcType;

public class FooIdUserType extends DescriptorImmutableType<FooId, VarcharJdbcType, FooIdTypeDescriptor> {
    public FooIdUserType() {
        super(FooId.class, VarcharJdbcType.INSTANCE, FooIdTypeDescriptor.INSTANCE);
    }

    public FooId fromStringValue(CharSequence sequence) throws HibernateException {
        return this.getExpressibleJavaType().wrap(sequence, null);
    }

    @Override
    public org.hibernate.type.descriptor.java.JavaType<?> getArrayJavaType() {
        // 指定数组元素类型为String,匹配JDBC的VARCHAR数组类型
        return ArrayJavaType.from(StringJavaType.INSTANCE, getJdbcType());
    }
}

方案2:禁用PostgreSQL数组批量参数

如果不想修改代码,可以通过Hibernate配置关闭数组参数的使用,让Hibernate生成IN子句替代ANY(?):

# application.properties
hibernate.dialect.postgresql.use_array_for_multivalued_parameters=false

该方案会失去PostgreSQL数组参数的性能优势,适合临时验证或低并发场景。

验证

修改后重新运行测试代码,fooRepository.findAll()可正常加载所有Foo实体及其关联的FooProgress集合,不再抛出ArrayStoreException。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 19:35:52