JPA OneToMany 列表找不到应该继承的 mappedBy 属性

JPA OneToMany list does not find mappedBy property which should be inherited

我们目前正在处理一些要求,我们必须将一些相似的实体(汽车图片、宠物图片、假日图片等)添加到数据库中,这些实体都属于一个所有者(人)。我们不直接 link 字节数组,而是使用引用。稍后可能会添加更多的图片类型,因此为了保持较低的复杂性,我们希望直接将其 link 转换为 Java Classes 以便我们可以使用 instance of 和类似的东西。我们想将 @Inheritance 用于超级 class PictureRef,它包含公共属性以及对人的 links。然后是另一个实体 Person,它将包含这些子 class 的列表。这是具有 mappedBy 属性的 OneToMany 关系。此 mappedBy 属性未知,因此 JPA returns 我们遇到此错误:

Caused by: org.hibernate.AnnotationException: mappedBy reference an unknown
target entity property:  de.company.project.somepackages.PictureRef.person 
in de.company.project.somepackages.Person.picturesOfCars

我认为下面的代码最能说明这一点。为了便于阅读,我删除了其他属性以及 getters/setters 和 id 序列。

1) 所有 JPA 实体都派生自一个包含 id 和审计值的抽象实体。这与其他 subclasses 一起正常工作,所以我认为这 class 不会导致问题。

Class 抽象实体

@MappedSuperclass
public abstract class AbstractEntity implements Serializable {
@Id
// Sequence definition removed
private Long id;
// other values are following //
}

2) 然后我们有一个superclass,它必须包含一个共同的属性。所有派生的classes都要写成一个table(因为它们看起来真的很相似)。我们还将拥有直接与该实体一起工作的业务逻辑,例如按id加载或删除。

Class 图片参考

@Entity
@Table(name = "t_picture_ref")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "picture_type")
// Sequence Definition removed
public class PictureRef extends AbstractEntity {
// common attributes, e.g. name or link to file //

@ManyToOne
@JoinColumn(name = "person_id")
private Person person;
}

3) 那么至少有两个子class。更多即将到来。它们将包含仅与此类图片相关的属性。

Class CarPictureRef

@Entity
@DiscriminatorValue("car")
public class CarPictureRef extends PictureRef {

@Column(name="licence_plate_visible")
private boolean licensePlateVisible;
}

Class 假日图片参考

@Entity
@DiscriminatorValue("holiday")
public class HolidayPictureRef extends PictureRef {

@Column(name="weather_condition")
private String weatherCondition;
}

4) 然后是拥有/上传所有这些图片的人。该人有每种图片的列表,因为应用程序对它们的处理方式非常不同。每个列表都包含具体的 subclasses,但是对于 mappedBy 属性,我们使用来自 superclass PictureRefperson。或许这种继承是不可能的?

Class

@Entity
@Table(name = "t_person")
// Sequence Definition removed
public class Person extends AbstractEntity {

@OneToMany(mappedBy = "person", targetEntity = CarPictureRef.class)
private List<CarPictureRef> picturesOfCars;

@OneToMany(mappedBy = "person", targetEntity = HolidayPictureRef.class)
private List< HolidayPictureRef> picturesOfHolidays;

// a lot of other fields following //
}

一种解决方法可能是只将具有所有属性的所有内容存储在一个 table 中(无论如何我们都想这样做),然后也只存储在一个实体中 PictureRef。然后我们将在后端编写应用程序逻辑来评估 pictureType 并为相应的业务案例创建新的 classes。但这似乎有点难看——我希望有一个针对这个常见用例的 JPA 解决方案? 也许我们只是缺少一个或多个注释?


为了完整起见,我添加了完整的堆栈跟踪。我们正在使用 Hibernate 4.3.8.Final 并且在部署到 WildFly 8.2.0.Final.

期间发生错误
21:19:24,283 ERROR [org.jboss.msc.service.fail] (ServerService Thread Pool -- 88) MSC000001: Failed to start service jboss.persistenceunit."Example-1.0-SNAPSHOT.war#ExamplePU": org.jboss.msc.service.StartException in service jboss.persistenceunit."Example-1.0-SNAPSHOT.war#ExamplePU": javax.persistence.PersistenceException: [PersistenceUnit: ExamplePU] Unable to build Hibernate SessionFactory
at org.jboss.as.jpa.service.PersistenceUnitServiceImpl.run(PersistenceUnitServiceImpl.java:172) [wildfly-jpa-8.2.0.Final.jar:8.2.0.Final]
at org.jboss.as.jpa.service.PersistenceUnitServiceImpl.run(PersistenceUnitServiceImpl.java:117) [wildfly-jpa-8.2.0.Final.jar:8.2.0.Final]
at java.security.AccessController.doPrivileged(Native Method) [rt.jar:1.8.0_25]
at org.wildfly.security.manager.WildFlySecurityManager.doChecked(WildFlySecurityManager.java:474) [wildfly-security-manager-1.0.0.Final.jar:1.0.0.Final]
at org.jboss.as.jpa.service.PersistenceUnitServiceImpl.run(PersistenceUnitServiceImpl.java:182) [wildfly-jpa-8.2.0.Final.jar:8.2.0.Final]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [rt.jar:1.8.0_25]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [rt.jar:1.8.0_25]
at java.lang.Thread.run(Thread.java:745) [rt.jar:1.8.0_25]
at org.jboss.threads.JBossThread.run(JBossThread.java:122) [jboss-threads-2.1.1.Final.jar:2.1.1.Final]
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: ExamplePU] Unable to build Hibernate SessionFactory
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(EntityManagerFactoryBuilderImpl.java:1239) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.access0(EntityManagerFactoryBuilderImpl.java:120) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.perform(EntityManagerFactoryBuilderImpl.java:855) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.perform(EntityManagerFactoryBuilderImpl.java:845) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:398) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:844) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
at org.jboss.as.jpa.hibernate4.TwoPhaseBootstrapImpl.build(TwoPhaseBootstrapImpl.java:44) [jipijapa-hibernate4-3-1.0.1.Final.jar:]
at org.jboss.as.jpa.service.PersistenceUnitServiceImpl.run(PersistenceUnitServiceImpl.java:154) [wildfly-jpa-8.2.0.Final.jar:8.2.0.Final]
... 8 more
Caused by: org.hibernate.AnnotationException: mappedBy reference an unknown target entity property:  
de.company.project.somepackages.PictureRef.person in de.company.project.somepackages.Person.picturesOfCars
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:768) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.cfg.annotations.CollectionBinder.secondPass(CollectionBinder.java:728) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:70) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1697) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1426) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1846) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.perform(EntityManagerFactoryBuilderImpl.java:852) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
... 13 more

21:19:24,291 ERROR [org.jboss.as.controller.management-operation] (management-handler-thread - 2) JBAS014613: Operation ("deploy") failed - address: ([("deployment" => "Example-1.0-SNAPSHOT.war")]) - failure description: {"JBAS014671: Failed services" => {"jboss.persistenceunit.\"Example-1.0-SNAPSHOT.war#ExamplePU\"" => "org.jboss.msc.service.StartException in service jboss.persistenceunit.\"Example-1.0-SNAPSHOT.war#ExamplePU\": javax.persistence.PersistenceException: [PersistenceUnit: ExamplePU] Unable to build Hibernate SessionFactory
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: ExamplePU] Unable to build Hibernate SessionFactory
Caused by: org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: de.company.project.somepackages.PictureRef.person in de.company.project.somepackages.Person.picturesOfCars"}}
21:19:24,292 ERROR [org.jboss.as.server] (management-handler-thread - 2) JBAS015870: Deploy of deployment "Example-1.0-SNAPSHOT.war" was rolled back with the following failure message: 
{"JBAS014671: Failed services" => {"jboss.persistenceunit.\"Example-1.0-SNAPSHOT.war#ExamplePU\"" => "org.jboss.msc.service.StartException in service jboss.persistenceunit.\"Example-1.0-SNAPSHOT.war#ExamplePU\": javax.persistence.PersistenceException: [PersistenceUnit: ExamplePU] Unable to build Hibernate SessionFactory
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: ExamplePU] Unable to build Hibernate SessionFactory
Caused by: org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: Caused by: org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: de.company.project.somepackages.PictureRef.person in de.company.project.somepackages.Person.picturesOfCars"}}

您触及了 JPA 规范中没有详细介绍的区域。 2.11 的 JPA 规范指出:

An entity may inherit from another entity class. Entities support inheritance, polymorphic associations, and polymorphic queries.

确切的支持是什么还没有定义。

当您尝试将 @OneToMany 映射到继承 @ManyToOne 关系的子类时......

public class Person extends AbstractEntity {

    @OneToMany(mappedBy = "person", targetEntity = CarPictureRef.class)
    private List<CarPictureRef> picturesOfCars;

    @OneToMany(mappedBy = "person", targetEntity = HolidayPictureRef.class)
    private List< HolidayPictureRef> picturesOfHolidays;

    ...

}

Hibernate 抱怨

mappedBy reference an unknown target entity property

因此,在将 mappedBy 解析为子类时,Hibernate 不允许继承字段(在本例中为 person)。然而,关系定义中有足够的信息来解析这些关系,EclipseLink 做得很好。

可以将 @ManyToOne 从超级 (PictureRef) 转移到具体子类(CarPictureRef 等)

@Entity
public class CarPictureRef extends PictureRef {

    @ManyToOne
    @JoinColumn(name = "person_id")
    private Person person;

    ...

}

使用此解决方案,您可以将 Person 中的 @OneToMany 映射保持原样。不幸的是,这在 Hibernate 中不起作用,因为 Hibernate 将查找具有正确 person_ID 的任何子实体(在本例中为任何 pictureRef 子类),然后抱怨找到的类型是错误的类型

org.hibernate.WrongClassException: Object [id=55] was not of the specified subclass.

同样,Hibernate 可以通过使用 Discriminator 列来解决这个问题,但它没有,而且,这是 EclipseLink 做的事情。

解决此问题的方法是为每个子类指定不同的连接列,这意味着随着每个新子类的出现,您需要修改 table,我认为这并不理想。

使用 Hibernate,您可以将 PictureRef 超类作为您的@OneToMany 关系的目标,这样 Person 实体就变成了;

@Entity
@Table(name = "t_person")
// Sequence Definition removed
public class Person extends AbstractEntity {

    @OneToMany(mappedBy = "person", targetEntity = PictureRef.class)
    private List<PictureRef> pictures;

    ...

}

然后您将拥有一个 PictureRef 列表,这些 PictureRef 将是任何子类。您可以测试列表中的项目并设置包含每个所需子类型的瞬态字段。所以在这个程度上,Hibernate 支持多态关联,但是 eclipseLink 做的更多,所以要注意可移植性问题。

对于类似的情况,可以通过使用@JoinColumn 而不是mappedBy 和@where(子句="discriminator value")来解决它。 但是奇怪的是,这个问题从2008年就开始出现了,为什么到现在还没有解决。 check this