JPA One-To-One
In JPA (Java Persistence API), a one-to-one association is a type of relationship between two entities where each instance of one entity is associated with exactly one instance of another entity. This relationship is represented using the @OneToOne annotation.
Cardinality:
- In a one-to-one relationship, each entity has a single corresponding entity. For example, if you have a
Personentity and aPassportentity, eachPersoncan have only onePassport, and eachPassportcan be assigned to only onePerson.
- In a one-to-one relationship, each entity has a single corresponding entity. For example, if you have a
Owning vs. Non-Owning Side:
- Owning Side: The entity that contains the foreign key is considered the owning side of the relationship. This entity’s table will have the column that refers to the primary key of the other entity.
- Non-Owning (Inverse) Side: The other entity that does not contain the foreign key. This entity maps the relationship using the
mappedByattribute in the@OneToOneannotation.
Bidirectional and Unidirectional Relationships:
- Unidirectional: Only one entity is aware of the relationship. This means only one entity has the
@OneToOneannotation, and the other entity has no reference to the relationship. - Bidirectional: Both entities are aware of the relationship. Each entity will have a reference to the other entity, and both will use the
@OneToOneannotation.
- Unidirectional: Only one entity is aware of the relationship. This means only one entity has the
Cascade Type:
- Cascade operations can be applied to propagate operations (like persist, remove) from one entity to another. This is often used in one-to-one relationships to manage both entities together.
Fetch Type:
- By default, one-to-one relationships use
FetchType.EAGER, meaning the related entity is fetched immediately along with the owner entity. However, you can change this toFetchType.LAZYif you want to load the related entity on demand.
- By default, one-to-one relationships use
Let’s consider an example where we have two entities: Person and Passport.
We wille be using the @MapsId annotation, where a Person entity has a one-to-one relationship with a Passport entity.
- The
@MapsIdannotation maps the primary key of the child (Passport) to the primary key of the parent (Person). - This enforces that both entities share the same primary key.
- The
Passportentity doesn’t have its own generated ID but instead inherits the ID fromPerson.
Here’s the same JPA One-to-One relationship using @MapsId, but now with Lombok to simplify the entities. This reduces boilerplate code by automatically generating constructors, getters, setters, toString(), equals(), and hashCode().
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "person")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = "passport") // Avoid circular reference in toString()
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
private Passport passport;
public Person(String name) {
this.name = name;
}
public void setPassport(Passport passport) {
this.passport = passport;
passport.setPerson(this); // Maintain bidirectional relationship
}
}
JPA Annotations
@Entity: Marks this class as a JPA entity.@Table(name = "person"): Maps this entity to the person table in the database.@Id: Marks theidfield as the primary key.@GeneratedValue(strategy = GenerationType.IDENTITY):- Uses the database’s identity column for automatic primary key generation.
- Suitable for databases like MySQL and PostgreSQL.
@OneToOne(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true):- Declares a one-to-one relationship with
Passport. mappedBy = "person": Thepersonfield inPassportowns the relationship.cascade = CascadeType.ALL: Any operations (persist, remove, merge) onPersoncascade toPassport.orphanRemoval = true: If aPersonloses itsPassport, thePassportis automatically deleted.
- Declares a one-to-one relationship with
Lombok Annotations
@Getter @Setter: Automatically generates getter and setter methods.@NoArgsConstructor: Generates a default constructor.@AllArgsConstructor: Generates a constructor with all fields.@ToString(exclude = "passport"): Prevents infinite recursion intoString()(sincePersonandPassportreference each other).
Custom
setPassport()Method- Ensures both entities maintain a bidirectional relationship.
passport.setPerson(this);ensures that thePassportcorrectly references itsPerson.
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "passport")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = "person") // Avoid circular reference in toString()
public class Passport {
@Id
private Long id; // Uses the same ID as the Person entity
private String passportNumber;
@OneToOne
@MapsId // Uses the same primary key as Person
@JoinColumn(name = "id")
private Person person;
public Passport(String passportNumber) {
this.passportNumber = passportNumber;
}
}
JPA Annotations
@Entity: MarksPassportas a JPA entity.@Table(name = "passport"): Maps to the passport table.@Id private Long id;: Uses the same primary key as Person.@OneToOne:- Defines a one-to-one relationship with
Person.
- Defines a one-to-one relationship with
@MapsId: Mapsidto the primary key ofPerson.Passportdoes not have its own ID.- Instead, it shares the same ID as its
Person.
@JoinColumn(name = "id"):- Specifies that the
idcolumn links to the primary key of Person.
- Specifies that the
Lombok Annotations
@Getter @Setter: Automatically generates getter and setter methods.@NoArgsConstructor: Generates a default constructor.@AllArgsConstructor: Generates a constructor with all fields.@ToString(exclude = "person"): Prevents infinite recursion intoString().
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = HibernateConfig.createEntityManagerFactory();
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
// Create Person
Person person = new Person("John Doe");
// Create Passport
Passport passport = new Passport("ABC123456");
// Establish relationship
person.setPassport(passport);
// Persist Person (Passport will be automatically persisted due to CascadeType.ALL)
em.persist(person);
tx.commit();
// Fetch and print data
Person retrievedPerson = em.find(Person.class, person.getId());
System.out.println("Person: " + retrievedPerson);
System.out.println("Passport Number: " + retrievedPerson.getPassport().getPassportNumber());
} catch (Exception e) {
if (tx != null && tx.isActive()) {
tx.rollback();
}
e.printStackTrace();
} finally {
em.close();
emf.close();
}
}
}
EntityManager Setup
EntityManagerFactory emf = HibernateConfig.createEntityManagerFactory();EntityManager em = emf.createEntityManager();- Manages database operations.
Transaction Handling
tx.begin();: Starts a new transaction.tx.commit();: Commits changes to the database.- If an exception occurs,
tx.rollback();is executed.
Creating and Persisting Entities
- Creates a
Personnamed"John Doe". - Creates a
Passportwith number"ABC123456". - Calls
person.setPassport(passport);to establish the relationship. - Calls
em.persist(person);to savePerson(automatically persistsPassportdue to CascadeType.ALL).
- Creates a
Retrieving and Printing Data
- Retrieves the
Personfrom the database. - Prints
Personand their associatedPassportnumber.
- Retrieves the
The generated schema will look like this:
| id | name |
|---|---|
| 1 | John Doe |
| id | passport_number |
|---|---|
| 1 | ABC123456 |
- The
idofpassportis the same as theidofperson(due to@MapsId). - This ensures a One-to-One relationship with shared primary keys.
One-to-One Relationship:
@OneToOne(mappedBy = "person")inPerson.java.@OneToOne @MapsIdinPassport.javato share the same primary key.
Cascade & Orphan Removal:
CascadeType.ALL: Automatically saves/deletes the associatedPassportwhenPersonis modified.orphanRemoval = true: If aPersonis deleted or loses theirPassport, thePassportis also deleted.
Lombok Simplifications:
@Getter,@Setter,@NoArgsConstructor,@AllArgsConstructor, and `@ToString