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

时间:2012-03-27 20:49:15

标签: java hibernate annotations mapping one-to-one

我有父子关系OneToOne,但其中有两个。注释不好但它产生了良好的数据库模式,但代码不起作用。如果我尝试保存Parent实例,Hibernate首先会尝试保存child1和child2 - 但它会破坏Child中定义的FK - >因为所有者在DB中还不存在...所以我需要保存Parent,然后保存child1和child2。

如果我能做到这一点没有帮助,因为当我尝试加载Parent时,Hibernate将不知道Child表中的哪条记录属于child1或child2 ...所以在这种情况下我需要指定一个条件在join1中加入类似“where type = 1”和child2“where type = 2”...

只是为了澄清:在Child表中,Parent ChildType.A(始终为child1)和零个或一个ChildType.B的孩子将有零个或一个孩子(总是孩子2)。

我需要保存看起来像这样的xml:

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

child1child2元素都是相同类型,因此是java类中Child的类型。唯一的区别是元素名称(在java中我将它们与ChildType区分开来)。只有来自父母的idoid属性才能识别儿童身份。他们在Parent中指向另一个target,因此Child

我需要以某种方式更改注释以使其正常工作......你们有一些想法,因为我真的被卡住了吗?

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 approach但是仍然存在加载问题 - 我无法告诉hibernate哪个子记录对哪个子类成员变量感兴趣。

3 个答案:

答案 0 :(得分:2)

我的解决方案中没有太多东西可以给出一个好的答案但只是一些想法:

考虑使用继承而不是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中:

public abstract Parent getParent();

现在整个家长/所有者/目标事物我仍然没有完全掌握。

答案 1 :(得分:2)

我发现使用一对一引用存在两个问题:

  1. Parent有零个或一个child1,零个或一个child2。我的理解是,当它总是一对一时,你会使用一对一。
  2. 您的ID变得非常复杂。一对一的想法是两个实体之间的ID是相同的,并且你有一个由两部分组成的主键和一个具有不同四部分主键的Child。
  3. 2.的另一个附录是,如果你使用1-1,那么父母和孩子的ID应该是相同的。但如果父母有两个孩子,他们不能都有相同的身份证!所以有一个真正的数据建模问题。此外,子类型是键的一部分也是一个难闻的气味,因为我希望子类型是您业务逻辑的一部分,

    我认为你想要的是Hibernate所谓的'外键上的单向一对一关联'。 Hibernate ORM manual显示了如何在XML中执行此操作,这里是带注释的example,但没有设置唯一属性。

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

    1. 使用@Id和@GeneratedValue(strategy = GenerationType.AUTO)将父和子更改为每个必须有一个字段主键。
    2. 更改Parent以将child1和child2的引用设为@ManyToOne(unique = true)
    3. 如果需要,请按照Hibernate手册中的详细说明更改Child,以便它引用Parent。
    4. 您将失去父级和子级具有相同ID的“功能”,但会在对象ID方案中获得大量简化,从而使数据库操作更快,代码更易于阅读。

      如果您没有说明为什么父母和/或孩子需要多部分主键的外部原因,则需要对此答案进行一些修改。

      我还提到@barsju关于使用继承甚至是完全独立的类来做两个Child类型的观点。如果您按照我在这里布局的方式使用ID,您可以让Parent引用具体的子类型,查询将正常工作。

答案 2 :(得分:1)

使用@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工作,它们与您的问题没有任何关系。过滤器应该以相同的方式工作。