A one-to-one relationship links an instance of one entity to only one instance of another, and vice versa — for instance, a person can only have one passport.
@Entitypublic class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id;
private String name;
// getters and setters}@Entitypublic class Passport {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id;
private String number;
// getters and setters}How do we say the person has a passport, or the passport belongs to a person? JPA has unidirectional and bidirectional relationships. It’s unidirectional when only one entity knows about the other (the person has a passport, but the passport stores nothing about the person, and vice versa).
Let’s start unidirectional — only Person knows about Passport. Just adding the Passport
field isn’t enough; Hibernate needs to be told explicitly what kind of relationship this is (a
OneToOne):
@Entitypublic class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id;
private String name;
@OneToOne private Passport passport;
// getters and setters}Saving both:
try { em.getTransaction().begin();
Person person = new Person(); person.setName("John");
Passport passport = new Passport(); passport.setNumber("ABC123");
person.setPassport(passport);
em.persist(person); em.persist(passport);
em.getTransaction().commit();}The order in which person and passport are persisted doesn’t matter — persist just puts
instances in the context; the actual DB insertion happens on commit.
If you save both and look at the Person table, you’ll see a passport_id column — the result
of the one-to-one relationship. This column follows the convention
otherEntity_primaryKey. To rename it (rarely needed), use @JoinColumn:
@OneToOne@JoinColumn(name = "passport")private Passport passport;This setup works for querying a person by their passport number. But can we query a passport by the person’s name? No — the passport has no knowledge of the person as it stands. This is why we’d reach for a bidirectional relationship.
Bidirectional
Both entities know about each other. On the owning side:
@Entitypublic class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id;
private String name;
@OneToOne private Passport passport;
// getters and setters}On the other side, add mappedBy, naming the field that links the two:
@Entitypublic class Passport {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id;
private String number;
@OneToOne(mappedBy = "passport") private Person person;
// getters and setters}With a bidirectional relationship, you must set both sides when saving, or the relationship is incomplete:
try { em.getTransaction().begin();
Person person = new Person(); person.setName("John");
Passport passport = new Passport(); passport.setNumber("ABC123");
person.setPassport(passport); // person is related to passport passport.setPerson(person); // passport is related to person
em.persist(person); em.persist(passport);
em.getTransaction().commit();}Cascading
Cascade means that applying an operation to an entity instance also applies it to related
entity instances. In the example above we explicitly persisted both person and passport, but
with cascading we can persist just person, and its passport gets persisted along with it.
Cascading is an attribute on the @OneToOne annotation on the owner of the relationship, and the
cascade types mirror the EntityManager operations.
// in our person class@OneToOne(cascade = CascadeType.PERSIST)private Passport passport;This says: if a person is persisted, persist its passport too. Cascade types can be an array,
for multiple cascade operations on the same field. CascadeType.ALL cascades every operation —
not generally recommended; it’s better practice to restrict cascading to precisely your use case.
Fetch type
Fetch controls how entity instances are retrieved when the owner of the relationship is
retrieved. For individual instances (like passport on Person), the default fetchType is
EAGER — the passport loads as soon as the person does. For collections (like a
List<Passport>), you might not want that (too many queries). Change it with
@OneToOne(fetch = FetchType.LAZY) — this postpones loading the related instance until it’s
actually used for the first time.