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

5

我们目前正在处理一些需求,需要将一些类似的实体(汽车图片、宠物图片、假日照片等)添加到数据库中,这些实体都属于同一个所有者(人)。我们不直接链接字节数组,而是使用引用。以后可能会添加更多类型的图片,为了保持复杂度低,我们想直接将其链接到Java类,这样我们就可以使用instance of和类似的东西。我们想使用@Inheritance来创建一个超类PictureRef,其中包含公共属性并链接到人。然后有另一个实体Person,它将具有这些子类的列表。这是一个OneToMany关系,具有一个mappedBy属性。这个mappedBy属性是未知的,所以JPA返回给我们这个错误:

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

我认为下面的代码可以最好地说明。为了提高可读性,我删除了其他属性、getter/setter和id序列。

1)所有的JPA实体都是从一个抽象实体派生出来的,该实体包含id和审计值。这个类与其他子类一起正常工作,所以我认为这个类不会引起问题。

AbstractEntity类

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

2) 然后我们有一个超类,必须包含共同的属性。所有派生类都必须写在一个表中(因为它们看起来非常相似)。此外,我们还将有直接与此实体一起工作的业务逻辑,例如按ID加载或删除。

PictureRef

@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) 然后至少有两个子类。即将有更多内容。它们将包含仅适用于此类型图片的属性。

CarPictureRef

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

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

HolidayPictureRef

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

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

4) 然后有一个拥有/上传所有这些图片的人。这个人为每种类型的图片都有一个列表,因为应用程序会根据它们进行不同的处理。每个列表包含具体的子类,但是对于mappedBy属性,我们使用超类PictureRef中的person。也许这种继承不可能?

Person

@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 //
}

一种解决方法可能是将所有带属性的内容都存储在一个表格中(我们本来就想这样做),然后也只在一个实体PictureRef中存储。然后我们将在后端编写应用程序逻辑来评估pictureType并为相应的业务情况创建新类。但这似乎有些丑陋 - 我期望有一个 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$1$1.run(PersistenceUnitServiceImpl.java:172) [wildfly-jpa-8.2.0.Final.jar:8.2.0.Final]
at org.jboss.as.jpa.service.PersistenceUnitServiceImpl$1$1.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$1.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.access$600(EntityManagerFactoryBuilderImpl.java:120) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:855) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.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$1$1.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$1.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$4.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"}}
2个回答

3
您碰到了一个在JPA规范中没有详细介绍的领域。JPA规范2.11节声明:

实体可以从另一个实体类继承。实体支持继承、多态关联和多态查询。

具体的支持方式并未定义。

当您试图将@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虽然可以通过使用鉴别器列来解决这个问题,但它并没有这样做,而EclipseLink则可以。

解决这个问题的方法是为每个子类指定一个不同的连接列,这意味着每当出现新的子类时,您都需要修改您的表格,我想这不是很理想。

使用Hibernate,您可以针对@OneToMany关系中的PictureRef超类进行定位,从而使Person实体变成:

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

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

    ...

}

然后您将拥有一个PictureRefs列表,其中包含任何子类。您可以测试列表中的项并设置临时字段来持有所需的每个子类型。因此,在这种程度上,Hibernate支持多态关联,但是eclipseLink做得更多,因此请注意可移植性问题。


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Christian
实际上,你的方法在EclipseLink中似乎运行良好... 请问你能够发布导致错误的持久化调用的服务代码吗? - NickJI
EclipseLink有趣的地方。所以可能是一个Hibernate问题,而不是JPA的一般性问题。错误发生在启动期间,应用程序部署时发生。我添加了完整的堆栈跟踪 - 如果那有任何帮助的话。 - Christian
感谢您的第二个(/编辑后的)答案!对于我们当前的功能,我们将坚持使用Hibernate,并将所有图片存储为一个实体,在检索时根据类型列进行过滤。从长远来看,切换到Eclipse Link是不可避免的。我很惊讶Hibernate不支持这种在我看来并不罕见的用例。 - Christian

-1

对于类似的情况,可以通过使用@JoinColumn而不是mappedBy以及@where(clause="discriminator value")来解决它。 然而,奇怪的是,尽管这个问题自2008年以来就存在,但为什么它仍然没有得到解决。 查看此链接


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