JPA/Hibernate 具有共享主键的单向一对一映射
我很难尝试建立单向的一对一关系来与 JPA(提供者:Hibernate)一起工作。在我看来,这应该不会太麻烦,但显然 JPA / Hibernate 不同意这一点;-)
问题是我必须映射一个我无法更改的遗留模式,并且该模式在两个实体之间使用共享主键同时也是一个实体的外键。
我创建了一个简单的测试用例:
DB 如下所示:
CREATE TABLE PARENT (PARENT_ID Number primary key, Message varchar2(50));
CREATE TABLE CHILD (CHILD_ID Number primary key, Message varchar2(50),
CONSTRAINT FK_PARENT_ID FOREIGN KEY (CHILD_ID )REFERENCES PARENT (PARENT_ID));
CREATE SEQUENCE SEQ_PK_PARENT START WITH 1 INCREMENT BY 1 ORDER;
父级(=一对一的拥有方)如下所示:
@Entity
@Table(name = "PARENT")
public class Parent implements java.io.Serializable {
private Long parentId;
private String message;
private Child child;
@Id
@Column(name = "PARENT_ID", unique = true, nullable = false, precision = 22, scale = 0)
@SequenceGenerator(name="pk_sequence", sequenceName="SEQ_PK_PARENT")
@GeneratedValue(generator="pk_sequence", strategy=GenerationType.SEQUENCE)
public Long getParentId() {
return this.parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
@Column(name = "MESSAGE", length = 50)
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
@OneToOne (cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn(name="PARENT_ID", referencedColumnName="CHILD_ID")
public Child getTestOneToOneChild() {
return this.child;
}
public void setTestOneToOneChild(Child child) {
this.child = child;
}
}
子级:
@Entity
@Table(name = "TEST_ONE_TO_ONE_CHILD", schema = "EXTUSER")
public class Child implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private Long childId;
private String message;
public Child() {
}
public Child(String message) {
this.message = message;
}
@Id
@Column(name = "CHILD_ID")
public Long getChildId() {
return this.childId;
}
public void setChildId(Long childId) {
this.childId = childId;
}
@Column(name = "MESSAGE", length = 50)
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
}
我完全看到 JPA 不知道如何为子级分配 id 的问题。然而,我也尝试使用 Hibernates“外来”密钥生成器,但也没有成功,因为它需要从子级向后引用父级,这是不可取的。 这个问题对我来说似乎并不少见,那么我在这里错过了什么?有解决办法吗?如果纯 JPA 不能提供解决方案,我还可以使用 hibernate 扩展。
我对正确行为的期望是: 如果我尝试保留附加有子项的父项:
- 从序列中获取 ID,将其设置在父项上
- 保留
- 父项在子项上设置父项的 ID
- 保留子项
如果我尝试保留“独立”子项(例如,entityManager.persist(aChild))我期望出现 RuntimeException。
非常感谢任何帮助!
I'm having a very hard time trying to get a unidirectional one-to-one relationship to work with JPA (Provider: Hibernate). In my opinion this should not be too much of a hassle but apparently JPA / Hibernate disagrees on that ;-)
The problem is that I have to map a legacy schema which I cannot change and that this schema uses a shared primary key between two entities which at the same time is the foreign key for one entity.
I created a simple TestCase:
DB looks as follows:
CREATE TABLE PARENT (PARENT_ID Number primary key, Message varchar2(50));
CREATE TABLE CHILD (CHILD_ID Number primary key, Message varchar2(50),
CONSTRAINT FK_PARENT_ID FOREIGN KEY (CHILD_ID )REFERENCES PARENT (PARENT_ID));
CREATE SEQUENCE SEQ_PK_PARENT START WITH 1 INCREMENT BY 1 ORDER;
The parent(=owning side of one-to-one) looks as follows:
@Entity
@Table(name = "PARENT")
public class Parent implements java.io.Serializable {
private Long parentId;
private String message;
private Child child;
@Id
@Column(name = "PARENT_ID", unique = true, nullable = false, precision = 22, scale = 0)
@SequenceGenerator(name="pk_sequence", sequenceName="SEQ_PK_PARENT")
@GeneratedValue(generator="pk_sequence", strategy=GenerationType.SEQUENCE)
public Long getParentId() {
return this.parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
@Column(name = "MESSAGE", length = 50)
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
@OneToOne (cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn(name="PARENT_ID", referencedColumnName="CHILD_ID")
public Child getTestOneToOneChild() {
return this.child;
}
public void setTestOneToOneChild(Child child) {
this.child = child;
}
}
The child:
@Entity
@Table(name = "TEST_ONE_TO_ONE_CHILD", schema = "EXTUSER")
public class Child implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private Long childId;
private String message;
public Child() {
}
public Child(String message) {
this.message = message;
}
@Id
@Column(name = "CHILD_ID")
public Long getChildId() {
return this.childId;
}
public void setChildId(Long childId) {
this.childId = childId;
}
@Column(name = "MESSAGE", length = 50)
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
}
I totally see the problem that JPA does not know how to assign the id for the child. However I also tried using Hibernates "foreign" key Generator with also no success because that one needs to have a back reference to the parent from child which is not desirable.
This problem does not seem too uncommon to me, so what am I missing here? Is there a solution at all? I can also use hibernate extensions if pure JPA does not provide a solution.
My expectations for a correct behavior would be:
If I try to persist the parent with a child attached:
- get ID from sequence, set it on the parent
- persist parent
- set parent's ID on child
- persist child
If I try to persist a "standalone" child (e.g. entityManager.persist(aChild)) I would expect a RuntimeException.
Any help is greatly appreciated!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
对于您描述的数据库架构,您可以使用 @MapsId 对依赖类(您的 Child 类)进行注释以实现映射回父级,如下所示:
添加从父级到子级的映射,您可以使用您列出的 @PrimaryKeyJoinColumn 注释,从而实现完整的双向一对一-one 映射如下所示:
我使用字段而不是方法访问(并删除了与关系无关的任何内容),但它与应用于 getter 的注释相同。
另请参阅第 2.2.3.1 节的最后一点 这里是@MapsId的另一个例子。
For the db schema you described, you can use @MapsId annotation on the dependent class (your Child class) to achieve the mapping back to the parent, like so:
Adding the mapping from parent to child you use the @PrimaryKeyJoinColumn annotation as you had listed, making the complete bi-directional one-to-one mapping look like this:
I used field rather than method access (and removed anything extraneous to the relationships), but it would be the same annotations applied to your getters.
Also see the last bit of section 2.2.3.1 here for another example of @MapsId.
好吧,如果子对象只有在有父对象的情况下才能存在,那么它们之间就存在一种关系。你可能只是不想用OO来表达,但它确实存在于关系模型中。
也就是说,我想说,自然的解决方案是让孩子有一个父母。
但如果您真的不想这样做,我建议您考虑将 ID 映射为 PK 类,并使用 @EmbeddedId 与两个类共享它们。我很确定它会解决您的问题,但有一个例外:
如果您决定在 PK 类中使用 @EmbeddedId 方法,我认为您需要将上述情况作为“业务规则”来处理。
Well, if a Child can only exist if there's a Parent, then there is a relationship between them. You may just not want to express in OO, but it does exist in the relational model.
That said, I would say that the natural solution for this is to have a Parent in the Child.
But if you really don't want to do that, I would suggest taking a look at mapping the ID as a PK class, and share them with both classes, using an @EmbeddedId. I'm pretty sure it would solve your problem, with one exception:
If you decide to use the @EmbeddedId approach in a PK class, I think you'll need to handle the above case as a "business rule".
这个问题的解决方案是在父实体上使用@PostPersist注释。
你必须在父实体类中创建一个方法,并用@PostPersist注解,这样父实体持久化后就会调用该方法,并且在该方法中只需设置子实体类的id即可。请参阅下面的示例。
The solution of this problem is using @PostPersist annotation on the parent Entity.
You have to create a method in the parent entity class and annotate it with @PostPersist, so this method will be invoked after parent entity is persist, and in this method just set the id of the child entity class. See the example below.