skip to content

Mapping & Identity

How a class is tied to a row: where declarations live, how the key arrives, how links and hierarchies behave, and rules applied to every statement. Asked first because later defects start here.

on this pageshow

questions

page 1 of 2

In a data-access layer that maps a link from both classes, what does it mean that one end owns the write?

level: juniorimportance: must knowfreq 72%

answer

  1. one stored slot, two object views
  2. only one end emits the statement
  3. inverse end is read-only at flush
  4. the column's table suggests the owner
  5. ownership is not cascade

basics

~20 s

The owning end is the side whose state the mapper turns into the foreign-key or junction-row statement. The other end is mapped for reading and navigation: changing it alone normally produces no SQL at all.

solid answer

~50 s

The stored link sits in one place — a foreign-key column on one row, or a row in a junction table — while the object model can expose it from both sides. The mapping names which side the layer reads when it builds statements. That **owning end** is dirty-checked and turned into an `UPDATE` of the link column or an `INSERT`/`DELETE` of a junction row; the **inverse end** is populated on load and navigated in code, but derives no statement in layers that draw the distinction. For a one-to-many link the column lives on the child row, so the child's reference is the natural owner. For a junction link either side can be declared the owner, because the junction row belongs to neither table. Ownership of the write is a separate declaration from cascade, which decides lifecycle.

go deeper

for a junior

Remember the shape: the link is stored once, the objects can show it twice, and only one of those views is what gets written. Be able to say which side that normally is for a parent-and-children link.

for a middle

Explain the mechanics — the dirty check runs over the owning field, the inverse end is populated on load, and statement ordering is the layer's decision. Say why a foreign-key link constrains the choice and a junction link does not.

for a senior

Show that you use the declaration deliberately: pick the owner that matches the side your code edits, keep cascade decisions separate from it, and know which behaviours your layer will and will not fill in for you.

for a principal

Treat it as an API-shape decision. A model where either end can be set independently will produce half-set links across a whole codebase, so the standard you set is that links are changed through one method, not that everyone remembers which end wins.

A link between two rows lives in exactly one place in the stored data: a **foreign-key column** on one of the two tables, or a **row in a junction table**. The object model can show that same link from both directions — a parent holding a collection of children, and each child holding a reference back to the parent. Two in-memory views, one storage slot. Something has to decide which view the layer reads when it builds the statement, because reading both would force it to resolve their disagreements on every flush. That decision is the **owning end**, and it is written into the mapping rather than inferred from the code that runs. ## One link, two views - **Owning end** — the side the layer inspects when it works out what to write. Its state becomes an `UPDATE child SET parent_id = ?`, an `INSERT` into the junction table, or a `DELETE` of a junction row. - **Inverse end** — mapped so the graph can be navigated and so the layer can populate it on load. In layers that draw this distinction, the inverse end derives no statement: code can add to that collection freely and the stored data does not move. - The distinction exists only because the link is mapped **twice**. A link mapped from a single side is trivially owned by that side, and the word rarely comes up. ## What the owning end does at flush 1. The layer compares the owning field's current value with the value it recorded when the object was loaded — the same dirty check it runs over every other mapped field. 2. Any difference becomes a statement against the column or the junction table. The inverse collection is not consulted. 3. Statement order is the layer's choice, not the calling code's: it will normally insert a parent before the child that points at it, and delete junction rows before the rows they reference. 4. Whatever the inverse side holds in memory is left exactly as the code left it. Nothing reconciles the two views for you unless the mapping explicitly asks for it. ## Which end can own | Link shape | Where the stored link sits | Which end normally owns it | |---|---|---| | One parent, many children | foreign-key column on the child row | the child's reference to the parent, because the column is on that row | | One row to one row | foreign-key column on one of the two rows | the side that carries the column | | Many to many | a junction row belonging to neither table | either side, decided by declaration | The pattern behind the table is simple: **the owner is whoever the storage lets you write in one statement.** For a foreign-key link, only the child's row can be updated to change the link, so declaring the parent's collection as the owner either is impossible or costs the layer extra statements. For a junction link, neither row changes, so the choice is genuinely free and is usually made by whichever side the application code edits. ## Ownership is not lifecycle Owning the write says which field the layer reads. It says nothing about which object matters more, which one is loaded first, or what happens to the other when one is deleted. Whether saving a parent also saves a new child, and whether deleting a parent deletes its children, is a **separate declaration** (cascade). Interviews routinely conflate the two, and the giveaway is a candidate who answers "the parent owns it, because the children belong to it" for a link whose foreign key sits on the child. ## Layers without a tracked set A query builder or a thin statement layer keeps no tracked copy of what it loaded and runs no dirty check, so the concept does not arise: the code writes the foreign-key column or the junction row itself, and there is no second view to fall out of step. Full mappers also differ from one another — some will keep both ends aligned when the pair is declared as one bidirectional link, others deliberately will not, on the grounds that guessing is worse than a rule. Treating the friendlier behaviour as universal is how the classic one-end-set defect ships. ## What an interviewer is listening for - Naming the storage location first — one column, or one junction row — and deriving the rest from it. - Stating that the inverse end is a read and navigation convenience at write time. - Separating the owning declaration from cascade. - Not claiming the layer compares both ends and picks a winner. - Knowing that the answer changes shape for a junction link, where either side can be declared the owner.

  • For a link stored in a junction table, why can either side be declared the owner?
    Neither row changes when the link changes — the layer inserts or deletes a junction row instead. Since the statement targets a third table, nothing about the storage forces the choice, so the mapping picks whichever side the application code actually edits.
  • Is the owning end also the side that decides whether saving one object saves the other?
    No. Ownership says which field the layer reads to build the link statement. Whether a save or delete propagates along the link is a separate cascade declaration, and it can be declared on the end that does not own the write.
  • Does the concept apply to a layer that does not track loaded objects?
    Not really. Without a tracked snapshot there is no dirty check and no second mapped view to fall out of step: the code writes the foreign-key column or the junction row itself, so the question of which end the layer reads never arises.

Two calendars showing the same meeting. Only one of them is the copy the scheduling system reads when it sends the invitations; editing the other changes what you see and nothing else.

saying these in an interview costs you the question

  • Thinks both ends of a mapped link produce statements independently
  • Says the parent's collection is what emits the foreign-key update
  • Claims the owning side is a free choice for every link shape
  • Assumes the layer compares both ends and reconciles them at flush
  • Confuses owning the write with owning the lifecycle of the other object
open as a page

In a data-access layer, what is an always-on filter on a mapped type, and which reads does it affect?

level: juniorimportance: must knowfreq 62%

basics

~20 s

An always-on filter is a predicate declared once on a mapped type, such as live-rows-only or current-tenant-only, that the layer adds to the WHERE clause of the statements it generates for that type, so no caller has to restate it.

open as a page

When the store assigns a key during the insert, at what point does a mapped object learn its identifier?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Only when the insert actually runs. Until that statement reaches the store the key field is empty, so asking a pending object for its identifier forces the layer to write that row immediately and read the generated value back.

open as a page

When a data-access layer maps a class hierarchy, how does it decide which concrete subclass to instantiate for a row?

level: juniorimportance: must knowfreq 58%

basics

~20 s

Either a type-marker column stored with the row names the class, or the layer discovers which subtype table holds a row for that key. The marker is free with the read; discovery costs a join or a probe per subtype.

open as a page

Where can a data-access layer's mapping rules live, and what does convention-only mapping mean?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Mapping rules have four possible homes: on the class itself, in an external configuration document, in a startup configuration API, or nowhere — derived by convention from class and property names. Most layers mix conventions with targeted overrides.

open as a page

In a data-access layer, what does it mean to flatten a multi-field value object into its owner's columns?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Flattening stores a composite value's fields as extra columns on the owner's own row rather than in a table of its own. The value gets no key and no independent lifecycle: it is loaded, written and discarded with the owner.

open as a page

In a bidirectional mapping, code adds a child to the parent's collection and nothing is written — why, and what is the fix?

level: middleimportance: must knowfreq 66%

basics

~20 s

The collection is the inverse end, and the layer builds link statements only from the owning end, so the flush finds nothing to write. Fix it with one method that sets both ends, and keep the raw setters private.

open as a page

Why does a query against the base type of a mapped hierarchy emit one statement in some mappings and several in others?

level: middleimportance: must knowfreq 62%

basics

~20 s

Because the statement follows where the subtypes' columns live: one shared table means one SELECT, per-subtype tables mean an outer join per subtype, and per-class tables mean a union arm each, so cost grows per subtype.

open as a page

How does a mapper turn class and property names into table and column names, and when must you override that?

level: middleimportance: must knowfreq 57%

basics

~20 s

A naming strategy derives every identifier nobody declared, turning class names into table names and property names into column names, usually with case conversion. Replace the strategy for a schema-wide rule; declare one name for a legacy exception.

open as a page

When a converter stores an enumeration by position rather than by name, what changes and what breaks?

level: middleimportance: must knowfreq 70%

basics

~20 s

Position stores the constant's index in declaration order, so inserting or reordering constants silently re-points every existing row. The name survives reordering but breaks on a rename. An explicit, stable code declared per constant avoids both failure modes.

open as a page

A mapped type carries a live-rows-only filter, yet deleted rows still reach callers. Which paths skip it?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Any path where the layer does not compose the SQL: hand-written statements, set-based updates and deletes, a key lookup answered from objects already tracked, an object served from a long-lived cache, and rows the database itself changes.

open as a page

A filter on an attribute written through a converter returns no rows although matching data exists — why?

level: seniorimportance: must knowfreq 66%

basics

~20 s

Usually the predicate never went through the converter, so the object-side value was compared against the stored encoding. Set-based and hand-written statements bypass conversion entirely, and ordering compares the stored form rather than the domain's order.

open as a page

In a mapper, what changes at read and write time when a field holds a related row's key value instead of a mapped reference?

level: middleimportance: should knowfreq 48%

basics

~20 s

The stored column is identical; the services differ. A key value is an ordinary field with no navigation, no deferred loading and no cascade. A mapped reference buys those, at the cost of loads that fire behind a field access.

open as a page

How does a data-access layer fill created and updated timestamps and actor columns through lifecycle hooks?

level: middleimportance: should knowfreq 54%

basics

~20 s

Registered callbacks run around insert, update and delete and set the fields before the statement is built — the time from an injected clock, the actor from ambient context — so callers never assign audit columns.

open as a page

What does a mapper demand of a composite key class used as a mapped object's identifier?

level: middleimportance: should knowfreq 44%

basics

~20 s

That it behave as a value: all parts populated, equality and hashing over every part, and no mutation once the row exists. The layer files objects under that whole value, so a changed key loses the row it addressed.

open as a page

How do pre-allocated key blocks let a data-access layer know an object's identifier before the row is written?

level: middleimportance: should knowfreq 55%

basics

~20 s

The layer claims a whole range from a store-side counter in one round trip, then hands values out from memory. The identifier therefore exists the moment the object is constructed; whatever is left in the range when the process stops is discarded.

open as a page

What does a mapper typically demand of a mapped class, and what does each demand cost encapsulation?

level: middleimportance: should knowfreq 54%

basics

~20 s

Most mappers need a construction path taking no arguments, members they can read and write, and classes open enough to substitute an intercepting stand-in; assigned collections are replaced by tracked wrappers. Each demand weakens an invariant the constructor would enforce.

open as a page

When every column backing a flattened value object is null, what can the layer hand back and why does it matter?

level: middleimportance: should knowfreq 46%

basics

~20 s

Layers differ: some return an object whose fields are all null, others return a null reference for the whole value. Either way the round trip can be asymmetric, producing null checks that never fire and updates with nothing to update.

open as a page

How does a mapper's declared cascade of saves and deletes actually execute, and how does that differ from a schema-level cascade?

level: seniorimportance: should knowfreq 55%

basics

~10 s

A declared cascade is an in-memory graph walk at flush: the layer follows marked links and emits one ordinary statement per object. A schema cascade runs inside the engine and covers every writer.

open as a page

Why does a mapper sometimes delete every row behind a mapped collection and reinsert them instead of one targeted statement?

level: seniorimportance: should knowfreq 50%

basics

~10 s

Because it can no longer compute a difference. Assigning a new collection to the mapped field discards the snapshot the layer diffed against, leaving a wholesale delete-and-reinsert as the only safe write.

open as a page

When should an always-on tenant or live-rows filter be switched off for one unit of work, and what breaks?

level: seniorimportance: should knowfreq 47%

basics

~20 s

Only for a narrow, explicit block that genuinely must see excluded rows — a support view, a purge, a restore. What breaks is downstream: unfiltered objects stay in the tracked set and reach code that still assumes the filter.

open as a page

How should a mapped class define equality and hashing when its identifier stays empty until the row is written?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Never let the hash depend on a key that is filled in later: it changes when the row is written and strands the object inside any hash container it already joined. Keep the hash stable, and compare identifiers only when both exist.

open as a page

How does a data-access layer decide whether a saved object needs an insert or an update, and what breaks when the application assigns keys?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Usually by a marker meaning never-written: an empty identifier, an absent version, or the layer's own tracking. An application-assigned key is populated from birth, so the layer reads a new object as existing and updates a row that never existed.

open as a page

A row's type marker names a subclass the running build does not contain. What happens, and how do you handle it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Hydration cannot resolve the marker to a class, so the read fails, usually the whole query rather than the row, and it surfaces on base-type queries that select every marker. Fix the reader first, then the rows.

open as a page

When a mapped class declares nothing about a column, what defaults apply, and why is that risky over time?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Undeclared elements still get values: which members are mapped, nullability, text length, numeric precision, the derived identifier. The risk is not that defaults are wrong but that a later reader cannot tell an unconsidered default from a deliberate decision.

open as a page

In a mapper with change detection, why can an in-place edit to a document-shaped converted attribute go unwritten?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Change detection compares the current state against the snapshot taken at load. If the snapshot holds the same mutable instance the code edited, both sides show the edit, the comparison finds no difference, and no update is emitted.

open as a page

When is mapping a class hierarchy the wrong call, and what alternatives keep polymorphism out of the persistence layer?

level: principalimportance: should knowfreq 40%

basics

~20 s

Map a hierarchy only when the database must resolve the type: base-typed references, authoritative cross-type queries, shared keys or hierarchy-wide rules. Otherwise keep the base class unmapped, or serve cross-type screens from a read-only projection.

open as a page

How would you decide between mapping the domain class directly and translating to a separate persistence model at the boundary?

level: principalimportance: should knowfreq 43%

basics

~20 s

Map the domain class directly while its shape and the row's shape stay close and the mapper's demands are tolerable. Introduce a separate persistence model when the shapes genuinely diverge — accepting duplicated types and translation that must move together.

open as a page

When no ambient tenant or actor exists — a scheduled job, a migration — should filtering and stamping fail or write null?

level: principalimportance: nice to knowfreq 38%

basics

~20 s

Treat them differently: a missing tenant should fail the unit of work, since filtering on nothing either leaks or silently matches no rows, while a missing actor should resolve to a named synthetic principal rather than null.

open as a page

showing 1–30 of 31