Hibernate支持将一对一关联作为主键吗?

4
有人能告诉我Hibernate是否支持将关联作为实体的主键吗?我认为这应该是受支持的,但我在尝试任何表示此类关系的映射时都遇到了很多麻烦。特别是对于下面的直接映射:
@Entity
public class EntityBar
{
    @Id
    @OneToOne(optional = false, mappedBy = "bar")
    EntityFoo foo

    // other stuff
}

我遇到了一个org.hibernate.MappingException错误:“无法确定EntityFoo的类型,表为ENTITY_BAR,列为[org.hibernate.mapping.Column(foo)]”。
深入代码后发现,ID始终被视为值类型;即“任何通过值而不是引用持久化的东西。它本质上是一个Hibernate Type,连同零个或多个列。”我可以通过声明它可序列化来使我的EntityFoo成为值类型,但我不认为这会导致正确的结果。
我原以为Hibernate会将列的类型视为整数(或父ID的实际类型),就像对于普通的一对一链接一样,但当我将其声明为ID时,似乎并没有发生这种情况。我是否超出了尝试将@OneToOne与@Id组合的可能性?如果是这样,那么如何合理地建模这种关系呢?

1
我不确定是否理解。也许提供一个示例映射,以及在这种情况下Hibernate选择其他实体会有所帮助。“外部”ID生成器是什么意思? - Thierry
好的,我已经重新表述了我的问题,以解决实际的根本问题,这可能更容易理解。 - Andrzej Doyle
4个回答

2
如果目标是拥有一个共享的主键,那么可以考虑以下方法(受《Java Persistence With Hibernate》样例启发,在宠物数据库上测试过):
@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    private Address shippingAddress;

    //...
}

这是首先插入并获得生成ID的“父”类。 地址 的样子如下:

@Entity
public class Address implements Serializable {
    @Id @GeneratedValue(generator = "myForeignGenerator")
    @org.hibernate.annotations.GenericGenerator(
        name = "myForeignGenerator",
        strategy = "foreign",
        parameters = @Parameter(name = "property", value = "user")
    )
    @Column(name = "ADDRESS_ID")
    private Long id;

    @OneToOne(mappedBy="shippingAddress")
    @PrimaryKeyJoinColumn
    User user;

    //...
}

使用上述实体,以下内容似乎按预期运行:

User newUser = new User();
Address shippingAddress = new Address();
newUser.setShippingAddress(shippingAddress);
shippingAddress.setUser(newUser);            // Bidirectional
session.save(newUser);

当保存一个Address时,插入的主键值与user属性引用的User实例的主键值相同。
加载UserAddress也很简单。
如果我漏掉了什么,请告诉我。
PS: 严格回答这个问题,根据一对一关系中的主键
JPA 1.0不允许在OneToOne或ManyToOne上使用@Id,但JPA 2.0允许。
但是,Hibernate的JPA 1.0兼容版本
允许在OneToOne或ManyToOne映射上使用@Id注释*。
我无法使Hibernate EM 3.4正常工作(它可以在Hibernate EM 3.5.1中工作,即JPA 2.0实现)。也许我做错了什么。
无论如何,使用共享主键似乎提供了一种有效的解决方案。

这个并不完全满足我的情况,因为我不能在地址表中有一个单独的列映射到用户(遗留架构)。然而,我在原始帖子中没有明确指定这一点,所以你已经回答了问题,值得获得奖励。 - Andrzej Doyle
@Andrzej 谢谢。只是为了澄清,使用上述映射并没有在地址表中创建一个单独的列来映射用户(这很合理,因为它们共享相同的值)。在Derby上测试过。如果您想要,我可以提供DDL。 - Pascal Thivent

1

是的,这是可能的。

看下面的示例,使用 Driver 和 DriverId 类作为 Driver 的 ID。

@Entity
public class Drivers {

private DriversId id; //The ID which is located in another class

public Drivers() {
}

@EmbeddedId
@AttributeOverrides({
        @AttributeOverride(name = "personId", column = @Column(name = "person_id", nullable = false))})
@NotNull
public DriversId getId() {
    return this.id;
}
   //rest of class
}

在这里,我们使用personId作为Driver的id

而DriversId类如下:

//composite-id class must implement Serializable
@Embeddable
public class DriversId implements java.io.Serializable {
private static final long serialVersionUID = 462977040679573718L;

private int personId;

public DriversId() {
}

public DriversId(int personId) {
    this.personId = personId;
}

@Column(name = "person_id", nullable = false)
public int getPersonId() {
    return this.personId;
}

public void setPersonId(int personId) {
    this.personId = personId;
}

public boolean equals(Object other) {
    if ((this == other))
        return true;
    if ((other == null))
        return false;
    if (!(other instanceof DriversId))
        return false;
    DriversId castOther = (DriversId) other;

    return (this.getPersonId() == castOther.getPersonId());
}

public int hashCode() {
    int result = 17;

    result = 37 * result + this.getPersonId();
    return result;
}
}

我不认为这是OP要求的内容。我的理解是,OP想要专用表,并希望将EntityFooEntityBar都映射为实体。 - Pascal Thivent

0

你可以通过在EntityFoo和EntityBar之间共享一个主键来实现这一点:

@Entity
public class EntityBar
{
    @Id @OneToOne
    @JoinColumn(name = "foo_id")
    EntityFoo foo;

    // other stuff
}

@Entity
public class EntityFoo
{
    @Id @GeneratedValue
    Integer id;

    // other stuff
}

但是EntityFoo没有与EntityBar的映射,所以当从数据库加载一个EntityFoo时,您无法获取相关的EntityBar对象。 - Andrzej Doyle

0

这里必须使用@EmbeddedId而不是@Id。 并且EntityFoo应该是Embeddable

另一种方法是使用一个整数,并使用OneToOne,将updatebleinstertable设置为false


但是 EntityFoo 不是一个可嵌入的组件,它本身就是一个一流的实体。如果我没记错的话,你无法在实体和组件之间进行 OneToOne 映射。 - Andrzej Doyle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接