具有共享ID的JPA @OneToOne —我可以做得更好吗?


68

我正在使用一个我不想更改的现有架构。该模式在表Person和VitalStats之间具有一对一关系,其中Person具有主键,而VitalStats使用相同的字段作为其主键和对Person的外键,这意味着其值是相应PK的值人。

这些记录是由外部流程创建的,我的JPA代码从不需要更新VitalStats。对于我的对象模型,我希望我的Person类包含VitalStats成员BUT:

当我尝试

@Entity
public class Person{
    private long id;
    @Id
    public long getId(){ return id; }

    private VitalStats vs;
    @OneToOne(mappedBy = “person”)
    public VitalStats getVs() { return vs; }
}

@Entity
    public class VitalStats{
     private Person person;
    @OneToOne
    public Person getPerson() { return person; }
}

我遇到的问题是VitalStats缺少@Id,这对于@Entity无效。\

如果我尝试

@Id @OneToOne
public Person getPerson() { return person; }

解决了@Id问题,但要求Person可序列化。我们将回到这一点。

我可以使VitalStats @Embeddable并通过@ElementCollection将其连接到Person,但是即使我知道只有一个元素,也必须将其作为一个集合进行访问。可行,但是既令人讨厌又令人困惑。

那么,是什么阻止了我只是说Person实现了Serializable?没什么,实际上,除了我喜欢代码中的所有内容是有原因的,而且我看不到任何逻辑,这使得我的代码可读性较差。

同时,我只是将VitalStats中的Person字段替换为一个长的personId,并使其成为VitalStats的@Id,所以现在@OneToOne可以使用了。

所有这些解决方案(对我而言)似乎都是一个简单的问题,有些笨拙,因此我想知道我是否缺少任何内容,或者是否有人至少可以向我解释为什么Person必须可序列化。

TIA

Answers:


94

要使用共享主键映射一对一关联,请使用@PrimaryKeyJoinColumn@MapsId注释。

Hibernate参考文档的相关部分:

PrimaryKeyJoinColumn

PrimaryKeyJoinColumn注释确实表示该实体的主键用作关联实体的外键值。

MapsId

MapsId注释要求Hibernate从另一个关联的实体复制标识符。在Hibernate行话中,它被称为外来生成器,但JPA映射读起来更好,值得鼓励

人.java

@Entity
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "person_id")
    private Long id;

    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    private VitalStats vitalStats;       
}

VitalStats.java

@Entity
public class VitalStats 
{
    @Id @Column(name="vitalstats_id") Long id;

    @MapsId 
    @OneToOne(mappedBy = "vitalStats")
    @JoinColumn(name = "vitalstats_id")   //same name as id @Column
    private Person person;

    private String stats;
}

人员数据库表

CREATE TABLE  person (
  person_id   bigint(20) NOT NULL auto_increment,
  name        varchar(255) default NULL,
  PRIMARY KEY  (`person_id`)
) 

VitalStats数据库表

CREATE TABLE  vitalstats 
(
  vitalstats_id  bigint(20) NOT NULL,
  stats          varchar(255) default NULL,
  PRIMARY KEY  (`vitalstats_id`)
)

谢谢,但恐怕这没有帮助。除了Person字段外,您还在VitalStats中添加了id字段,而我无法向架构中添加新字段,而不得不使用作为“ person”基础的FK作为VitalStats的PK。这就是重点。
迈克尔

2
由于person属性上具有MapById批注,因此无需添加其他列。我发布的示例并不清楚,我修改了示例以向VitalStats添加更多详细信息(请参阅person属性上的#JoinColumn)。我添加了VitalStats和Person数据库表,您可以看到VitalStats表仅包含一个PK,即person的FK。我想和你想要的一样吗?如果不是您想要的,可以在表格布局上添加更多详细信息吗?@MapId的优点是人员实体不需要可序列化。我更新了示例。
Joel Hudon

@JoelHudon您怎么知道将@JoinColumn注释放置到位?
Kevin Bowersox 2014年

我尝试了此操作,但是却收到类似“-尝试从人员存储空的一对一属性”的错误。上面的代码片段和我的代码片段之间的唯一区别是,我使用了一个序列来生成个人实体中的主键。
kondu 2015年

我通过按照Person.java
kondu 2015年

21

就我而言,这就是窍门:

家长班:

public class User implements Serializable {
  private static final long serialVersionUID = 1L;

  /** auto generated id (primary key) */
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(unique = true, nullable = false)
  private Long id;

  /** user settings */
  @OneToOne(cascade = CascadeType.ALL, mappedBy = "user")
  private Setting setting;
}

子班:

public class Setting implements Serializable {
  private static final long serialVersionUID = 1L;

  /** setting id = user id */
  @Id
  @Column(unique = true, nullable = false)
  private Long id;

  /** user with this associated settings */
  @MapsId
  @OneToOne
  @JoinColumn(name = "id")
  private User user;
}

1
这是对我的正确答案。它在数据库中创建一个外键,这是其他答案的问题。不幸的是,我不能强迫休眠仅执行1个查询来获取诸如用户和设置之类的实体。即使创建了外键并且fetch = FetchType.EAGER ...,也总是执行2个查询
dorsz 2016年

@MapsId应该是@MapsId("id")。阅读没有它的问题
乔治·Z。20年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.