我有父子关系OneToOne,但有两个。注释不好,但它产生了良好的DB模式,但代码不起作用。如果我试图保存Parent实例,Hibernate首先尝试保存child1和child2,但它破坏了Child->中定义的FK,因为DB中还不存在所有者。。。所以我需要保存Parent,然后保存child1和child2。
如果我能做到这一点,那也无济于事,因为当我尝试加载Parent时,Hibernate将不知道Child表中的哪个记录属于child1或child2…所以在这种情况下,我需要在联接中为child1指定一个条件,比如"where type=1",为child2指定"where type=2"。。。
只是澄清一下:在Child
表中,对于一个Parent
和ChildType.A
(始终为child1),将有零个或一个子级,而对于ChildType.B
(始终为child2),则将有零个子级或一个子级。
我需要保存如下所示的xml:
<parent id="" oid="">
<child1 (and other attributes)>
<child2 (and other attributes)>
<parent>
child1
和child2
元素都是相同的类型,因此在java类中是Child
的类型。唯一的区别是元素名称(在java中,我用ChildType
来区分它们)。子级的唯一标识是来自父级的id
和oid
属性。它们指向另一个Parent
,因此指向Child
中的target
。
我需要以某种方式更改注释以使其工作。。。你们有什么想法吗,因为我真的被卡住了???
Parent.java
public class Parent {
private String oid
private Long id;
private Child child1;
private Child child2;
@Id
@GeneratedValue(generator = "IdGenerator")
@GenericGenerator(name = "IdGenerator", strategy = "com.example.IdGenerator")
@Column(name = "id")
public Long getId() {
return id;
}
@Id
@GeneratedValue(generator = "OidGenerator")
@GenericGenerator(name = "OidGenerator", strategy = "com.example.OidGenerator")
@Column(name = "oid", nullable = false, updatable = false, length = 36)
public String getOid() {
return oid;
}
@OneToOne(optional = true, fetch = FetchType.EAGER)
@Cascade({org.hibernate.annotations.CascadeType.ALL})
public Child getChild1() {
return child1;
}
@OneToOne(optional = true, fetch = FetchType.EAGER)
@Cascade({org.hibernate.annotations.CascadeType.ALL})
public Child getChild2() {
return child2;
}
}
Child.java
public class Child {
private Parent owner;
private String ownerOid;
private Long ownerId;
private ChildType type;
private Parent target;
@MapsId("owner")
@ManyToOne(fetch = FetchType.LAZY)
@PrimaryKeyJoinColumns({
@PrimaryKeyJoinColumn(name = "owner_oid", referencedColumnName = "oid"),
@PrimaryKeyJoinColumn(name = "owner_id", referencedColumnName = "id")
})
public Parent getOwner() {
return owner;
}
@MapsId("target")
@ManyToOne(fetch = FetchType.LAZY)
@PrimaryKeyJoinColumns({
@PrimaryKeyJoinColumn(name = "target_oid", referencedColumnName = "oid"),
@PrimaryKeyJoinColumn(name = "target_id", referencedColumnName = "id")
})
public Parent getTarget() {
return target;
}
@Id
@Column(name = "owner_id")
public Long getOwnerId() {
if (ownerId == null && owner != null) {
ownerId = owner.getId();
}
return ownerId;
}
@Id
@Column(name = "owner_oid", length = 36)
public String getOwnerOid() {
if (ownerOid == null && owner != null) {
ownerOid = owner.getOid();
}
return ownerOid;
}
@Id
@Column(name = "target_id")
public Long getTargetId() {
if (targetId == null && target != null) {
targetId = target.getId();
}
return targetId;
}
@Id
@Column(name = "target_oid", length = 36)
public String getTargetOid() {
if (targetOid == null && target != null) {
targetOid = target.getOid();
}
if (targetOid == null) {
targetOid = "";
}
return targetOid;
}
@Id
@Enumerated(EnumType.ORDINAL)
public ChildType getType() {
if (type == null) {
return ChildType.A;
}
return type;
}
}
ChildType.java
public enum ChildType {
A, B;
}
我还尝试使用mappedBy
方法mappedBy方法,但加载仍然存在问题——我无法告诉hibernate哪个子记录属于哪个子类成员变量。
在您的解决方案中,有太多的东西我不太了解,无法给出一个好的答案,只是一些想法:
请考虑使用继承而不是ChildType枚举。所以你会有ChildA和ChildB扩展Child。
这样你的父母就可以拥有:
private ChildA child1;
private ChildB child2;
我不考虑使用复合主键,而是考虑使用唯一的自动生成键,然后在其他id和oid字段上添加唯一约束。它应该让孩子和父母的关系更容易,你可以为ChildA和ChildB:有不同的父母实现
In ChildA:
@OneToOne(mappedBy="child1")
public Parent getParent() {
return parent;
}
和
In ChildB:
@OneToOne(mappedBy="child2")
public Parent getParent() {
return parent;
}
在Child just:
public abstract Parent getParent();
现在,我仍然没有完全理解家长/所有者/目标的全部内容。
我发现使用一对一引用有两个问题:
- 父级有零个或一个子级1,以及零个或1个子级2。我的理解是,当总是一对一时,你会使用一对一
- 你的身份证越来越复杂了。一对一的想法是,两个实体之间的ID是相同的,并且您有一个由两部分组成的主键的Parent和一个由四部分组成的不同主键的Child
2。如果使用1-1,则父项和子项的ID应该相同。但是,如果父母有两个孩子,他们不可能都有相同的身份证!因此,存在一个真实的数据建模问题。此外,子类型是关键的一部分这一事实也是一种臭味,因为我认为子类型是您的业务逻辑的一部分,
我认为您想要的是Hibernate所说的"外键上的单向一对一关联"。Hibernate ORM手册展示了如何在XML中实现这一点,这里有一个Annotations的例子,尽管没有唯一的属性集。
我将假设没有外部商业原因,为什么你的主键必须是这样的,并建议你
- 使用@Id和@GeneratedValue(策略=GenerationType.AUTO)将Parent和Child更改为每个都必须有一个字段主键
- 更改Parent以使对child1和child2的引用为@ManyToOne(unique=true)
- 如果需要,请按照Hibernate手册中的详细说明更改Child,使其具有对Parent的引用
您将失去父母和子女拥有相同ID的"功能",但在对象ID方案中获得了极大的简化,使数据库操作更快,代码更易于阅读。
如果您没有指定父级和/或子级需要多部分主键的外部原因,则需要对该答案进行一些修改。
我还支持@barsju关于使用继承,甚至完全独立的类来实现两个Child类型的观点。如果按照我在这里列出的方式使用ID,则可以使Parent引用具体的子类型,查询将正常工作。
使用@JoinColumns并定义过滤器
在Parent.java中:
@OneToOne(optional = true, fetch = FetchType.EAGER)
@JoinColumns ({
@JoinColumn(name="id", referencedColumnName = "owner_id"),
@JoinColumn(name="oid", referencedColumnName = "owner_oid"),
})
@FilterJoinTable(name="type", condition="type = 'A'")
@Cascade({org.hibernate.annotations.CascadeType.ALL})
public Child getChild1() {
return child1;
}
@OneToOne(optional = true, fetch = FetchType.EAGER)
@JoinColumns ({
@JoinColumn(name="id", referencedColumnName = "owner_id"),
@JoinColumn(name="oid", referencedColumnName = "owner_oid"),
})
@FilterJoinTable(name="type", condition="type = 'B'")
@Cascade({org.hibernate.annotations.CascadeType.ALL})
public Child getChild2() {
return child2;
}
我从来没有使用过过滤器,所以确切的语法可能有点不同,但这应该会让你知道它应该如何工作。
顺便说一句,如果引入一个单独的id列(而不是像现在这样的复合id),可以使设计变得更容易。在这种情况下,类型不需要是id的一部分。Hibernate对复合id并不满意。尽管如此,复合id是有效的,它们与您的问题无关。过滤器应该以同样的方式工作。