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

Spring Boot+Hibernate中Map转PostgreSQL数组出现类型转换异常

问题:Spring Boot + PostgreSQL 中 Map<DayOfWeek, Integer> 映射到 integer[7] 数组报错

环境与需求

  • Spring Boot 3.4.4
  • PostgreSQL 15
  • 需求:将Java的Map<DayOfWeek, Integer>映射到PostgreSQL的integer[7]数组,数组下标对应DayOfWeek.ordinal(),例如[0,2,3,0,0,0,0]表示TUESDAY对应值2,WEDNESDAY对应值3,其余日期为0。

现有代码

转换器实现

@Converter
public class MyConverter implements AttributeConverter<Map<DayOfWeek, Integer>, Integer[]> {

  @Override
  public Integer[] convertToDatabaseColumn(Map<DayOfWeek, Integer> map) {
    return Arrays.stream(DayOfWeek.values())
        .map(type -> map.getOrDefault(type, 0))
        .toArray(Integer[]::new);
  }

  @Override
  public Map<DayOfWeek, Integer> convertToEntityAttribute(Integer[] values) {
    return Arrays.stream(DayOfWeek.values())
        .collect(Collectors.toMap(type -> type, dayOfWeek -> values[dayOfWeek.ordinal()]));
  }
}

Entity属性定义

@Convert(converter = MyConverter.class)
@Column(name = "dow_quantities", nullable = false, columnDefinition = "integer[7]")
private Map<DayOfWeek, Integer> daysOfWeekQuantities;

数据库表结构

dow_quantities       INTEGER[7] NOT NULL,

抛出异常

Caused by: java.lang.ClassCastException: class org.hibernate.type.internal.CustomMutabilityConvertedBasicTypeImpl cannot be cast to class org.hibernate.type.BasicPluralType (org.hibernate.type.internal.CustomMutabilityConvertedBasicTypeImpl and org.hibernate.type.BasicPluralType are in unnamed module of loader 'app')
    at org.hibernate.type.descriptor.sql.internal.ArrayDdlTypeImpl.getCastTypeName(ArrayDdlTypeImpl.java:37) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.sql.ast.spi.AbstractSqlAstTranslator.getCastTypeName(AbstractSqlAstTranslator.java:7088) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.sql.ast.spi.AbstractSqlAstTranslator.visitCastTarget(AbstractSqlAstTranslator.java:7035) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.sql.ast.tree.expression.CastTarget.accept(CastTarget.java:80) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.sql.ast.spi.AbstractSqlAstTranslator.render(AbstractSqlAstTranslator.java:7186) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.query.sqm.produce.function.internal.PatternRenderer.render(PatternRenderer.java:233) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.query.sqm.produce.function.internal.PatternRenderer.render(PatternRenderer.java:167) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.query.sqm.produce.function.internal.PatternRenderer.render(PatternRenderer.java:150) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.dialect.function.CastFunction.render(CastFunction.java:87) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.sql.ast.spi.AbstractSqlAstTranslator.renderCasted(AbstractSqlAstTranslator.java:5823) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.dialect.SqlAstTranslatorWithMerge.renderMergeUsingQuerySelection(SqlAstTranslatorWithMerge.java:161) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.dialect.SqlAstTranslatorWithMerge.renderMergeUsingQuery(SqlAstTranslatorWithMerge.java:150) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.dialect.SqlAstTranslatorWithMerge.renderMergeUsing(SqlAstTranslatorWithMerge.java:126) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.dialect.SqlAstTranslatorWithMerge.renderMergeStatement(SqlAstTranslatorWithMerge.java:82) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.dialect.SqlAstTranslatorWithMerge.createMergeOperation(SqlAstTranslatorWithMerge.java:43) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.dialect.PostgreSQLDialect.usingMerge(PostgreSQLDialect.java:1570) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.dialect.PostgreSQLDialect.createOptionalTableUpdateOperation(PostgreSQLDialect.java:1562) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.sql.model.internal.OptionalTableUpdate.createMutationOperation(OptionalTableUpdate.java:144) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.persister.entity.mutation.AbstractMutationCoordinator.createOperationGroup(AbstractMutationCoordinator.java:87) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.buildStaticUpdateGroup(UpdateCoordinatorStandard.java:1663) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.<init>(UpdateCoordinatorStandard.java:93) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.persister.entity.mutation.MergeCoordinator.<init>(MergeCoordinator.java:23) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.buildMergeCoordinator(AbstractEntityPersister.java:3827) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.doLateInit(AbstractEntityPersister.java:3569) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.postInstantiate(AbstractEntityPersister.java:3859) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl.finishInitialization(MappingMetamodelImpl.java:203) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.internal.SessionFactoryImpl.initializeMappingModel(SessionFactoryImpl.java:373) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:302) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:463) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1517) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final]
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:66) ~[spring-orm-6.2.5.jar:6.2.5]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:390) ~[spring-orm-6.2.5.jar:6.2.5]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:419) ~[spring-orm-6.2.5.jar:6.2.5]
    ... 19 common frames omitted

解决方案

问题根源是Hibernate处理数组类型自定义转换器时,无法正确识别BasicPluralType导致类型转换失败,以下是三种可行解决方式:

方案1:用Hibernate @Type替代JPA @Convert

借助Hibernate提供的数组类型映射,先将属性映射为Integer[],再手动处理与Map<DayOfWeek, Integer>的转换:

  1. 修改Entity属性定义:
@Type(value = IntegerArrayType.class)
@Column(name = "dow_quantities", nullable = false, columnDefinition = "integer[7]")
private Integer[] daysOfWeekQuantities;
  1. 封装转换工具方法:
public Map<DayOfWeek, Integer> arrayToMap(Integer[] array) {
    return Arrays.stream(DayOfWeek.values())
        .collect(Collectors.toMap(day -> day, day -> array[day.ordinal()]));
}

public Integer[] mapToArray(Map<DayOfWeek, Integer> map) {
    return Arrays.stream(DayOfWeek.values())
        .map(day -> map.getOrDefault(day, 0))
        .toArray(Integer[]::new);
}

注:需确保依赖中包含对应版本的hibernate-types包(如hibernate-types-60适配Hibernate 6.x)。

方案2:自定义Hibernate UserType

实现Hibernate的UserType接口,直接完成Map<DayOfWeek, Integer>到PostgreSQL数组的映射:

  1. 自定义类型实现:
public class DayOfWeekMapType implements UserType {

    private static final int[] SQL_TYPES = {Types.ARRAY};

    @Override
    public int[] sqlTypes() {
        return SQL_TYPES;
    }

    @Override
    public Class<?> returnedClass() {
        return Map.class;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == y) return true;
        if (x == null || y == null) return false;
        return x.equals(y);
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
        Integer[] array = (Integer[]) rs.getArray(names[0]).getArray();
        return Arrays.stream(DayOfWeek.values())
            .collect(Collectors.toMap(day -> day, day -> array[day.ordinal()]));
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
        @SuppressWarnings("unchecked")
        Map<DayOfWeek, Integer> map = (Map<DayOfWeek, Integer>) value;
        Integer[] array = Arrays.stream(DayOfWeek.values())
            .map(day -> map.getOrDefault(day, 0))
            .toArray(Integer[]::new);
        st.setArray(index, session.connection().createArrayOf("integer", array));
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        if (value == null) return null;
        @SuppressWarnings("unchecked")
        Map<DayOfWeek, Integer> map = (Map<DayOfWeek, Integer>) value;
        return new EnumMap<>(map);
    }

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

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) deepCopy(value);
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return deepCopy(cached);
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return deepCopy(original);
    }
}
  1. 在Entity中使用该类型:
@Type(value = DayOfWeekMapType.class)
@Column(name = "dow_quantities", nullable = false, columnDefinition = "integer[7]")
private Map<DayOfWeek, Integer> daysOfWeekQuantities;

方案3:禁用PostgreSQL MERGE策略

异常栈显示问题出在MERGE语句处理中,可通过配置强制使用UPDATE语句规避:
在application.properties中添加:

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.hql.bulk_id_strategy=org.hibernate.hql.spi.id.inline.InlineIdsInClauseBulkIdStrategy

说明

方案1、2为长期可靠解决方案,其中方案2可直接在Entity中使用Map<DayOfWeek, Integer>类型,无需手动转换,推荐优先采用;方案3为临时规避手段,仅适用于特定场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 10:00:55