skip to content

Object-Relational Mapping

The structural patterns that bridge objects and tables: identity fields, foreign-key and association-table mapping, lazy versus eager loading, and the three inheritance mapping strategies. The N+1 query problem lives here and is one of the most common interview questions in the area.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

6

What problem does the Identity Field pattern solve when mapping a database row to an in-memory object, and why can't you just rely on the object's own reference identity or equals() instead?

level: juniorimportance: must knowfreq 65%

answer

  1. PK mirrored in memory
  2. answers 'which row am I?'
  3. drives INSERT vs UPDATE
  4. surrogate vs natural key
  5. transient object has no id yet

basics

~20 s

Identity Field stores the database primary key inside the object so the mapper always knows which row that object represents, because loading the same row twice can create two different object instances that need to be recognized as the same thing.

solid answer

~50 s

Identity Field adds a property to a mapped object holding the value of the underlying table's primary key. Its job is to answer 'which row does this object correspond to?' so the mapping layer can decide INSERT vs UPDATE, and so two separately loaded objects representing the same row are recognized as the same entity. Reference identity isn't reliable here: the same row can be fetched by two different queries, in two sessions, or after a serialization round trip (e.g. a web form), producing distinct object instances that are still 'the same' business entity. Two brand-new unsaved objects have no database identity yet either. The identity field is usually the table's primary key - autoincrement integer, sequence, or UUID - and is normally kept separate from, or handled carefully in, equals()/hashCode(), because a transient object's identity value can change once persisted.

go deeper

for a junior

Should state, in plain terms, that the object keeps a copy of the row's primary key and that this is how the ORM knows which row to update.

for a middle

Should explain the INSERT-vs-UPDATE decision the identity field drives, and know the equals()/hashCode() pitfall for transient objects.

for a senior

Should reason clearly about surrogate vs natural key trade-offs and connect the identity field to the Identity Map within a session/unit of work.

for a principal

Should discuss identity-field design choices at a systems level: distributed ID generation, offline-first UUID generation, and the migration cost of ever changing a natural key used as identity.

## What an Identity Field is An Identity Field is a property added to a mapped class - conventionally called `id` - whose value mirrors the primary key of the table row that object represents. Mechanically, the lifecycle looks like this: 1. An application creates a new domain object with no identity value set (null, zero, or another 'unsaved' sentinel). 2. When the object is saved, the mapper issues an `INSERT`; the database (or the application, for client-assigned keys such as UUIDs) produces a primary key value, and the mapper writes that value back into the object's identity field. 3. From that moment the object is 'persistent': future `SELECT`, `UPDATE`, and `DELETE` statements target the row identified by that field, and the mapper can tell apart 'this object needs an INSERT' from 'this object needs an UPDATE' purely by checking whether the identity field is set. ## Two notions of sameness The pattern exists because in-memory objects and database rows have two different notions of sameness that do not naturally line up. - **An object** has reference identity (is this the exact same object in memory) and, if you define one, value equality (do two objects have equal fields). - **A database row** has row identity, defined by its primary key, which is stable and independent of the row's current column values. Without an explicit identity field, there is no way for application code to ask 'are these two Customer objects, loaded by two different queries, actually the same customer row?' - they might be two distinct object instances with (momentarily) identical field values, or one might be stale. The identity field is the anchor that survives across queries, across sessions, and across process boundaries (for example, an object serialized to JSON for a web form and then deserialized on form submission still carries its id, so the server knows which row to update). ## Surrogate versus natural key The main design trade-off is surrogate versus natural key as the identity field. - **A surrogate key** (autoincrement integer, database sequence, or UUID) decouples identity from business data: identity never changes even if every business attribute of the row changes, and it stays a single, simple column regardless of how many attributes make the entity 'unique' in business terms. The cost is that it's meaningless outside the application, requires an extra column, and - for autoincrement/sequence keys - complicates distributed or offline object creation, since the ID often isn't known until the row is actually inserted (this is one reason systems that create IDs offline, like mobile apps with local-first writes, favor UUIDs despite their larger size and index-fragmentation cost). - **A natural key** (e.g. an email address or national ID) avoids the extra column and is meaningful on its own, but breaks the assumption that identity should never change: if the 'natural' value must later be edited, every foreign key referencing it has to cascade, and composite natural keys make foreign-key mapping and `equals()` implementations noticeably more complex. ## The two failure modes 1. **The most common production failure mode** is building `equals()`/`hashCode()` naively off the identity field. If a newly created (transient) object is placed into a `HashSet` or used as a `HashMap` key before it is saved, its hash code is computed from a null or default identity value; once the object is persisted and the identity field is populated, its hash code changes, and the object effectively 'disappears' from the collection it was already stored in, because the collection is still bucketed by the old hash. This is a well-known gotcha with JPA/Hibernate entities and is usually fixed by excluding the identity field from `equals()`/`hashCode()` until it is set, or by using a business key, or by overriding `equals()` to fall back to reference equality for transient instances. 2. **A second failure mode** is conflating identity with business meaning: reusing a mutable natural key as the identity field means an innocuous business change (a customer changes their email) turns into a data-integrity migration across every table that stores a foreign key to it. ## Where you have already seen it Hibernate/JPA's `@Id` annotation, Rails ActiveRecord's implicit `id` column, and Django's automatic `pk` field are all direct applications of this pattern - and in each case the identity field also powers the ORM's first-level identity map, so within one unit of work, two loads of the row with id=42 return the very same object reference, not just equal ones. The pattern isn't ORM-specific either: a REST API returning {"id": 42, ...} is exposing the identity field to clients precisely so a later PUT or PATCH request can say which resource it means to modify.

  • How does an Identity Field interact with an ORM's first-level cache / Identity Map within one session or unit of work?
    The Identity Map keys its cache by the identity field's value, so when the mapper is asked to load a row it has already loaded in this session, it returns the exact same object reference instead of building a new one. This guarantees that within one unit of work, changes made through one reference are visible through every other reference to the same row, and prevents two conflicting in-memory copies of the same entity from silently diverging.
  • What goes wrong if you put the identity field straight into equals()/hashCode() before the object has been saved?
    A transient object's identity field is null or a default sentinel, so its hash code is computed from that placeholder value. If the object is inserted into a hash-based collection before being saved, then saved (which changes the identity field and therefore the hash code), the collection's internal bucket no longer matches, and lookups for that object silently fail even though it's still physically present in the collection.
  • How do surrogate keys like autoincrement or UUID differ as an identity field, and which would you pick for a distributed, offline-capable system?
    Autoincrement/sequence keys are compact and index-friendly but require a round trip to the database (or a central sequence) to be assigned, which is awkward when multiple nodes create rows offline and later sync. UUIDs can be generated locally with negligible collision risk, so they suit distributed or offline-first systems, at the cost of larger storage, worse index locality, and less human-readable identifiers.

Like a hospital wristband: two different photographs of the same patient (two object instances loaded separately) are recognized as the same person because they carry the same wristband number, not because the photos look identical.

saying these in an interview costs you the question

  • Says two objects loaded from the same row are 'different' with no way to reconcile them
  • Builds equals()/hashCode() purely off the raw id field without handling the transient/unsaved case
  • Confuses Identity Field with the Identity Map / first-level cache pattern
  • Assumes a natural key can never cause problems as an identity field
  • Can't explain how the mapper decides INSERT vs UPDATE

context

open as a page

In an ORM's Foreign Key Mapping pattern, how do you represent a one-to-many (or many-to-one) association between two tables using a foreign key column, and what problem arises when a child object must be saved before its parent has a database-assigned primary key?

level: middleimportance: must knowfreq 80%

basics

~20 s

Foreign Key Mapping stores the parent's ID as a column on the child's table (or an in-memory reference resolved to that column), so 'many' rows point back to 'one' row. The problem is you can't put a real parent ID on the child until the parent has actually been saved and been given one.

open as a page

A web app loads a list of 50 orders, then in a template loop calls order.getLineItems() for each one, and the ORM fires 50 separate SELECT statements plus the original query. What is this problem called, why does lazy loading cause it, and how would you fix it without simply switching every association to eager loading?

level: middleimportance: must knowfreq 90%

basics

~20 s

This is the N+1 selects problem: one query loads the list, then one extra query per item fetches its related data, because each lazy association is fetched separately the moment it's touched. Fix it by fetching the related data in one batched query up front, not by making everything eager everywhere.

open as a page

An ORM needs to map a class hierarchy - an Employee base class with Salaried and Hourly subclasses - onto relational tables. Compare Single Table Inheritance, Class Table Inheritance, and Concrete Table Inheritance for doing this, including what happens to nullable columns, joins, and polymorphic queries in each.

level: seniorimportance: must knowfreq 70%

basics

~20 s

Single Table Inheritance puts every subclass's columns in one wide table with lots of nulls. Class Table Inheritance splits shared and subclass-specific columns into separate tables joined by shared ID. Concrete Table Inheritance gives each subclass its own fully self-contained table, duplicating the shared columns.

open as a page

Two entities, Student and Course, have a many-to-many relationship with no extra data on the relationship itself. Explain how Association Table Mapping represents this in the relational schema, and how the mapping has to change if the relationship later needs an attribute like an enrollment date.

level: seniorimportance: should knowfreq 55%

basics

~20 s

Association Table Mapping adds a third table with two foreign key columns, one pointing to each side, so many Students can link to many Courses without duplicating data on either table. Once the relationship needs its own attribute, like an enrollment date, that link table gets a real primary key and becomes a full entity itself.

open as a page

As the tech lead redesigning persistence for a system with a deep object graph - Order pointing to LineItem, LineItem to Product, Product to Supplier, Supplier to Address - some screens need only order totals while others need the full graph. How do you decide, association by association, whether to default to lazy or eager loading, and what alternative to loading full domain objects would you consider for read-heavy endpoints?

level: principalimportance: should knowfreq 45%

basics

~20 s

There's no single right default: pick lazy or eager per association based on how often each screen actually needs it, and for read-heavy endpoints, skip loading full objects altogether and query a flat projection with just the columns that screen displays.

open as a page