当前,我们正在处理一些需求,我们必须在数据库中添加所有属于一个所有者(人)的相似实体(汽车,宠物,度假图片等)。我们不直接链接字节数组,而是使用引用。稍后可能会添加更多图片类型,因此为了保持较低的复杂性,我们希望将其直接链接到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
属性,我们使用超类person
中的PictureRef
。也许这种继承是不可能的?类人
@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 //
}
一种解决方法可能是仅将具有所有属性的所有内容存储在一个表中(无论如何我们都想这样做),然后也仅将其存储在一个Entity
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"}}
最佳答案
您碰到了JPA规范未涵盖的任何细节。 2.11的JPA规范指出:
一个实体可以从另一个实体类继承。实体支持
继承,多态关联和多态查询。
确切地说,没有定义该支持。
当您尝试将@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;
...
}
冬眠抱怨
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;
...
}
使用此解决方案,您可以将
@OneToMany
映射保留在Person
中。不幸的是,这在Hibernate中不起作用,因为Hibernate会寻找具有正确person_ID的任何子实体(在这种情况下为任何pictureRef
子类),然后抱怨找到的类型是错误的类型。org.hibernate.WrongClassException: Object [id=55] was not of the specified subclass.
同样,Hibernate可以通过使用Discriminator列解决此问题,但事实并非如此,这也是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的功能更多,因此请注意可移植性问题。