Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA @OneToOne with Shared ID -- Can I do this Better?

I’m working with an existing schema that I’d rather not change. The schema has a one-to-one relationship between tables Person and VitalStats, where Person has a primary key and VitalStats uses the same field as both its primary key and its foreign key to Person, meaning its value is the value of the corresponding PK of Person.

These records are created by external processes, and my JPA code never needs to update VitalStats. For my object model I’d like my Person class to contain a VitalStats member, BUT:

When I try

@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; } } 

I have the problem that VitalStats lacks an @Id, which doesn’t work for an @Entity.\

If I try

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

that solves the @Id problem but requires that Person be Serializable. We’ll get back to that.

I could make VitalStats @Embeddable and connect it to Person via an @ElementCollection, but then it would have to be accessed as a collection, even though I know that there’s only one element. Doable, but both a little bit annoying and a little bit confusing.

So what’s preventing me from just saying that Person implements Serializable? Nothing, really, except that I like everything in my code to be there for a reason, and I can’t see any logic to this, which makes my code less readable.

In the meantime I just replaced the Person field in VitalStats with a long personId and made that VitalStats’s @Id, so now the @OneToOne works.

All of these solutions to what seems (to me) like a straightforward issue are a bit clunky, so I’m wondering whether I’m missing anything, or whether someone can at least explain to me why Person has to be Serializable.

TIA

like image 775
Michael Avatar asked Jul 26 '11 16:07

Michael


People also ask

Is @ID mandatory in JPA?

Id is required by JPA, but it is not required that the Id specified in your mapping match the Id in your database. For instance you can map a table with no id to a jpa entity.

How do you make a foreign key a primary key in Hibernate?

Use @PrimaryKeyJoinColumn and @PrimaryKeyJoinColumns annotations. From Hibernate manual: The @PrimaryKeyJoinColumn annotation does say that the primary key of the entity is used as the foreign key value to the associated entity.

Does JPA require primary key?

Every JPA entity must have a primary key. You can specify a primary key as a single primitive, or JDK object type entity field (see "Configuring a JPA Entity Simple Primary Key Field").


2 Answers

To map one-to-one association using shared primary keys use @PrimaryKeyJoinColumn and @MapsId annotation.

Relevant sections of the Hibernate Reference Documentation:

PrimaryKeyJoinColumn

The PrimaryKeyJoinColumn annotation does say that the primary key of the entity is used as the foreign key value to the associated entity.

MapsId

The MapsId annotation ask Hibernate to copy the identifier from another associated entity. In the Hibernate jargon, it is known as a foreign generator but the JPA mapping reads better and is encouraged

Person.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; } 

Person Database Table

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

VitalStats Database Table

CREATE TABLE  vitalstats  (   vitalstats_id  bigint(20) NOT NULL,   stats          varchar(255) default NULL,   PRIMARY KEY  (`vitalstats_id`) ) 
like image 85
Joel Hudon Avatar answered Sep 24 '22 18:09

Joel Hudon


In my case this made the trick:

Parent class:

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; } 

Child class:

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; } 
like image 29
camposer Avatar answered Sep 20 '22 18:09

camposer