Chapter 11

ORM & JPA: From JDBC to the Entity Manager

Mapping objects to tables without writing SQL. The JDBC boilerplate JPA removes, entities and the persistence context, and the four decisions every exam asks you to justify from data cardinalities — owner, mapped-by, fetch policy and cascade — plus the entity lifecycle and container transactions.

Reading: ~42 min Interactive: 1 widgets Source: Polimi Data Base 2 2025/26 — Lecture 5.1 (Application Architectures) · Polimi Data Base 2 2025/26 — Lecture 5.2 (JPA Object-Relational Mapping) · Polimi Data Base 2 2025/26 — Lecture 5.3 (The Entity Manager) · Keith & Schincariol — Pro JPA 2 in Java EE 8, Ch. 4 and 10

01 · Context

Three-tier architectures and the JEE stack

A short orientation: where in an application the code we are about to write actually runs.

Distributed architecture went one-tier (1960s–70s) → client-server (70s–80s) → RPC-based (80s) → object-oriented distributed (90s) → web (late 90s) → today’s mix of service-oriented, mobile and cloud architectures.

Client-server puts business and presentation logic in the client, which sends SQL to a server that only manages data. Three-tier inserts a middle tier that centralizes connections to the data server, masks the data model from clients, and can be replicated to scale. In a pure-HTML web application the client is a thin browser, and the middle tier holds a web server, the business logic that generates content, and the presentation assembly.

The Java EE pieces that matter here: JDBC, the first industry-standard database-independent connectivity API — still important but superseded for our purposes; Servlets in the presentation tier; EJB for server-side business components with container services; JPA, the specification for mapping relational data to objects, including the JPQL query language; and JTA, which manages transactions in a resource-agnostic way and allows transactional properties to be declared rather than coded.

02 · Motivation

The JDBC boilerplate problem

Everything JPA does is best understood as the removal of specific, tedious code. Here is the code.

The course’s running example is an expense-report application: users log in, see their travel missions, file expenses against one, and close it once reimbursed. A mission moves through three states — openreportedclosed — and the schema is three tables with foreign keys.

Written directly against JDBC, four kinds of tedium appear.

Connection management. A ConnectionHandler loads the driver and opens a connection from web.xml context parameters; every controller repeats init() and destroy() to acquire and release it. Connections end up held one per controller as a data member, which is not efficient, and forgetting to release one is costly.

Copying result rows into objects. Every query walks a ResultSet and copies column by column:

try (ResultSet result = pstatement.executeQuery()) {
  while (result.next()) {
    Mission mission = new Mission();
    mission.setId(result.getInt("id"));
    mission.setStartDate(result.getDate("date"));
    mission.setDestination(result.getString("destination"));
    // … one line per column, forever
  }
}

Copying objects back into statements. Inserts and updates do the same in reverse, binding each field to a ? placeholder in order.

Manual transaction demarcation. Two updates that must be atomic require explicit control:

connection.setAutoCommit(false);
try {
  pstatement.executeUpdate();                       // 1st update
  missionDAO.changeMissionStatus(id, REPORTED);     // 2nd, must be atomic with it
  connection.commit();
} catch (SQLException e) {
  connection.rollback();
  throw e;
} finally { connection.setAutoCommit(true); }

The lectures then ask the obvious question — wouldn’t it be better if a query returned results already in the right application type, if creating an object created a tuple, if updating an object updated the matching row, if connections were dispatched automatically, and if methods joined transactions by themselves? — and answer it with a problem-solution matrix: ORM for the two copying problems, JTA plus dependency injection for connections, and container-managed transactions for demarcation.

03 · Foundation

Entity, persistence unit, persistence context, managed entity

Four definitions. They are asked verbatim, so learn them as a quotable set.

The underlying difficulty is impedance mismatch — concepts in one model with no direct equivalent in the other:

Object model (Java)Relational model
objects, classesrows, tables
attributes, propertiescolumns
identity (memory address)primary key
reference to another entityforeign key
inheritance, polymorphismnot supported
methodsstored procedures, triggers

JPA bridges it with a POJO persistence model. The four terms:

Entity

A class (JavaBean) representing a collection of persistent objects mapped onto a relational table.

Persistence unit

The set of all classes persistently mapped to one database — analogous to a database schema.

Persistence context

The set of all managed objects of the entities defined in the persistence unit — analogous to the database instance in use.

Managed entity

An entity that is part of a persistence context, and whose state changes are tracked for automatic synchronization to the database.

Q

JPA — 6 of 14 sessions, 49 points

JPA exercises appear in 6 of the 14 papers, worth 9–10 points each. Five of the six are relationship mapping; the sixth (2024-08-30) is pure theory and asks for exactly the four definitions above, plus fetch modes and the notion of relationship owner. Note what the bank does not contain: no exercise has ever asked about the EntityManager lifecycle in depth, or about @TransactionAttribute. Sections 8 is therefore covered properly but briefly.

2024-08-30-q32024Q03JPA core conceptseasy9 pts
Consider the Object-Relational Mapping of JPA. 1. (3 pts) Define the JPA concepts of entity, persistence unit, persistence context, and managed entity. 2. (3 pts) Define the concept of fetch mode for relationships, the annotations needed to express it, and the defaults provided by JPA. 3. (3 pts) Define the notion of relationship owner in JPA, provide an example, and motivate its importance.

An entity class carries a few hard requirements: a public or protected no-argument constructor; the class must not be final, nor may any persistent field or method be; and it must implement Serializable if instances are to be passed by value as detached objects.

04 · Mapping

Identity and attribute mapping

A POJO has no durable identity. Persistence gives it one, and it is the primary key.

@Entity
public class Mission implements Serializable {
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
  private int id;
  @Temporal(TemporalType.DATE)          private Date date;
  @Enumerated(EnumType.STRING)          private MissionStatus status;
  @Basic(fetch = FetchType.LAZY) @Lob   private byte[] photo;
  @Transient                            private BigDecimal computedDiscount;
}

@Id marks the simple primary key; composite keys use @EmbeddedId or @IdClass. @GeneratedValue delegates key generation to the provider, with four strategies:

StrategyMeaning
AUTOthe provider picks whatever it likes
TABLEidentifiers come from a generator table
SEQUENCEuses the database’s sequence feature
IDENTITYuses identity/auto-increment columns

The lectures’ advice is short: whenever possible, let the database generate the ids.

For attributes, the annotations worth knowing are @Temporal (DATE / TIME / TIMESTAMP), @Enumerated (as STRING or as the ordinal), @Lob for large objects, @Transient for fields that are not persisted, and @Table / @Column to override the default naming — by default an entity maps to a table of the same name and each field to a column of the same name. @Column also carries schema generation hints such as nullable, unique and length.

×

Always pair @Lob with lazy fetching

The published grader’s comments for January 2024 list “missing annotations for Blob fields” among the common errors, noting that the typical annotations are among the goals for that part of the exam. A photo or media file loaded eagerly on every query is a real performance bug, and the fix is @Lob @Basic(fetch = FetchType.LAZY). It is one line, and it is worth marks.

05 · Mapping

Relationships: directionality, cardinality, ownership

The centre of the chapter and of the exam. Every relationship has four characteristics, and getting ownership right is what most answers turn on.

  • Directionality. All JPA relationships are unidirectional. A bidirectional relationship is a matched pair of unidirectional mappings, and the matching must be declared explicitly with mappedBy.
  • Role. One entity is the source, the other the target of each direction.
  • Cardinality. Four combinations: many-to-one, one-to-many, one-to-one, many-to-many. A to-one direction means a single reference; a to-many means a collection.
  • Ownership. One side owns the relationship.

The owner is whichever table physically holds the foreign key

In the database a relationship is implemented by a FK column — in JPA, a join column. For 1:N and 1:1, the entity whose table stores that column is the owner, and its side is the owning side. It matters for three reasons: the physical-mapping annotations (@JoinColumn, @JoinTable) go on the owner; the inverse side carries mappedBy; and — the part that bites — only changes made on the owning side are persisted.

That last point deserves the code, because it is counter-intuitive:

// OWNING side — Employee holds department_id.  Generates UPDATE EMPLOYEE SET department_id = 3 …
emp.setDepartment(newDept);
em.persist(emp);

// INVERSE side — no SQL is generated at all.
dept.getEmployees().add(emp);
em.persist(dept);

The mappings themselves:

// 1:N bidirectional — the MANY side is always the owner
@Entity class Employee {
  @ManyToOne @JoinColumn(name = "dept_fk")     // owner: its table holds the FK
  private Department dept;
}
@Entity class Department {
  @OneToMany(mappedBy = "dept")                 // inverse: points at the owning attribute
  private Collection<Employee> employees;
}

// 1:1 — either side may own; the one with the FK does
@Entity class Employee {
  @OneToOne @JoinColumn(name = "P_SPACE_FK") private ParkingSpace parkingSpace;
}
@Entity class ParkingSpace {
  @OneToOne(mappedBy = "parkingSpace") private Employee employee;
}

// N:M — no FK column exists, so a join table is used and either side may own
@Entity class Employee {
  @ManyToMany
  @JoinTable(name = "EMP_PROJ",
             joinColumns = @JoinColumn(name = "EMP_ID"),
             inverseJoinColumns = @JoinColumn(name = "PROJ_ID"))
  private Collection<Project> projects;
}
@Entity class Project {
  @ManyToMany(mappedBy = "projects") private Collection<Employee> employees;
}

Two defaults save typing. Without @JoinColumn, a to-one mapping’s column is named <attribute>_<target PK column>parkingspace_id. Without @JoinTable, an N:M join table is named <Owner>_<Inverse> with columns <Owner>_id and <Inverse>_id.

×

mappedBy is what stops JPA creating a bridge table

Omit mappedBy on a 1:N and the default mapping does not silently fall back to the FK — it creates a bridge table, exactly as for N:M. So mappedBy is doing two jobs: declaring which side is inverse, and telling JPA “the relationship is already mapped by a FK in the other entity, do not build a join table”. The grader’s comments for January 2024 flag @JoinTable/@JoinColumn/mappedBy placed on the wrong side relative to the declared owner as a recurring error, with a penalty when the owner was not stated explicitly at all.

One more annotation appears in nearly every exam solution and is easy to forget: @OrderBy("creationdate DESC") on a collection, whenever the application shows that collection in a particular order. Missing @OrderBy is the first item on January 2024’s list of common errors.

06 · Mapping

Fetch policies

When you load an entity, are its related entities loaded too? The answer is a performance decision, and the exam wants it justified from the stated cardinalities.

The fetch policy is set with the fetch attribute of the relationship annotation:

  • FetchType.EAGER — load the related entities immediately.
  • FetchType.LAZY — defer until they are actually accessed.

The defaults, which are asymmetric

When the fetch mode is not specified: a single-valued (to-one) relationship is fetched EAGERly; a collection-valued (to-many) relationship is loaded LAZYily.

Why it matters: an entity with four eager to-one relationships loads its address, department, company and profile on every single query, whether or not the application needs them — larger queries, unnecessary joins, more data transferred. The lectures recommend treating lazy as the appropriate default for all relationships, overriding to eager only where the data really is needed immediately.

Two asymmetries are worth internalising. First, in a bidirectional relationship the two directions may differ — commonly they should, since the two navigations have different cardinalities. Second, and subtler:

×

LAZY is a hint; EAGER is a promise

A directive to fetch lazily is only a hint to the persistence provider, which may ignore it — loading data early never breaks correctness. The converse is not true: specifying eager may be critical, because once an entity is detached the un-fetched associations can no longer be loaded at all.

The exam’s reasoning pattern is always the same. From September 2025: authors are in the thousands, countries in the tens, books in the tens of thousands, readers in the millions. So Reader → Country can be eager (one country per reader), while Country → Reader must be lazy (a country has millions of readers), and Book → Author can be eager (few authors per book) while a popular keyword’s article list must be lazy. Quote the cardinality in the justification — that is where the marks are.

07 · Mapping

Cascading and orphan removal

By default, an entity-manager operation applies only to the entity you hand it. Sometimes that is right; sometimes it is not.

Employee emp = new Employee();
Department dept = new Department();
emp.setDepartment(dept);
em.persist(emp);        // only emp is persisted — dept is not
em.remove(dept);        // deletes only the Department; fails if FKs still reference it

For remove that default is usually what you want. For persist it usually is not — a dependent child should normally be saved with its parent. The cascade attribute says so:

@OneToMany(cascade = CascadeType.PERSIST) private Address address;

The available values are PERSIST, REFRESH, REMOVE, MERGE, DETACH, and ALL as shorthand for all five. Three rules about them:

  • There is no default cascade type. By default no operations are cascaded.
  • Cascade settings, like relationships, are unidirectional — set them explicitly on both sides if both directions need the behaviour.
  • Cascading REMOVE can generate inefficient SQL (related entities deleted one by one in separate statements) and should be avoided in most practical cases.

Orphan removal is a stronger, different thing, available on @OneToOne and @OneToMany:

@OneToMany(mappedBy = "dept", orphanRemoval = true) private List<Employee> employees;

It suits a privately owned parent-child relationship — a weak entity, in ER terms — where each child belongs to exactly one parent through exactly one relationship. The child is deleted whenever the link is broken, not only when the parent is.

Deep dive orphanRemoval versus CascadeType.REMOVE

The two coincide when the parent is deleted and differ everywhere else. Take dept.setEmployees(null) — simply dropping the reference:

  • With orphanRemoval = true, the related Employee entities are removed from the database automatically, provided they are managed and no longer referenced.
  • With CascadeType.REMOVE, they are not removed: the cascade only fires when the parent itself is deleted.

The same distinction applies to dept.getEmployees().remove(emp) — orphan removal deletes emp, cascade-remove does nothing. And a caveat for both: if the orphaned object is not in the managed state, removal does not occur.

The practical rule for exam answers: use REMOVE (or orphanRemoval) only where the child genuinely cannot exist without its parent — a configuration without its product, a profile without its author. Never across a shared association such as authors-and-books or users-and-countries, where deleting one side would destroy data that other entities still reference.

08 · Runtime

The entity manager, the lifecycle, and transactions

The persistence context is a kind of in-memory database of managed objects. The application never sees it directly — it talks only to the entity manager.

The EntityManager is the central authority for all persistence actions:

MethodEffect
persist(entity)make a transient instance managednot the same as writing it
find(Class, primaryKey)load by primary key; the result becomes managed, or null
remove(entity)schedule the tuple for deletion; the Java object still exists
refresh(entity)overwrite the object from the database
flush()write pending changes to the database as soon as possible

Four properties of the persistence context explain the whole model: database writes happen asynchronously, at a time the provider chooses; for them to happen at all the context must be attached to a transaction; a managed entity has two lives, one as a Java object and one as a tuple bound to it, with the POJO’s id equal to the tuple’s primary key; and that binding exists only inside the persistence context — once the object leaves, it is untracked.

Hence the lifecycle:

NEW

Just constructed. Unknown to the entity manager, no persistent identity, no tuple. new Employee() alone does nothing to the database.

MANAGED

In a persistence context. Changes to the object are automatically synchronized to the database — but not vice versa.

DETACHED

Has an identity potentially matching a tuple, but changes are no longer propagated.

REMOVED

Scheduled for deletion; becomes deleted when the transaction commits or flush() runs.

×

persist() does not write to the database

It makes the entity managed. The row appears when the associated transaction commits, or earlier if you call flush(). Likewise remove() does not erase the Java object — it breaks the association and schedules the tuple for deletion. Calling persist() on an already-managed entity is legal and triggers the cascade process.

Transactions exist at three levels: DBMS transactions, demarcated by SQL; resource-local transactions, created through the JDBC connection interface and managed by the application; and container transactions, defined through JTA and mapped by the container onto the ones below. This course uses the container level, where the entity manager is injected and the transaction is provided automatically:

@Stateless
public class MissionService {
  @PersistenceContext(unitName = "MyPersistenceUnit")
  private EntityManager em;

  // transaction started here by the container
  public void closeMission(int missionId, int reporterId) throws BadMissionForClosing {
    Mission mission = em.find(Mission.class, missionId);
    if (mission.getStatus() != MissionStatus.REPORTED) throw new BadMissionForClosing("…");
    mission.setStatus(MissionStatus.CLOSED);
  }   // transaction committed by the container — no demarcation code at all
}

Compare that with the JDBC version in section 2: the setAutoCommit, commit, rollback and finally block have all disappeared. When a method calls another business method, the same transaction is reused by default; @TransactionAttribute overrides that per method, with values MANDATORY (a transaction must already be active), REQUIRED (the default — join one or start one), REQUIRES_NEW (always its own; any active transaction is suspended), SUPPORTS, NOT_SUPPORTED (suspend any active transaction) and NEVER (throw if one is active).

09 · Exam

Filling the relationship table

Since February 2025 the JPA exercise has had a fixed answer format: a table, one block per relationship direction. Practising the format is worth as much as knowing the material.

Each block asks for five things: Required? (with motivation), Owner, FetchType, CascadeType, and the complete annotation. Two instructions in the rubric catch people out: even when the answer to “Required?” is No, you must still fill in every other field as if the relationship were mapped; and you must always choose an owner, even for an N:M where either side would do.

Worked exam Working a relationship block (exam 2026-01-22)

The schema and the access patterns

Author, AuthorProfile (1:1, optional), Article (each with exactly one main author), Keyword (N:M with Article). Scale: 50 K authors, ≤50 K profiles, 200 K articles, 2 K keywords; 1–50 articles per author; 5–10 keywords per article; popular keywords tag tens of thousands of articles. Access patterns: (1) author → profile + articles, (2) keyword → articles + each main author, (3) article → main author + keywords.

Author → AuthorProfile

Required? Yes — pattern 1 needs it. Owner? AuthorProfile: the author_id FK sits in its table, and the FK decides ownership regardless of which entity the application starts from. Fetch? EAGER — a single correlated instance. Cascade? REMOVE — a profile cannot outlive its author. Annotation: @OneToOne(mappedBy = "author", fetch = EAGER, cascade = REMOVE).

AuthorProfile → Author

Required? No — nothing in the access patterns navigates this way; map it for symmetry. Still answer the rest: owner AuthorProfile (unchanged — ownership is a property of the relationship, not the direction), EAGER, no cascade, and @OneToOne @JoinColumn(name = "author_id") — the owning side carries the join column.

Keyword → Article

Required? Yes — pattern 2. Owner? N:M, so either; pick Article and say so. Fetch? LAZY — a popular keyword tags tens of thousands of articles. Cascade? None — N:M between independent entities. Annotation: @ManyToMany(mappedBy = "keywords", fetch = LAZY).

Author → Article

Required? Yes — pattern 1. Owner? Article, which holds main_author_id. Fetch? LAZY (1–50 articles; EAGER is defensible if you argue it). Cascade? REMOVE, given the schema’s semantics. Annotation: @OneToMany(mappedBy = "mainAuthor", fetch = LAZY, cascade = REMOVE).

Then the entity code

Part (b) asks for one entity in full. For Article: @Id @GeneratedValue(IDENTITY), @Temporal(DATE) on the date, @ManyToOne(fetch = EAGER) @JoinColumn(name = "main_author_id") for the owner side of the author link, and the owning @ManyToMany with an explicit @JoinTable naming article_keywords and both join columns — EAGER here, because 5–10 keywords is a small collection.

2026-01-22-q32026Q03JPA mapping & annotationsmedium10 pts
An academic application has tables Author(id, name, email, country), AuthorProfile(author_id, orcid, institution, bio), Article(article_id, title, publicationDate, main_author_id), Keyword(keyword_id, name), Article_keywords(article_id, keyword_id). An author optionally has one profile (owned 1:1); each article has exactly one main author; articles carry many keywords and vice versa. Scale — 50K authors, ≤50K profiles, 200K articles, 2K keywords; 1–50 articles per author; 5–10 keywords per article; popular keywords tag tens of thousands of articles. Access patterns — (1) author id → author + profile + articles; (2) keyword name → articles + each article's main author; (3) article id → article + main author + keywords. a) (6 pts) For the directions Author→AuthorProfile, AuthorProfile→Author, Keyword→Article and Author→Article, give: Required? (motivated), Owner, FetchType, CascadeType, and the COMPLETE annotation. b) (4 pts) Write the JPA entity mapping Article, justifying owners, fetch policies and cascade types against the access patterns and cardinalities.

A Category has tens of Brands; a Brand belongs to exactly one Category. Which entity owns the relationship, and where does mappedBy go?

Load-bearing ideas

  • JPA removes four kinds of JDBC boilerplate: connection management, ResultSet→object copying, object→statement copying, and manual transaction demarcation.
  • The four definitions. Entity = class mapped to a table. Persistence unit = all mapped classes (≈ schema). Persistence context = all managed objects (≈ instance). Managed entity = one whose changes are tracked. Asked verbatim in 2024-08-30.
  • The owner is the side holding the FK — the “many” side of a 1:N, either side of a 1:1 or N:M. @JoinColumn/@JoinTable go on the owner; mappedBy goes on the inverse; only owner-side changes are persisted; and omitting mappedBy makes JPA build a bridge table.
  • Fetch defaults are asymmetric: to-one EAGER, to-many LAZY. LAZY is a hint the provider may ignore; EAGER can be essential for detached objects. Justify every choice with the given cardinality.
  • No cascade by default. REMOVE only where the child cannot exist without the parent, never across shared associations. orphanRemoval additionally deletes on link-breaking, which CascadeType.REMOVE does not.
  • persist() makes an entity managed, not written. NEW → MANAGED → DETACHED / REMOVED, with the object→database synchronization one-directional.
  • Container-managed transactions wrap each business method; nested calls reuse the same transaction unless @TransactionAttribute says otherwise.
  • Exam radar. Re-derive before the exam: the relationship table format with all five fields filled even for “Required? No”; the ownership rule and what happens if you update the inverse side; the fetch defaults; and @OrderBy plus @Lob @Basic(fetch = LAZY), the two annotations the published grader’s notes single out as commonly missing.