Hibernate是否能够处理重叠的外键?

14

我有一张表,其中有两个外键,分别指向两个不同的表,这两个外键都共用一个列

CREATE TABLE ZipAreas
(
  country_code CHAR(2) NOT NULL,
  zip_code VARCHAR(10) NOT NULL,
  state_code VARCHAR(5) NOT NULL,
  city_name VARCHAR(100) NOT NULL,
  PRIMARY KEY (country_code, zip_code, state_code, city_name),
  FOREIGN KEY (country_code, zip_code) REFERENCES Zips (country_code, code),
  FOREIGN KEY (country_code, state_code, city_name) REFERENCES Cities (country_code, state_code, name)
)

正如您所看到的,有两个外键共享country_code(巧合地引用了引用路径末尾相同的列)。 实体类如下(JPA 1.0 @IdClass):

@Entity
@Table(name = "ZipAreas")
@IdClass(value = ZipAreaId.class)
public class ZipArea implements Serializable
{
    @Id
    @Column(name = "country_code", insertable = false, updatable = false)
    private String countryCode;

    @Id
    @Column(name = "zip_code", insertable = false, updatable = false)
    private String zipCode;

    @Id
    @Column(name = "state_code", insertable = false, updatable = false)
    private String stateCode;

    @Id
    @Column(name = "city_name", insertable = false, updatable = false)
    private String cityName;

    @ManyToOne
    @JoinColumns(value = {@JoinColumn(name = "country_code", referencedColumnName = "country_code"), @JoinColumn(name = "zip_code", referencedColumnName = "code")})
    private Zip zip = null;

    @ManyToOne
    @JoinColumns(value = {@JoinColumn(name = "country_code", referencedColumnName = "country_code", insertable = false, updatable = false), @JoinColumn(name = "state_code", referencedColumnName = "state_code"), @JoinColumn(name = "city_name", referencedColumnName = "name")})
    private City city = null;

    ...
}

如您所见,我将countryCode属性和city的country_code @JoinColumn标记为只读(insertable = false,updatable = false)。 但是,Hibernate出现了以下错误:

Exception in thread "main" javax.persistence.PersistenceException: [PersistenceUnit: geoinfo] Unable to configure EntityManagerFactory
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:374)
    at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:56)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:48)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:32)
    at tld.geoinfo.Main.main(Main.java:27)
Caused by: org.hibernate.AnnotationException: Mixing insertable and non insertable columns in a property is not allowed: tld.geoinfo.model.ZipAreacity
    at org.hibernate.cfg.Ejb3Column.checkPropertyConsistency(Ejb3Column.java:563)
    at org.hibernate.cfg.AnnotationBinder.bindManyToOne(AnnotationBinder.java:2703)
    at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1600)
    at org.hibernate.cfg.AnnotationBinder.processIdPropertiesIfNotAlready(AnnotationBinder.java:796)
    at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:707)
    at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:3977)
    at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3931)
    at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1368)
    at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1345)
    at org.hibernate.ejb.Ejb3Configuration.buildMappings(Ejb3Configuration.java:1477)
    at org.hibernate.ejb.EventListenerConfigurator.configure(EventListenerConfigurator.java:193)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:1096)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:278)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:362)
    ... 4 more

在我看来,这似乎相当基础。 "在属性中混合可插入和不可插入的列是不允许的",这个理由太弱了,不是吗?

Hibernate应该能够处理这个问题,例如根据JPA规范吗?这是一个bug吗?


Hibernate 不喜欢复合主键 - 在某些情况下,比如这种情况,它会出现问题。尝试重构你的数据库以使用单列主键。 - Andreas Petersson
是的,Hibernate在复合键方面表现不佳。无论如何,这从未被报告过,现在已经有了:http://opensource.atlassian.com/projects/hibernate/browse/HHH-6221 - Kawu
4个回答

36

有一种方法可以绕过验证并使其工作,从而表明该列是“@JoinColumnsOrFormulas”,然后提供解决方案:

错误:

@ManyToOne
@JoinColumns(value = {
    @JoinColumn(name = "country_code", referencedColumnName = "country_code"), 
    @JoinColumn(name = "zip_code", referencedColumnName = "code")})
private Zip zip = null;

@ManyToOne
@JoinColumns(value = {
    @JoinColumn(name = "country_code", referencedColumnName = "country_code", insertable = false, updatable = false),
    @JoinColumn(name = "state_code", referencedColumnName = "state_code"), 
    @JoinColumn(name = "city_name", referencedColumnName = "name")})
private City city = null;

好的:

@ManyToOne
@JoinColumns(value = {
    @JoinColumn(name = "country_code", referencedColumnName = "country_code"), 
    @JoinColumn(name = "zip_code", referencedColumnName = "code")})
private Zip zip = null;

@ManyToOne
@JoinColumnsOrFormulas(value = {
    @JoinColumnOrFormula(formula = @JoinFormula(value = "country_code", referencedColumnName = "country_code")),
    @JoinColumnOrFormula(column = @JoinColumn(name = "state_code", referencedColumnName = "state_code")),
    @JoinColumnOrFormula(column = @JoinColumn(name = "city_name", referencedColumnName = "name"))
})
private City city = null;

敬礼,


9
我认为这应该是被采纳的答案,因为它提供了一个现在就可以工作的解决方案,而不是等到 Hibernate 5 正式发布时才能使用。 - azerole
@ManuNavarro 为什么在好样本中城市属性的列数比坏样本多? - banterCZ
你好@banterCZ,向左移动并查看第三个关系。 - Manu Navarro
在我的情况下,我从公式转换为列得到了一个不可能的转换。该公式实际上会强制执行只读状态,但似乎在这里不起作用。 - Ben
3
根据我所读的内容,如果在@Id列上使用公式(Formula),从Formula到Column的ClassCastException无法生效。Formula将导致它显式不可插入,而Hibernate希望它们是可插入的:https://dev59.com/JGIk5IYBdhLWcg3wL7bj - Ben

10

3
看起来这已经被移至 Hibernate 6。 - Daniel Scott
2
这是Hibernate Atlassian问题页面上的问题:https://hibernate.atlassian.net/browse/HHH-6221 - Ben
创建于2011年。最近更新于2017年底。是什么让这个特性如此难以包含? - Jordan Mackie

5

在Hibernate 5中仍然无法解决这个问题。 然而,如果我使用@JoinColumnsOrFormulas就会出现ClassCastException。 将所有的连接列都加上insertable = false, updatable = false就可以解决我的问题:

例如:

@ManyToOne
@JoinColumns(value = {
    @JoinColumn(name = "country_code", referencedColumnName = "country_code", insertable = false, updatable = false),
    @JoinColumn(name = "state_code", referencedColumnName = "state_code", insertable = false, updatable = false), 
    @JoinColumn(name = "city_name", referencedColumnName = "name", insertable = false, updatable = false)})
private City city = null;

非常感谢! - RoutesMaps.com

2

我正在使用Hibernate 5,但仍然遇到了异常。如果您只将insert="false",update="false"添加到其中一个,您将收到一个异常,指出您混合了可插入和不可插入的列,这是不允许的。这是一个已经在跟踪器中的问题,但似乎尚未得到解决。Hibernate throws AnnotationException on column used by multiple overlapping foreign keys

在我们的情况下,这意味着我们迁移到了EclipseLink,这实际上相当容易,因为您主要需要替换persistence.xml并将HSQL(Hibernate SQL)重写为JPQL(JPA SQL)。此外,您可能需要替换自定义命名策略(Eclipse称其为SessionCustomizer)。当然,如果您使用Hibernate的特殊功能,例如Hibernate Search等,则可能更难做到。但在我们的情况下,我们试图修复重叠的外键数周,而最终迁移只花费了几个小时。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接