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

JavaJpaOne to-One

Java Problem Overview


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

Java Solutions


Solution 1 - Java

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`)
)

Solution 2 - Java

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMichaelView Question on Stackoverflow
Solution 1 - JavaJoel HudonView Answer on Stackoverflow
Solution 2 - JavacamposerView Answer on Stackoverflow