JPA - Unidirectional vs. Bidirectional Relationships
In JPA, the difference between unidirectional and bidirectional OneToMany associations lies in how the entities reference each other. In a unidirectional relationship, only one entity knows about the relationship, while in a bidirectional relationship, both entities are aware of the relationship.
Let’s take the Package and Shipment example and explore both approaches. The example is taken from this Class Diagram:

1. Unidirectional One-to-Many Relationship
In a unidirectional OneToMany relationship, only the Package entity is aware of the relationship. The Shipment entity doesn’t have any reference back to Package.
Code Example (Unidirectional)
Package Entity:
import jakarta.persistence.*;
import java.util.List;
@Entity
public class Package {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String trackingNumber;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "package_id") // Adds a foreign key (package_id) to the Shipment table
private List<Shipment> shipments;
// Constructors, Getters, and Setters
public Package() {}
public Package(String trackingNumber) {
this.trackingNumber = trackingNumber;
}
public Long getId() {
return id;
}
public String getTrackingNumber() {
return trackingNumber;
}
public List<Shipment> getShipments() {
return shipments;
}
public void setShipments(List<Shipment> shipments) {
this.shipments = shipments;
}
}
Shipment Entity:
import jakarta.persistence.*;
@Entity
public class Shipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
// Constructors, Getters, and Setters
public Shipment() {}
public Shipment(String description) {
this.description = description;
}
public Long getId() {
return id;
}
public String getDescription() {
return description;
}
}
Key Points of Unidirectional
- The
@JoinColumn(name = "package_id")annotation in thePackageentity places a foreign key (package_id) in theShipmenttable, creating the association. CascadeType.ALLensures that when aPackageis persisted, updated, or removed, the relatedShipmententities are also persisted or removed.- The
Shipmententity does not have a reference to thePackageentity, making this relationship unidirectional.
Table Structure
- The
Shipmenttable will have apackage_idforeign key column that links to thePackagetable.
2. Bidirectional One-to-Many Relationship
In a bidirectional OneToMany relationship, both Package and Shipment are aware of the relationship. The Package entity maintains a collection of Shipments, and the Shipment entity has a reference back to its owning Package.
Code Example (Bidirectional)
Package Entity:
import jakarta.persistence.*;
import java.util.List;
@Entity
public class Package {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String trackingNumber;
@OneToMany(mappedBy = "pkg", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Shipment> shipments;
// Constructors, Getters, and Setters
public Package() {}
public Package(String trackingNumber) {
this.trackingNumber = trackingNumber;
}
public Long getId() {
return id;
}
public String getTrackingNumber() {
return trackingNumber;
}
public List<Shipment> getShipments() {
return shipments;
}
public void setShipments(List<Shipment> shipments) {
this.shipments = shipments;
for (Shipment shipment : shipments) {
shipment.setPkg(this); // Maintain the bidirectional link
}
}
}
Shipment Entity:
import jakarta.persistence.*;
@Entity
public class Shipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
@ManyToOne
@JoinColumn(name = "package_id", nullable = false) // Foreign key column in Shipment table
private Package pkg;
// Constructors, Getters, and Setters
public Shipment() {}
public Shipment(String description) {
this.description = description;
}
public Long getId() {
return id;
}
public String getDescription() {
return description;
}
public Package getPkg() {
return pkg;
}
public void setPkg(Package pkg) {
this.pkg = pkg;
}
}
Key Points of Bidirectional
- The
@OneToMany(mappedBy = "pkg")annotation in thePackageentity makes this a bidirectional relationship, meaning theShipmententity refers back to thePackagevia thepkgfield. - The
mappedByattribute indicates that theShipmententity owns the relationship by having aManyToOnerelationship withPackage. - In
Shipment, the@ManyToOneannotation with the@JoinColumn(name = "package_id")defines the foreign key. CascadeType.ALLensures cascading actions betweenPackageand itsShipments, and orphanRemoval = true ensures that if a shipment is removed from thePackage, it is also deleted from the database.
Table Structure
- The
Shipmenttable will have apackage_idforeign key column that links to thePackagetable, just like in the unidirectional relationship.
Maintaining the Relationship
- You need to ensure that when adding or removing
Shipmententities in aPackage, the link is maintained on both sides by callingshipment.setPkg(this)when setting shipments on a package.
Summary:
Unidirectional One-to-Many
- The owning side is only in the
Packageentity. - No reference to the
Packagefrom theShipmententity. - Simpler to implement if you don’t need back-references.
Bidirectional One-to-Many
- Both
PackageandShipmentare aware of the relationship. - The
Packagehas a collection ofShipments, and eachShipmentreferences itsPackage. - More powerful and useful when you need to navigate the relationship from both sides.
Each type of association has its use case:
- Unidirectional is simpler and more lightweight but limits the ability to navigate relationships from both entities.
- Bidirectional provides better navigation and flexibility but requires more careful management of both sides of the relationship.
Let me know if you need further clarifications or examples!