实体中的两个几乎相同的OneToOne,自定义联接注释



我有父子关系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表中,对于一个ParentChildType.A(始终为child1),将有零个或一个子级,而对于ChildType.B(始终为child2),则将有零个子级或一个子级。

我需要保存如下所示的xml:

<parent id="" oid="">
   <child1 (and other attributes)>
   <child2 (and other attributes)>
<parent>

child1child2元素都是相同的类型,因此在java类中是Child的类型。唯一的区别是元素名称(在java中,我用ChildType来区分它们)。子级的唯一标识是来自父级的idoid属性。它们指向另一个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,以及零个或1个子级2。我的理解是,当总是一对一时,你会使用一对一
  2. 你的身份证越来越复杂了。一对一的想法是,两个实体之间的ID是相同的,并且您有一个由两部分组成的主键的Parent和一个由四部分组成的不同主键的Child

2。如果使用1-1,则父项和子项的ID应该相同。但是,如果父母有两个孩子,他们不可能都有相同的身份证!因此,存在一个真实的数据建模问题。此外,子类型是关键的一部分这一事实也是一种臭味,因为我认为子类型是您的业务逻辑的一部分,

我认为您想要的是Hibernate所说的"外键上的单向一对一关联"。Hibernate ORM手册展示了如何在XML中实现这一点,这里有一个Annotations的例子,尽管没有唯一的属性集。

我将假设没有外部商业原因,为什么你的主键必须是这样的,并建议你

  1. 使用@Id和@GeneratedValue(策略=GenerationType.AUTO)将Parent和Child更改为每个都必须有一个字段主键
  2. 更改Parent以使对child1和child2的引用为@ManyToOne(unique=true)
  3. 如果需要,请按照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是有效的,它们与您的问题无关。过滤器应该以同样的方式工作。

最新更新