A common model is to have a dependent object share the primary key of its parent. In the case of a OneToOne
the child's primary key is the same as the parent, and in the case of a ManyToOne
the child's primary key is composed of the parent's primary key and another locally unique field.
Unfortunately JPA does not handle this model well, and things become complicated, so to make your life a little easier you may consider defining a generated unique id for the child. It would be simple if JPA allowed the @Id
annotation on a OneToOne
or ManyToOne
mapping, but it does not. JPA requires that all @Id
mappings be Basic
mappings, so if your Id
comes from a foreign key column through a OneToOne
or ManyToOne
mapping, you must also define a Basic
@Id
mapping for the foreign key column. The reason for this is in part that the Id
must be a simple object for identity and caching purposes, and for use in the IdClass
or the EntityManager
find()
API.
Because you now have two mappings for the same foreign key column you must define which one will be written to the database (it must be the Basic
one), so the OneToOne
or ManyToOne
foreign key must be defined to be read-only. This is done through setting the JoinColumn
attributes insertable
and updatable
to false, or by using the @PrimaryKeyJoinColumn
instead of the @JoinColumn
.
A side effect of having two mappings for the same column is that you now have to keep the two in synch. This is typically done through having the set method for the OneToOne
attribute also set the Basic
attribute value to the target object's id. This can become very complicated if the target object's primary key is a GeneratedValue
, in this case you must ensure that the target object's id has been assigned before relating the two objects.
Some times I think that JPA primary keys would be much simpler if they were just defined on the entity using a collection of Column
s instead of mixing them up with the attribute mapping. This would leave you free to map the primary key field in any manner you desired. A generic List
could be used to pass the primary key to find()
methods, and it would be the JPA provider's responsibility for hashing and comparing the primary key correctly instead of the user's IdClass
. But perhaps for simple singleton primary key models the JPA model is more straight forward.
- TopLink / EclipseLink : Allow the primary key to be specified as a list of columns instead of using
Id
mappings. This allowsOneToOne
andManyToOne
mapping foreign keys to be used as the primary key without requiring a duplicate mapping. It also allows the primary key to be defined through any other mapping type. This is set through using aDescriptorCustomizer
and theClassDescriptor
addPrimaryKeyFieldName
API.
Example OneToOne id annotation
... @Entity public class Address { @Id @Column(name="OWNER_ID") private long ownerId; @OneToOne @PrimaryKeyJoinColumn(name="OWNER_ID", referencedColumnName="EMP_ID") private Employee owner; ... public void setOwner(Employee owner) { this.owner = owner; this.ownerId = owner.getId(); } ... }
Example OneToOne id XML
<entity name="Address" class="org.acme.Address" access="FIELD"> <id name="ownerId"> <column name="OWNER_ID"/> </id> <one-to-one name="owner"> <primary-key-join-column name="OWNER_ID" referencedColumnName="EMP_ID"/> </one-to-one> <entity/>
Example ManyToOne id annotation
... @Entity @IdClass(PhonePK.class) public class Phone { @Id @Column(name="OWNER_ID") private long ownerId; @Id private String type; @ManyToOne @PrimaryKeyJoinColumn(name="OWNER_ID", referencedColumnName="EMP_ID") private Employee owner; ... public void setOwner(Employee owner) { this.owner = owner; this.ownerId = owner.getId(); } ... }