Lesson 10 / 25
@ManyToOne, @OneToMany and the Owning Side
Map one-to-many relationships with the correct owning side and helper methods.
Foreign keys as object references
Most relationships are many-to-one at the database level: an order_lines.order_id foreign key. Map it with @ManyToOne (plus @JoinColumn(name = "order_id")) on the many side, which is the owning side: the side whose field determines the foreign key value. If you also want navigation from the parent, add @OneToMany(mappedBy = "order") on the one side; mappedBy marks it as the inverse side, which Hibernate ignores when writing the foreign key. A very common bug is updating only the inverse side (order.getLines().add(line)) without setting line.setOrder(order), so the foreign key is never written. Keep both sides in sync with helper methods (addLine, removeLine) on the parent. A unidirectional @OneToMany without mappedBy creates an extra join table or extra UPDATE statements, so prefer bidirectional mappings or just the @ManyToOne. Consider whether you need the collection at all: loading an order's lines through a query is often simpler than mapping a large collection.
Owning side and inverse side
The many side owns the foreign key; the one side mirrors it with mappedBy.
A bidirectional one-to-many with helper methods
The helper keeps both sides consistent so the foreign key is always written.
@Entity
@Table(name = "orders")
public class PurchaseOrder {
@Id @GeneratedValue private Long id;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderLine> lines = new ArrayList<>();
public void addLine(String sku, int qty, BigDecimal unitPrice) {
OrderLine line = new OrderLine(this, sku, qty, unitPrice);
lines.add(line); // inverse side
}
public void removeLine(OrderLine line) {
lines.remove(line);
line.detachFromOrder(); // keep both sides in sync
}
}
@Entity
public class OrderLine {
@Id @GeneratedValue private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false) // owning side: writes order_id
@JoinColumn(name = "order_id")
private PurchaseOrder order;
private String sku;
private int quantity;
private BigDecimal unitPrice;
protected OrderLine() { }
OrderLine(PurchaseOrder order, String sku, int quantity, BigDecimal unitPrice) {
this.order = order; this.sku = sku; this.quantity = quantity; this.unitPrice = unitPrice;
}
void detachFromOrder() { this.order = null; }
}mappedBy names the field, not the column
@OneToMany(mappedBy = "order") refers to the Java field order in OrderLine, not the database column order_id. Getting this wrong produces a mapping exception at start-up.
Quick check: In a bidirectional one-to-many mapping, which side writes the foreign key?
- The @OneToMany side with mappedBy
- Both sides equally
- The @ManyToOne side (the owning side)
- Neither; the database decides
Answer
The @ManyToOne side (the owning side) — The owning side, the @ManyToOne field, determines the foreign key value.