skip to content

In a bidirectional JPA association, what does the `mappedBy` attribute mean, and which side's changes actually cause Hibernate to write the foreign key?

level: middleimportance: must knowfreq 80%

answer

  1. One column, two views, one owner
  2. mappedBy = field name on the other entity
  3. @ManyToOne always owns the FK
  4. Inverse-only add = no SQL
  5. Owner-only set = stale collection

basics

~20 s

mappedBy marks the inverse (non-owning) side and names the owning field on the other entity. Only the owning side — the one holding the foreign-key column, normally the @ManyToOne — produces INSERT/UPDATE of that column. Changing only the inverse collection writes nothing.

solid answer

~50 s

Every association has exactly one **owning side**: the side whose state Hibernate reads when it decides what to write into the foreign key. For a many-to-one/one-to-many pair the owner is always the `@ManyToOne`, because that entity's table physically holds the column. The other side declares `@OneToMany(mappedBy = "customer")`, where the string is the *field name on the owning entity*, not a column name. `mappedBy` says: "I am a read view of a column someone else manages; do not map a column or table for me." The consequence trips up almost everyone: `customer.getOrders().add(order)` alone emits no SQL for the FK. You must set `order.setCustomer(customer)`. The reverse — setting only the owning side — persists correctly, but leaves the in-memory collection stale until the persistence context is cleared or the entity re-read. For `@ManyToMany` and unidirectional `@OneToMany` with a join table, the owning side is the one that declares `@JoinTable`; the inverse side again uses `mappedBy`.

code

java · 12 lines
java
@Entity
class Order {
    @ManyToOne(optional = false)
    @JoinColumn(name = "customer_id")   // owns the column
    private Customer customer;
}

@Entity
class Customer {
    @OneToMany(mappedBy = "customer")   // "customer" = field on Order
    private Set<Order> orders = new HashSet<>();
}

go deeper

for a junior

Recall that one side owns, that mappedBy marks the other, and that you must set the @ManyToOne field for the data to be saved.

for a middle

Explain that both fields describe one column, that mappedBy names a field on the owning entity, and describe both failure modes: null FK and stale collection.

for a senior

Add flush-time snapshot reasoning, the cascade-does-not-set-the-FK trap, the double-owner extra UPDATE, and why the bug hides inside a single persistence context.

for a principal

Discuss it as a consistency-invariant problem: pick owners deliberately, enforce the invariant in the entity API rather than trusting callers, and prefer unidirectional mappings where the inverse view earns nothing.

## The problem mappedBy solves A bidirectional association is two Java fields describing **one** database column. `Order.customer` and `Customer.orders` are not two relationships; they are two views of the `orders.customer_id` column. If both were treated as independent sources of truth, the ORM would have to reconcile them on every flush — and would have no rule for what to do when they disagree (customer A's collection contains an order whose `customer` field points at customer B). JPA solves this by decree rather than reconciliation: **one side owns the mapping, the other is declared inverse with `mappedBy`, and only the owner is consulted when generating SQL.** ## Reading a mappedBy declaration ```java @OneToMany(mappedBy = "customer") private Set<Order> orders; ``` The value `"customer"` is the **name of the Java field on the other entity** that owns the mapping — here `Order.customer`. It is not a column name and not a table name. Getting it wrong is a boot-time mapping failure, which is helpful: the mistake surfaces immediately. A field annotated with `mappedBy` may not also carry `@JoinColumn` or `@JoinTable`; those describe the physical mapping, and the inverse side has none. If you find yourself wanting both, you have two owners and therefore two independent mappings for one column — Hibernate will happily generate a redundant, sometimes conflicting, extra UPDATE. ## Who owns what - `@ManyToOne` / `@OneToMany`: the `@ManyToOne` side always owns, because the FK lives in its table. The collection side is always `mappedBy`. - `@OneToOne`: whichever entity's table holds the FK owns it; the other uses `mappedBy`. With `@MapsId` the dependent (shared-PK) side owns. - `@ManyToMany`: the side declaring `@JoinTable` owns; the other uses `mappedBy`. The choice is arbitrary from the schema's point of view, so pick the side whose code does the writing. - Unidirectional associations have no inverse side at all; the single mapped side owns by definition. ## What "generates SQL" really means At flush time Hibernate compares each managed entity against the snapshot it took when the entity was loaded, and derives the SQL. For an association, the state it inspects is the **owning** field. So: ```java customer.getOrders().add(order); // in-memory only; no FK write order.setCustomer(customer); // this is what produces the UPDATE/INSERT ``` Set only the inverse collection and the row's `customer_id` stays null (or unchanged) — the classic "my save silently did nothing" bug. Worse, within the same persistence context the collection *looks* right, because you mutated it yourself; the truth only appears after a clear, a new transaction, or a reload. That is why the failure so often escapes tests that assert inside the same session. Set only the owning side and the data is correct, but the other entity's collection in memory is stale for the rest of the session. This is the milder bug, and the reason for the sync-helper convention that keeps both fields consistent in one call. ## Special cases worth knowing **Inverse collection with cascade.** Cascading from the inverse side still works — cascade follows Java references, not ownership — so `persist` can reach children through a `mappedBy` collection. But cascade only makes the children persistent; it does not set their FK. A cascaded child with a null owning reference will still be inserted with a null `customer_id`, or fail the NOT NULL constraint. **Unidirectional `@OneToMany` without `@JoinColumn`.** With no owner on the many side and no explicit `@JoinColumn`, JPA defaults to a join table, which is usually a surprise and produces extra delete/insert traffic. Either add `@JoinColumn` (making the collection the owner of the FK, with its own quirks) or, preferably, map the `@ManyToOne` and let it own. **Both sides mapped as owners.** Two `@JoinColumn`s on the same column with no `mappedBy` yields two mappings that both write; Hibernate emits an extra UPDATE and the two can fight. Adding `insertable = false, updatable = false` to one is the read-only escape hatch, but `mappedBy` is the correct expression of intent. ## How to answer in an interview Say it in one sentence — "`mappedBy` names the owning field on the other entity and marks this side as a non-writing view" — then give the failing snippet, then name the two symptoms: null foreign key when you set only the inverse side, stale collection when you set only the owning side. That progression demonstrates you have actually debugged it rather than memorised it.

  • What happens if you add a child to the inverse collection but never set the child's reference back to the parent?
    The child is inserted (if it is persisted or cascaded) with a null foreign key, or the INSERT fails against a NOT NULL constraint. Nothing sets `customer_id`, because Hibernate reads only the owning `@ManyToOne` field when generating SQL. Inside the same persistence context the collection looks correct, so the bug frequently survives tests that never clear or reload.
  • Can you make both sides owning, and what happens if you do?
    You can physically map `@JoinColumn` on both sides with no `mappedBy`, but then two mappings claim the same column. Hibernate treats them independently, typically issuing a redundant extra UPDATE, and inconsistent in-memory state can produce contradictory writes. If you really need the column readable from both sides, mark one `insertable = false, updatable = false`; the intended mechanism is `mappedBy`.
  • In a bidirectional @ManyToMany, which side should own the join table?
    Either side is relationally valid, so choose by usage: the side whose code performs the link changes, and typically the smaller or more stable collection. The owner declares `@JoinTable`; the other declares `mappedBy`. Mutating only the inverse collection writes no rows at all, exactly as with a one-to-many.

Two people looking at the same ledger entry: only one holds the pen. The other can read it and can be out of date, but nothing they write on their copy reaches the ledger.

saying these in an interview costs you the question

  • Believing mappedBy takes a column name rather than the other entity's field name
  • Thinking that adding to the inverse collection is enough to persist the link
  • Putting @JoinColumn on the mappedBy side as well
  • Claiming the collection side owns a one-to-many because it "contains" the children
  • Assuming cascade from the inverse side also sets the foreign key

context