Java Persistence/OneToOne
OneToOne
[edit | edit source]A OneToOne
relationship in Java is where the source object has an attribute that references another target object and (if) that target object had the inverse relationship back to the source object it would also be a OneToOne
relationship. All relationships in Java and JPA are unidirectional, in that if a source object references a target object there is no guarantee that the target object also has a relationship to the source object. This is different than a relational database, in which relationships are defined through foreign keys and querying such that the inverse query always exists.
JPA also defines a ManyToOne
relationship, which is similar to a OneToOne
relationship except that the inverse relationship (if it were defined) is a OneToMany
relationship. The main difference between a OneToOne
and a ManyToOne
relationship in JPA is that a ManyToOne
always contains a foreign key from the source object's table to the target object's table, where as a OneToOne
relationship the foreign key may either be in the source object's table or the target object's table. If the foreign key is in the target object's table JPA requires that the relationship be bi-directional (must be defined in both objects), and the source object must use the mappedBy
attribute to define the mapping.
In JPA a OneToOne
relationship is defined through the @OneToOne
annotation or the <one-to-one>
element. A OneToOne
relationship typically requires a @JoinColumn
or @JoinColumns
if a composite primary key is used.
Example of a OneToOne relationship database
[edit | edit source]EMPLOYEE (table)
EMP_ID | FIRSTNAME | LASTNAME | SALARY | ADDRESS_ID |
1 | Bob | Way | 50000 | 6 |
2 | Sarah | Smith | 60000 | 7 |
ADDRESS (table)
ADDRESS_ID | STREET | CITY | PROVINCE | COUNTRY | P_CODE |
6 | 17 Bank St | Ottawa | ON | Canada | K2H7Z5 |
7 | 22 Main St | Toronto | ON | Canada | L5H2D5 |
Example of a OneToOne relationship annotations
[edit | edit source]@Entity
public class Employee {
@Id
@Column(name="EMP_ID")
private long id;
...
@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name="ADDRESS_ID")
private Address address;
}
Example of a OneToOne relationship XML
[edit | edit source]<entity name="Employee" class="org.acme.Employee" access="FIELD">
<attributes>
<id name="id">
<column name="EMP_ID"/>
</id>
<one-to-one name="address" fetch="LAZY">
<join-column name="ADDRESS_ID"/>
</one-to-one>
</attributes>
</entity>
Inverse Relationships, Target Foreign Keys and Mapped By
[edit | edit source]A typical object centric perspective of a OneToOne
relationship has the data model mirror the object model, in that the source object has a pointer to the target object, so the database source table has a foreign key to the target table. This is not how things always work out in the database though, in fact many database developers would think having the foreign key in the target table to be logical, as this enforces the uniqueness of the OneToOne
relationship. Personally I prefer the object perspective, however you will most likely encounter both.
To start considering a bi-directional OneToOne
relationship, you do not require two foreign keys, one in each table, so a single foreign key in the owning side of the relationship is sufficient. In JPA the inverse OneToOne
must use the mappedBy
attribute (with some exceptions), this makes the JPA provider use the foreign key and mapping information in the source mapping to define the target mapping.
See also, Target Foreign Keys, Primary Key Join Columns, Cascade Primary Keys.
The following gives an example of what the inverse address
relationship would look like.
Example of an inverse OneToOne relationship annotations
[edit | edit source]@Entity
public class Address {
@Id
@Column(name = "ADDRESS_ID")
private long id;
...
@OneToOne(fetch=FetchType.LAZY, mappedBy="address")
private Employee owner;
...
}
Example of an inverse OneToOne relationship XML
[edit | edit source]<entity name="Address" class="org.acme.Address" access="FIELD">
<attributes>
<id name="id"/>
<one-to-one name="owner" fetch="LAZY" mapped-by="address"/>
</attributes>
</entity>
See Also
[edit | edit source]Common Problems
[edit | edit source]Foreign key is also part of the primary key.
[edit | edit source]Foreign key is also mapped as a basic.
[edit | edit source]- If you use the same field in two different mappings, you typically require to make one of them read-only using
insertable, updatable = false
. - See Target Foreign Keys, Primary Key Join Columns, Cascade Primary Keys.
Constraint error on insert.
[edit | edit source]- This typically occurs because you have incorrectly mapped the foreign key in a
OneToOne
relationship.
- It can also occur if your JPA provider does not support referential integrity, or does not resolve bi-directional constraints. In this case you may either need to remove the constraint, or use
EntityManager
flush()
to ensure the order your objects are written in.
Foreign key value is null
[edit | edit source]- Ensure you set the value of the object's
OneToOne
, if theOneToOne
is part of a bi-directionalOneToOne
relationship, ensure you setOneToOne
in both objects, JPA does not maintain bi-directional relationships for you. - Also check that you defined the
JoinColumn
correctly, ensure you did not setinsertable, updatable = false
or use aPrimaryKeyJoinColumn
, ormappedBy
.
Advanced
[edit | edit source]Target Foreign Keys, Primary Key Join Columns, Cascade Primary Keys
[edit | edit source]If a OneToOne
relationship uses a target foreign key (the foreign key is in the target table, not the source table), then JPA requires that you define a OneToOne
mapping in both directions, and that the target foreign key mapping use the mappedBy
attribute. The reason for this, is the mapping in the source object only affects the row the JPA writes to the source table, if the foreign key is in the target table, JPA has no easy way to write this field.
There are other ways around this problem however. In JPA the JoinColumn
defines an insertable
and updatable
attribute, these can be used to instruct the JPA provider that the foreign key is actually in the target object's table. With these enabled JPA will not write anything to the source table, most JPA providers will also infer that the foreign key constraint is in the target table to preserve referential integrity on insertion. JPA also defines the @PrimaryKeyJoinColumn
that can be used to define the same thing. You still must map the foreign key in the target object in some fashion though, but could just use a Basic
mapping to do this.
Some JPA providers may support an option for a unidirectional OneToOne
mapping for target foreign keys.
Target foreign keys can be tricky to understand, so you might want to read this section twice. They can get even more complex though. If you have a data model that cascades primary keys then you can end up with a single OneToOne
that has both a logical foreign key, but has some fields in it that are logically target foreign keys.
For example consider Company
, Department
, Employee
. Company
's id is COM_ID
, Department
's id is a composite primary key of COM_ID
and DEPT_ID
, and Employee
's id is a composite primary key of COM_ID
, DEP_ID
, and EMP_ID
. So for an Employee
its relationship to company
uses a normal ManyToOne
with a foreign key, but its relationship to department
uses a ManyToOne
with a foreign key, but the COM_ID
uses insertable, updatable = false
or PrimaryKeyJoinColumn
, because it is actually mapped through the company
relationship. The Employee
's relationship to its address
then uses a normal foreign key for ADD_ID
but a target foreign key for COM_ID
, DEP_ID
, and EMP_ID
.
This may work in some JPA providers, others may require different configuration, or not support this type of data model.
Example of cascaded primary keys database
[edit | edit source]COMPANY(table)
COM_ID | NAME |
1 | ACME |
2 | Wikimedia |
DEPARTMENT(table)
COM_ID | DEP_ID | NAME |
1 | 1 | Billing |
1 | 2 | Research |
2 | 1 | Accounting |
2 | 2 | Research |
EMPLOYEE (table)
COM_ID | DEP_ID | EMP_ID | NAME | MNG_ID | ADD_ID |
1 | 1 | 1 | Bob Way | null | 1 |
1 | 1 | 2 | Joe Smith | 1 | 2 |
1 | 2 | 1 | Sarah Way | null | 1 |
1 | 2 | 2 | John Doe | 1 | 2 |
2 | 1 | 1 | Jane Doe | null | 1 |
2 | 2 | 1 | Alice Smith | null | 1 |
ADDRESS(table)
COM_ID | DEP_ID | ADD_ID | ADDRESS |
1 | 1 | 1 | 17 Bank, Ottawa, ONT |
1 | 1 | 2 | 22 Main, Ottawa, ONT |
1 | 2 | 1 | 255 Main, Toronto, ONT |
1 | 2 | 2 | 12 Main, Winnipeg, MAN |
2 | 1 | 1 | 72 Riverside, Winnipeg, MAN |
2 | 2 | 1 | 82 Riverside, Winnipeg, MAN |
Example of cascaded primary keys and mixed OneToOne and ManyToOne mapping annotations
[edit | edit source]@Entity
@IdClass(EmployeeId.class)
public class Employee {
@Id
@Column(name="EMP_ID")
private long employeeId;
@Id
@Column(name="DEP_ID", insertable=false, updatable=false)
private long departmentId;
@Id
@Column(name="COM_ID", insertable=false, updatable=false)
private long companyId;
...
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="COM_ID")
private Company company;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({
@JoinColumn(name="DEP_ID"),
@JoinColumn(name="COM_ID", insertable=false, updatable=false)
})
private Department department;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({
@JoinColumn(name="MNG_ID"),
@JoinColumn(name="DEP_ID", insertable=false, updatable=false),
@JoinColumn(name="COM_ID", insertable=false, updatable=false)
})
private Employee manager;
@OneToOne(fetch=FetchType.LAZY)
@JoinColumns({
@JoinColumn(name="ADD_ID"),
@JoinColumn(name="DEP_ID", insertable=false, updatable=false),
@JoinColumn(name="COM_ID", insertable=false, updatable=false)
})
private Address address;
...
}
Example of cascaded primary keys and mixed OneToOne and ManyToOne mapping annotations using PrimaryKeyJoinColumn
[edit | edit source]@Entity
@IdClass(EmployeeId.class)
public class Employee {
@Id
@Column(name="EMP_ID")
private long employeeId;
@Id
@Column(name="DEP_ID", insertable=false, updatable=false)
private long departmentId;
@Id
@Column(name="COM_ID", insertable=false, updatable=false)
private long companyId;
...
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="COM_ID")
private Company company;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="DEP_ID")
@PrimaryKeyJoinColumn(name="COM_ID")
private Department department;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="MNG_ID")
@PrimaryKeyJoinColumns({
@PrimaryKeyJoinColumn(name="DEP_ID")
@PrimaryKeyJoinColumn(name="COM_ID")
})
private Employee manager;
@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name="ADD_ID")
@PrimaryKeyJoinColumns({
@PrimaryKeyJoinColumn(name="DEP_ID")
@PrimaryKeyJoinColumn(name="COM_ID")
})
private Address address;
...
}
Mapping a OneToOne Using a Join Table
[edit | edit source]In some data models, you may have a OneToOne
relationship defined through a join table. For example consider you had existing EMPLOYEE
and ADDRESS
tables with no foreign key, and wanted to define a OneToOne
relationship without changing the existing tables. To do this you could define an intermediate table that contained the primary key of both objects. This is similar to a ManyToMany
relationship, but if you add a unique constraint to each foreign key you can enforce that it is OneToOne
(or even OneToMany
).
JPA defines a join table using the @JoinTable
annotation and <join-table>
XML element. A JoinTable
can be used on a ManyToMany
or OneToMany
mappings, but the JPA 1.0 specification is vague whether it can be used on a OneToOne
. The JoinTable
documentation does not state that it can be used in a OneToOne
, but the XML schema for <one-to-one>
does allow a nested <join-table>
element. Some JPA providers may support this, and others may not.
If your JPA provider does not support this, you can workaround the issue by instead defining a OneToMany
or ManyToMany
relationship and just define a get/set method that returns/sets the first element on the collection.
Example of a OneToOne using a JoinTable database
[edit | edit source]EMPLOYEE (table)
EMP_ID | FIRSTNAME | LASTNAME | SALARY |
1 | Bob | Way | 50000 |
2 | Sarah | Smith | 60000 |
EMP_ADD(table)
EMP_ID | ADDR_ID |
1 | 6 |
2 | 7 |
ADDRESS (table)
ADDRESS_ID | STREET | CITY | PROVINCE | COUNTRY | P_CODE |
6 | 17 Bank St | Ottawa | ON | Canada | K2H7Z5 |
7 | 22 Main St | Toronto | ON | Canada | L5H2D5 |
Example of a OneToOne using a JoinTable
[edit | edit source] @OneToOne(fetch=FetchType.LAZY)
@JoinTable(
name="EMP_ADD",
joinColumns=
@JoinColumn(name="EMP_ID", referencedColumnName="EMP_ID"),
inverseJoinColumns=
@JoinColumn(name="ADDR_ID", referencedColumnName="ADDRESS_ID"))
private Address address;
...
Example of simulating a OneToOne using a OneToMany JoinTable
[edit | edit source] @OneToMany
@JoinTable(
name="EMP_ADD"
joinColumns=
@JoinColumn(name="EMP_ID", referencedColumnName="EMP_ID"),
inverseJoinColumns=
@JoinColumn(name="ADDR_ID", referencedColumnName="ADDRESS_ID"))
private List<Address> addresses;
...
public Address getAddress() {
if (this.addresses.isEmpty()) {
return null;
}
return this.addresses.get(0);
}
public void setAddress(Address address) {
if (this.addresses.isEmpty()) {
this.addresses.add(address);
} else {
this.addresses.set(0, address);
}
}
...