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

Michael picture Michael · Jul 26, 2011 · Viewed 48.5k times · Source

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

Answer

Joel Hudon picture Joel Hudon · Jul 27, 2011

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