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

Hibernate从3.6升级到5.2后用JoinColumnOrFormula触发ClassCastException

Hibernate 5.2.18迁移后ClassCastException异常分析与解决

数据库模型

TABLE_CONNECTION:
+--+
|ID|
+--+
|1 |
|2 |
+--+

TABLE_ENDPOINT:
+--+------+-------------+
|ID|CON_ID|ENDPOINT_TYPE|
+--+------+-------------+
|1 |1     |FROM         |
|2 |1     |TO           |
|3 |2     |FROM         |
|4 |2     |TO           |
+--+------+-------------+

Hibernate 3.6.10映射代码

Connection实体

@Entity
@Table(name = "TABLE_CONNECTION")
@Immutable
public class Connection {

    @Id
    @Column(name = "ID", unique = true, nullable = false, precision = 38, scale = 0)
    private BigDecimal id;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumnsOrFormulas({
        @JoinColumnOrFormula(column = @JoinColumn(name = "ID", referencedColumnName = "CON_ID"))
        @JoinColumnOrFormula(formula = @JoinFormula(value = "'FROM'", referencedColumnName = "ENDPOINT_TYPE"))
    })
    private Endpoint fromEndpoint;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumnsOrFormulas({
        @JoinColumnOrFormula(column = @JoinColumn(name = "ID", referencedColumnName = "CON_ID"))
        @JoinColumnOrFormula(formula = @JoinFormula(value = "'TO'", referencedColumnName = "ENDPOINT_TYPE"))
    })
    private Endpoint toEndpoint;

    ...
}

Endpoint实体

@Entity
@Table(name = "TABLE_ENDPOINT")
@Immutable
public class Endpoint {

    @Id
    @Column(name = "ID", unique = true, nullable = false, precision = 38, scale = 0)
    private BigDecimal id;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "CON_ID", nullable = false)
    private Connection connectionId;

    @Column(name = "ENDPOINT_TYPE", length = 4, nullable = false)
    @Enumerated(EnumType.STRING)
    private EndpointType endpointType;

    ...
}

enum EndpointType {FROM, TO}

迁移后抛出的异常

调用hibernateTemplate.get(Connection.class, BigDecimal.valueOf(1))时出现如下异常:

java.lang.ClassCastException: com.our.package.Endpoint cannot be cast to java.math.BigDecimal

    at org.hibernate.type.descriptor.java.BigDecimalTypeDescriptor.extractHashCode(BigDecimalTypeDescriptor.java:19)
    at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:204)
    at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:209)
    at org.hibernate.engine.spi.EntityUniqueKey.generateHashCode(EntityUniqueKey.java:67)
    at org.hibernate.engine.spi.EntityUniqueKey.<init>(EntityUniqueKey.java:48)
    at org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:250)
    at org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:171)
    at org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:128)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:238)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:209)
    at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:133)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:122)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86)
    at org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:185)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4121)
    at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)
    at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)
    at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)
    at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:278)
    at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:121)
    at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)
    at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1240)
    at org.hibernate.internal.SessionImpl.access$1900(SessionImpl.java:204)
    at org.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.doLoad(SessionImpl.java:2842)
    at org.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.load(SessionImpl.java:2816)
    at org.hibernate.internal.SessionImpl.get(SessionImpl.java:1076)
    at org.springframework.orm.hibernate5.HibernateTemplate.lambda$get$0(HibernateTemplate.java:456)
    at org.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:367)
    at org.springframework.orm.hibernate5.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:334)
    at org.springframework.orm.hibernate5.HibernateTemplate.get(HibernateTemplate.java:451)
    at org.springframework.orm.hibernate5.HibernateTemplate.get(HibernateTemplate.java:445)

异常原因

Hibernate 5.x版本对@JoinColumnsOrFormulas的处理逻辑做了严格调整,尤其是在实体缓存的主键哈希计算环节。原来的映射中,Connection通过复合列+公式关联Endpoint,而Hibernate 5在处理Endpoint到Connection的反向ManyToOne关联时,错误地将Endpoint实体实例当作BigDecimal类型的主键传入哈希计算方法,导致类型转换失败。

简单来说,Hibernate 3对这种非常规关联的兼容性处理更宽松,而5.x版本的实体唯一键生成逻辑更严谨,混淆了关联键的类型识别。

解决方法

方案1:改用@Where注解替代公式关联(推荐)

放弃@JoinColumnsOrFormulas,使用@OneToOne配合@Where注解过滤Endpoint类型,这种方式更符合Hibernate 5的规范:

@Entity
@Table(name = "TABLE_CONNECTION")
@Immutable
public class Connection {

    @Id
    @Column(name = "ID", unique = true, nullable = false, precision = 38, scale = 0)
    private BigDecimal id;

    @OneToOne(fetch = FetchType.LAZY, mappedBy = "connection")
    @Where(clause = "ENDPOINT_TYPE = 'FROM'")
    private Endpoint fromEndpoint;

    @OneToOne(fetch = FetchType.LAZY, mappedBy = "connection")
    @Where(clause = "ENDPOINT_TYPE = 'TO'")
    private Endpoint toEndpoint;

    // ...其他代码
}

同时把Endpoint中的connectionId字段名改为connection,避免命名歧义:

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "CON_ID", nullable = false)
private Connection connection;

方案2:修正原有映射的细节

如果要保留@JoinColumnsOrFormulas,需要补充关联的非空约束,并确保字段命名无歧义:

  1. 在Connection的关联上添加optional = false:
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumnsOrFormulas({
    @JoinColumnOrFormula(column = @JoinColumn(name = "ID", referencedColumnName = "CON_ID", nullable = false)),
    @JoinColumnOrFormula(formula = @JoinFormula(value = "'FROM'", referencedColumnName = "ENDPOINT_TYPE"))
})
private Endpoint fromEndpoint;
  1. 将Endpoint的connectionId字段重命名为connection,避免和主键ID混淆。

方案3:升级Hibernate版本

该兼容性问题在Hibernate 5.4及以上版本中已被修复,升级到5.6.x等稳定版本可以直接解决问题,同时获得更多bug修复和性能优化。

内容的提问来源于stack exchange,提问作者Tomáš Záluský

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 20:05:01