Topic revision clinic · Data Management · 34 misses

Data Management — the 34 you got wrong

Built from your run through the 416-question Data Management bank. This is not an exam review — it's a topic rebuild, organised the way the subject is organised rather than by question number, so you can revise it in one sitting and come back to it. Data Management is your most volatile section — it has gone 91% → 36% → 75% → 57% across your last four papers, swinging further than anything else on the syllabus. The reason is visible in these 34: they cluster hard, and the clusters are exactly the material that looks familiar and turns out not to be.

15
Transactions (44%)
11
Spring Data JPA
8
JdbcTemplate & JDBC
5
Propagation alone
Where the 34 actually fall. Half the topic is fine; the damage is concentrated.
Transaction propagation 5Q12, Q37, Q77, Q95, Q98 Spring Data repositories 5Q2, Q13, Q26, Q27, Q79 JdbcTemplate methods 4Q1, Q11, Q64, Q73 Transaction fundamentals 4Q3, Q52, Q66, Q92 JPA / Boot configuration 4Q30, Q65, Q75, @GeneratedValue Isolation levels 3Q16, Q17, Q56 Transaction managers 3Q45, Q47, Q67 JdbcTemplate callbacks 3Q14, Q53, Q90 @Query 2Q33, Q99 Exception hierarchy 1Q51

The headline: five separate questions asked you to name a propagation level, and you missed all fiveNOT_SUPPORTED, NEVER, MANDATORY twice, and REQUIRED as the default. Three more asked which isolation level permits which anomaly. Those two tables are eight marks of this topic and about ten minutes of memorising. Learn them first — they're printed below.

Master table 1 — the seven propagation levels

Every propagation question is answered by reading two columns of this table. Learn it as "what happens with an outer transaction" and "what happens without one".

PropagationOuter transaction existsNo outer transactionRemember it as
REQUIRED (default)join itstart a new one"always transactional" — 99% of methods
REQUIRES_NEWsuspend it, start a new onestart a new one"always my own transaction"
NESTEDsavepoint inside itstart a new one"partial rollback" — JDBC savepoints only
SUPPORTSjoin itrun non-transactionally"I don't mind either way"
NOT_SUPPORTEDsuspend it, run non-transactionallyrun non-transactionally"never transactional, but polite about it"
MANDATORYjoin itthrow IllegalTransactionStateException"you must give me one"
NEVERthrow IllegalTransactionStateExceptionrun non-transactionally"you must not give me one"

The three pairs that make it memorable. MANDATORY and NEVER are mirror images — both throw, on opposite conditions. REQUIRES_NEW and NOT_SUPPORTED both suspend — one to start its own transaction, one to have none at all. SUPPORTS and NOT_SUPPORTED differ only when an outer exists — join vs suspend. Everything else is REQUIRED.

Two facts that ride along: propagation is only evaluated when the call passes through the proxy, so self-invocation ignores it entirely; and NESTED needs JDBC savepoints — it works with DataSourceTransactionManager but not with JtaTransactionManager, and not with reactive transactions.

Master table 2 — isolation levels and the three read anomalies

Three of your 34 were this table asked from three different angles. It is four rows.

Isolation levelDirty readNon-repeatable readPhantom read
READ_UNCOMMITTEDpossiblepossiblepossible
READ_COMMITTEDpreventedpossiblepossible
REPEATABLE_READpreventedpreventedpossible
SERIALIZABLEpreventedpreventedprevented

It is a staircase — each level prevents one more anomaly than the one above, in a fixed order: dirty → non-repeatable → phantom. If you can reconstruct that ordering you can rebuild the whole table from memory, and every question is then a lookup.

AnomalyOne sentence
Dirty readyou read a change another transaction hasn't committed yet, and it might be rolled back
Non-repeatable readyou read the same row twice and it changed — someone updated it and committed in between
Phantom readyou run the same query twice and get extra rows — someone inserted matching rows and committed

In Spring: @Transactional(isolation = Isolation.REPEATABLE_READ). The default is Isolation.DEFAULT, which means whatever the datastore's default is — Postgres, Oracle and SQL Server say READ_COMMITTED; MySQL InnoDB says REPEATABLE_READ. Isolation is also the only ACID letter you configure in Spring, which is why "concurrent" or "as if alone in the system" in a question stem always points at Isolation, never Atomicity.

Jump to a section

① Transaction propagation — 5 ② Transaction fundamentals — 4 ③ Transaction managers — 3 ④ Isolation levels — 3 ⑤ JdbcTemplate methods — 4 ⑥ JdbcTemplate callbacks — 3 ⑦ The exception hierarchy — 1 ⑧ Spring Data repositories — 5 ⑨ @Query — 2 ⑩ JPA & Boot configuration — 4

① Transaction propagation 5 misses

All five are answered by master table 1. Read them as five angles on one table rather than five separate facts.

Q12 · "Executes non-transactionally, suspends the current transaction if one exists"

NOT_SUPPORTED

Your answer: SUPPORTS, MANDATORY, REQUIRED and REQUIRES_NEWfour ticks on a one-answer question. Only two levels suspend at all, and only one of those is non-transactional.

Take the stem apart clause by clause — the exam builds these definitions from exactly two facts:

ClauseRules outLeaves
"executes non-transactionally"REQUIRED, REQUIRES_NEW, NESTED, MANDATORYSUPPORTS, NOT_SUPPORTED, NEVER
"suspends the current transaction"SUPPORTS (joins), NEVER (throws)NOT_SUPPORTED

What suspension actually does, because knowing the mechanism makes the table stick: AbstractPlatformTransactionManager calls doSuspend(), which unbinds the ConnectionHolder/EntityManagerHolder from TransactionSynchronizationManager and stashes it. The inner method then runs with a clean thread state — any JDBC call it makes gets its own connection in auto-commit — and the resources are rebound afterwards.

The practical trap: a NOT_SUPPORTED method that then uses the same EntityManager will fail, because the EM is unbound. And calling it via this. from inside the transactional method bypasses the proxy, so no suspension happens at all and the code silently runs inside the outer transaction.

Memory hook: Only REQUIRES_NEW and NOT_SUPPORTED suspend. REQUIRES_NEW then starts its own; NOT_SUPPORTED runs with none.
Q37 · "Executes non-transactionally; throws an exception if a transaction exists"

NEVER

Your answer: REQUIRES_NEW, REQUIRED, SUPPORTS and MANDATORYfour ticks again. MANDATORY is the exact mirror of the right answer, which is why it feels plausible.

Only two propagation levels throw at all, and they throw on opposite conditions. Learn them as a pair and both questions collapse into one fact:

Outer transaction existsNo outer transaction
MANDATORYjoin itthrows
NEVERthrowsruns non-transactionally

Both throw the same exception — IllegalTransactionStateException — which is itself a commonly-asked detail. Read them as assertions rather than behaviours: MANDATORY says "my caller must have opened a transaction"; NEVER says "I must not be inside one". Neither ever creates a transaction.

Memory hook: MANDATORY and NEVER are the only two that throw, and they mirror each other. Same exception: IllegalTransactionStateException.
Q77 & Q95 · "Must use an existing transaction, or throw" asked twice, missed twice

MANDATORY — and calling it with no transaction throws IllegalTransactionStateException

Q77: you ticked NEVER and REQUIRES_NEW. Q95: you ticked "it starts a new transaction". Two questions, same level, two different wrong answers — and on Q95 you chose the behaviour of REQUIRED.

The distinction Q95 was really testing is MANDATORY vs REQUIRED, and it is a single word:

No outer transaction
REQUIREDcreates one
MANDATORYthrows — it never creates
@Transactional(propagation = Propagation.MANDATORY)
public void auditAction(Action action) {
    // called with no active transaction → IllegalTransactionStateException
    // called from a @Transactional method → joins that transaction
}

Why anyone uses it: it's a guard on a helper that only makes sense as part of a larger unit of work — an audit write, a partial update — and must never be invoked standalone. It fails loudly at the boundary instead of quietly committing on its own.

Memory hook: MANDATORY demands a transaction and never makes one. REQUIRED makes one if needed. That's the whole difference.
Q98 · The default propagation level

REQUIRED

Your answer: you also ticked REQUIRES_NEW. Close in name, opposite in behaviour: REQUIRED joins an existing transaction, REQUIRES_NEW refuses to.

REQUIRED is the default for @Transactional and for TransactionDefinition, and it is what you want essentially always: join the caller's transaction so the whole request commits or rolls back as one unit; start one if the caller had none.

The contrast worth owning, because it's the other stock question: REQUIRES_NEW takes a second connection from the pool while the outer transaction still holds the first. Its legitimate use is work that must survive the caller's rollback — audit records, failure logs. Its hazard is that a pool sized for one connection per request can deadlock against itself under load.

Memory hook: Default is REQUIRED — join or create. REQUIRES_NEW always suspends and always takes a second connection.

② Transaction fundamentals 4 misses

Q3 · "A set of actions treated as one" / "the JDBC mode to disable"

unit of work, and auto-commit

Your answer: "auto-commit, unit of work"the right two terms in the wrong order. Read the second blank first: only one of the four options is a JDBC mode.

This is the raw JDBC that Spring's transaction abstraction hides:

connection.setAutoCommit(false);   // ← the mode you must disable
try {
    // several statements — together, the unit of work
    connection.commit();
} catch (Exception e) {
    connection.rollback();
}

Auto-commit is JDBC's default, and with it on every single statement is its own transaction — so there is nothing to roll back as a group. Spring's transaction managers do exactly this for you: DataSourceTransactionManager.doBegin() calls setAutoCommit(false), binds the connection to the thread, and restores the flag on completion.

Note the term the other distractors used: transaction demarcation is the act of marking where a transaction begins and ends — which is what @Transactional (declarative) and TransactionTemplate (programmatic) do. That's vocabulary worth having, since questions use it as a stem.

Memory hook: Unit of work = what you group. Auto-commit = the JDBC mode you turn OFF to group it. Demarcation = where you draw the boundary.
Q52 · @Transactional(noRollbackFor = CustomUncheckedException.class)

The transaction commits, and the exception still propagates

Your answer: "the transaction is rolled back due to the unchecked exception"that's the default, which noRollbackFor exists precisely to override.

The default rule, and the four attributes that bend it:

Exception typeDefaultOverride with
RuntimeException / Error (unchecked)rolls backnoRollbackFor, noRollbackForClassName
Checked exceptioncommitsrollbackFor, rollbackForClassName

Two things people get wrong about this. First, noRollbackFor does not swallow the exception — it still propagates to the caller; it only changes the commit/rollback decision. Second, every rollback attribute contains the word For: there is no bare rollback attribute, which is a stock "which of these is not an attribute" trap.

The legitimate use case: a business-rule violation that should still persist something written earlier in the same transaction — an audit row, an attempt counter. Use it sparingly.

Memory hook: Unchecked rolls back, checked doesn't. Every rollback attribute has "For" in it. noRollbackFor commits but still throws.
Q66 · Valid @Transactional attributes

isolation and propagationwriteOnly, readWrite and nestedTransaction don't exist

Your answer: you ticked writeOnlyan invented name. The real one is readOnly, and there is no write-side counterpart.

The complete attribute list, which is short enough to hold whole:

AttributeTypeDefault
propagationPropagation enumREQUIRED
isolationIsolation enumDEFAULT (the datastore's)
timeoutint seconds-1 (none)
readOnlybooleanfalse
rollbackFor / rollbackForClassNameclasses / strings
noRollbackFor / noRollbackForClassNameclasses / strings
transactionManager (alias value)bean namethe primary one

Note what isn't there: no nesting attribute (that's propagation = Propagation.NESTED), no retry, no writeOnly. And @Transactional does not support SpEL — every value is a fixed constant, enum or class literal.

Memory hook: propagation · isolation · timeout · readOnly · the four rollback*For* · transactionManager. Nothing else.
Q92 · Transactions spanning several resources

Global transactions — managed by JTA

Your answer: "Composite"plausible-sounding but not a term Spring or the JTA specification uses.

Two kinds of transaction, and the manager each one needs:

TypeScopeManager
Localone resource — a single databaseDataSourceTransactionManager, JpaTransactionManager
Globalseveral resources — databases and message queuesJtaTransactionManager

Global transactions use two-phase commit (2PC): a prepare phase where every resource confirms it can commit, then a commit phase where they all do — or all roll back if any resource refused. That's what makes "write to the DB and post to the queue atomically" possible, and why it needs XA-capable drivers and an external transaction coordinator.

The exam point behind the vocabulary: Spring's PlatformTransactionManager abstraction means your application code doesn't change when you move from local to global — you swap the manager bean, not the @Transactional annotations. And don't confuse global (multi-resource) with nested (a savepoint inside one transaction).

Memory hook: Local = one resource. Global = many, via JTA and two-phase commit. Nested = a savepoint, not a second resource.

③ Transaction managers 3 misses

Three questions, one hierarchy. Learn the four class names and the interface above them and all three are free.

Q47 & Q67 · The central transaction interface asked twice

PlatformTransactionManagergetTransaction(), commit(), rollback()

Q47: you ticked TransactionManager (the empty marker interface). Q67: you ticked "an interface used to implement synchronized sections". The second is a whole category error — transactions are about atomicity, not thread synchronisation.

The type hierarchy, which is exactly what the distractors play on:

TypeWhat it is
TransactionManageran empty marker interface (Spring 5.2+) — the common parent, no methods
PlatformTransactionManagerthe central SPI for imperative transactions: three methods
ReactiveTransactionManagerthe reactive sibling
TransactionTemplatea helper that delegates to a PlatformTransactionManager
TransactionInterceptorthe AOP MethodInterceptor that implements @Transactional

How the pieces connect — knowing this answers a whole family of questions: @Transactional is applied by TransactionInterceptor, which turns the annotation's attributes into a TransactionDefinition, asks the configured PlatformTransactionManager for a TransactionStatus, runs your method, then calls commit() or rollback() on that manager. TransactionTemplate does the same thing, just triggered by your code rather than by a proxy.

Memory hook: PlatformTransactionManager = getTransaction / commit / rollback. TransactionManager is an empty marker. TransactionTemplate and @Transactional both end up calling the manager.
Q45 · The manager for a single JDBC DataSource

DataSourceTransactionManager

Your answer: JDBCPlatformTransactionManageran invented name. All three distractors began with "JDBC", and none of them exist. The real class is named after the DataSource, not after JDBC.

The four implementations, and when Boot picks each:

ImplementationManagesAuto-configured when
DataSourceTransactionManagera JDBC Connectiononly JDBC on the classpath
JpaTransactionManagera JPA EntityManager (and its JDBC connection)JPA on the classpath
HibernateTransactionManagera Hibernate Sessionplain Hibernate, no JPA
JtaTransactionManagerdistributed / XA resourcesa JTA starter
R2dbcTransactionManagerreactive connectionsR2DBC — implements ReactiveTransactionManager

The pitfall worth knowing: JpaTransactionManager also manages the underlying JDBC connection, so one manager covers both your JPA code and plain JdbcTemplate code sharing the same DataSource. Declaring both a JpaTransactionManager and a DataSourceTransactionManager on the same DataSource is a classic misconfiguration — the JDBC one bypasses the EntityManager lifecycle and flushes happen at the wrong time, or not at all.

And if several managers do exist in the context, @Transactional must name one: @Transactional("jpaTxManager").

Memory hook: DataSource → DataSourceTransactionManager. JPA → JpaTransactionManager (which also covers JDBC). XA → JtaTransactionManager. Nothing is called "JDBCSomethingTransactionManager".

④ Isolation levels 3 misses

Three questions, one table — the one printed at the top of this page. They approached it from three directions: "which levels allow X", "which levels prevent X", and "which level prevents A but allows B and C".

Q16 · Where can non-repeatable reads occur?

READ_UNCOMMITTED and READ_COMMITTED

Your answer: you also ticked SERIALIZABLEthe level that prevents everything. Whenever SERIALIZABLE appears in a "can this anomaly happen" question, the answer is always no.

Read the staircase downwards for "can occur", upwards for "prevented":

LevelNon-repeatable read
READ_UNCOMMITTEDcan occur
READ_COMMITTEDcan occur
REPEATABLE_READprevented — it's in the name
SERIALIZABLEprevented

REPEATABLE_READ is the level whose name is the guarantee — re-reading a row inside one transaction returns the same value. Anything weaker allows the row to change under you.

Memory hook: SERIALIZABLE never allows an anomaly. REPEATABLE_READ's name is its guarantee. The two READ_* levels allow non-repeatable reads.
Q17 · Where can non-repeatable reads not occur?

REPEATABLE_READ and SERIALIZABLE

Your answer: under-selectedyou took one of the two. This is the same fact as Q16 asked with the polarity flipped, so if you get one you should get both.

Treat "which prevent" and "which allow" as the same question. The four levels split two-and-two on non-repeatable reads, so whichever way it's asked the answer is a pair:

Question formAnswer
"non-repeatable reads can occur"READ_UNCOMMITTED, READ_COMMITTED
"non-repeatable reads cannot occur"REPEATABLE_READ, SERIALIZABLE
"phantom reads can occur"all except SERIALIZABLE (three of them)
"dirty reads can occur"READ_UNCOMMITTED only

Notice the counts — 1, 2, 3. Dirty reads are possible at exactly one level, non-repeatable at two, phantoms at three. If your answer doesn't have that many options ticked, you've made a mistake.

Memory hook: Dirty = 1 level · non-repeatable = 2 levels · phantom = 3 levels. Count your ticks against that.
Q56 · Prevents dirty reads, allows non-repeatable and phantom reads

READ_COMMITTED

Your answer: you also ticked REPEATABLE_READ and SERIALIZABLEboth prevent more than the question described. The stem specifies exactly one row of the table.

Three constraints, and each one eliminates:

ClauseEliminates
"prevents dirty reads"READ_UNCOMMITTED
"allows non-repeatable reads"REPEATABLE_READ, SERIALIZABLE
"allows phantom reads"(already eliminated)

READ_COMMITTED is worth knowing well for a second reason: it is the default in PostgreSQL, Oracle and SQL Server, so it's what Isolation.DEFAULT resolves to in most real applications. MySQL InnoDB is the outlier at REPEATABLE_READ.

Memory hook: READ_COMMITTED = "only committed data, but no promise it stays the same". The default nearly everywhere except MySQL.

⑤ JdbcTemplate methods 4 misses

Q1 & Q64 · Which method for INSERT / UPDATE asked twice, missed twice

update() — and it returns the number of rows affected

Q1: you ticked queryForList, query and execute. Q64: you ticked query and execute. The same question twice, and both times execute was among your picks.

The method names map cleanly onto SQL categories — learn it as a grid and all four questions in this section resolve:

MethodSQLReturns
update()DML — INSERT, UPDATE, DELETEint — rows affected
batchUpdate()the same, many timesint[]
query()SELECT, many rowsList<T> via a RowMapper
queryForObject()SELECT, exactly one rowT
queryForList()SELECT, many rowsList<Map<String,Object>>
execute()DDL — CREATE, ALTER, DROPvoid

The distinction to nail is update() vs execute(), since it caught you twice. execute() can run any SQL, but it gives you no row count and is intended for schema statements. When a question says "recommended for", it wants the purpose-built method.

int rows = jdbcTemplate.update(
        "INSERT INTO users (name, email) VALUES (?, ?)", "Alice", "alice@example.com");

jdbcTemplate.execute("CREATE TABLE users (id SERIAL, name VARCHAR(255))");
Memory hook: update = DML (returns a count) · query* = SELECT · execute = DDL. "Recommended for INSERT/UPDATE" is always update().
Q73 · Query for a single object mapped from a single row

queryForObject(sql, RowMapper<T>, args…)

Your answer: queryForMapclose, but it returns a Map<String,Object> keyed by column name, not a mapped domain object.

The single-row family, and what each one hands back:

MethodReturns
queryForObject(sql, RowMapper, args)your mapped domain object
queryForObject(sql, Class<T>, args)a scalarInteger, String, BigDecimal
queryForMap(sql, args)Map<String,Object> keyed by column
queryForRowSet(sql, args)SqlRowSet — you still iterate and extract

All the queryFor…Object/Map variants enforce cardinality at the Spring layer: zero rows throws EmptyResultDataAccessException, more than one throws IncorrectResultSizeDataAccessException. That's the most-asked fact about queryForObject. If "not found" is a normal outcome for you, use query(...).stream().findFirst() instead of catching.

On mappers: BeanPropertyRowMapper matches columns to JavaBean properties (case-insensitive, underscores auto-mapped) and silently leaves fields at defaults when a column is missing or misspelled. DataClassRowMapper (Spring 5.3+) does constructor binding and works with records.

Memory hook: queryForObject + RowMapper = one mapped object. It demands exactly one row — 0 and 2+ both throw.
Q11 · What JdbcTemplate does for you

Resource management, SQL execution, boilerplate — and it translates exceptions rather than rethrowing them

Your answer: you ticked "it catches JDBC exceptions and rethrows them as is"the one false statement. Translation is the entire point of the abstraction.

The four responsibilities, which is what "what does JdbcTemplate do" questions enumerate:

ResponsibilityDetail
Resource managementacquires and releases connections, statements, result sets — even on exception
SQL executionqueries, updates, batch updates, stored procedure calls
Core JDBC workflowstatement creation, parameter binding, execution
Exception translationchecked SQLExceptionunchecked DataAccessException

Why translation matters, and why it's always in these questions: the JDBC SQLException is checked, and its meaning depends on vendor-specific error codes. Spring maps those codes onto a consistent, unchecked hierarchy — so a unique-constraint violation is DuplicateKeyException whether you're on Postgres or Oracle, and the same hierarchy also covers JPA and Hibernate. That's what lets you swap persistence technology without touching a catch block.

Note that JdbcTemplate does not manage transactions — that's the transaction manager's job — and it is thread-safe once configured, which is why you inject one shared instance.

Memory hook: Resources · execution · workflow · translation. It never rethrows SQLException as-is — that's the whole reason it exists.

⑥ JdbcTemplate callbacks 3 misses

Three questions about the same three interfaces. The distinction is scope (one row vs the whole result set) and whether you return anything.

Q14 & Q53 · The three row callbacks two questions, one table

RowMapper returns · RowCallbackHandler doesn't · ResultSetExtractor drives the cursor

Q14: you ticked "maps one row into the object returned by a callback" — that's RowMapper. Q53: you ticked RowCallbackHandler for "must call next()". Both times you swapped two members of the same family.

One table settles all three questions:

InterfaceCalledReturnsCalls next()?Use for
RowMapper<T>once per rowT — collected into a List<T>no — cursor already positionedordinary object mapping
RowCallbackHandleronce per rowvoid — publishes by side effectnostreaming to a file, running totals
ResultSetExtractor<T>once for the whole ResultSeta single Tyes — you drive itone-to-many joins, object graphs

The logic is consistent once you see it: the two per-row callbacks are handed an already-positioned row, so calling next() would skip records. ResultSetExtractor is handed the raw ResultSet and is responsible for the whole traversal — which is exactly what lets it assemble one object from many rows.

// RowCallbackHandler — no return value; write straight out
jdbc.query("select id, name from employee", (ResultSet rs) ->
        writer.println(rs.getLong("id") + "," + rs.getString("name")));

// ResultSetExtractor — you iterate
jdbc.query("select o.id, i.sku from orders o join items i on …",
        (ResultSet rs) -> {
            Map<Long, Order> orders = new LinkedHashMap<>();
            while (rs.next()) { /* build the graph */ }
            return new ArrayList<>(orders.values());
        });

Two rules for all three: never close the ResultSet, statement or connection — JdbcTemplate owns them; and a callback holding mutable state is not thread-safe, so create a fresh instance per query.

Memory hook: Per row: RowMapper returns, RowCallbackHandler doesn't. Whole set: ResultSetExtractor — and it's the only one that calls next().
Q90 · The drawback of the Template Method pattern

It relies on inheritance, which couples subclass to superclass

Your answer: "uses composition, which limits static analysis"backwards. Template Method is the inheritance-based pattern; composition is what the alternatives use.

The classic GoF pattern fixes an algorithm's skeleton in a base class and delegates the variable steps to abstract methods a subclass overrides. The cost is a compile-time relationship you can't change: Java's single inheritance means the subclass can't also extend anything else, and swapping the skeleton means a new hierarchy.

The reason this is a Spring question at all: JdbcTemplate deliberately sidesteps that cost. Instead of asking you to subclass it, it takes callback objectsRowMapper, ResultSetExtractor, PreparedStatementCreator — so the variable part arrives by composition:

AspectGoF Template MethodSpring's callback approach
Extensionsubclass overrides abstract methodspass a callback object or lambda
Couplingtight — subclass bound to the superclass contractloose — depends only on a small interface
Reuselimited by single inheritancecompose callbacks freely
Testinginstantiate a concrete subclassstub the interface directly
Java 8+verbose anonymous subclasssingle-method interface → lambda

Internally JdbcTemplate does use the pattern — execute(StatementCallback<T>) holds the fixed skeleton (acquire connection → create statement → execute → translate exceptions → release) — but it exposes only the callback surface. That's the design insight the question is fishing for.

Memory hook: Template Method = inheritance = tight coupling. Spring keeps the fixed skeleton but takes callbacks instead of subclasses.

⑦ The exception hierarchy 1 miss

Q51 · DuplicateKeyException

A primary-key or unique-constraint violation on INSERT or UPDATE

Your answer: you also ticked "it is thrown when the database connection is lost"that's DataAccessResourceFailureException. Each subclass names one cause; that's the point of the hierarchy.

The subclasses that actually appear in questions, each mapped to its cause:

ExceptionThrown when
DuplicateKeyExceptiona primary key or unique constraint is violated
DataIntegrityViolationExceptionany constraint violation — FK, NOT NULL (the parent of the above)
EmptyResultDataAccessExceptionqueryForObject() found 0 rows
IncorrectResultSizeDataAccessExceptionthe wrong number of rows came back
DataAccessResourceFailureExceptionconnection failure or timeout
DeadlockLoserDataAccessExceptionthis transaction was chosen as the deadlock victim
CannotAcquireLockExceptiona lock could not be obtained in time

The chain for this one: DuplicateKeyExceptionDataIntegrityViolationExceptionNonTransientDataAccessExceptionDataAccessExceptionRuntimeException. That transient / non-transient split is itself a useful fact: a transient failure (deadlock, lock timeout, connection blip) may succeed if you retry; a non-transient one (duplicate key, bad SQL) never will.

And the ordering rule the code in that question demonstrated — catch specific before general. Since every one of these is unchecked, nothing forces you to catch anything, which is deliberate: most data-access failures aren't recoverable at the call site.

Memory hook: All unchecked, all under DataAccessException. Duplicate key ⇒ DuplicateKeyException · 0 rows ⇒ EmptyResult… · connection gone ⇒ ResourceFailure…

⑧ Spring Data repositories 5 misses

Q2 · Does a JpaRepository interface need @Repository?

No — extending the interface is what makes it discoverable

Your answer: TrueSpring Data finds repositories by the interface they extend, not by an annotation.

@EnableJpaRepositories (or Boot's auto-configuration) scans for interfaces extending Repository — including CrudRepository, PagingAndSortingRepository and JpaRepository — builds a proxy implementation for each at runtime, and registers it as a bean. @Repository adds nothing.

WhatNeeds @Repository?
An interface extending JpaRepository/CrudRepositoryno
An interface annotated @RepositoryDefinitionno
A hand-written DAO class you wrote yourselfyes — it makes it a bean and enables exception translation

Worth separating the annotation's two jobs, because that's what makes the question confusing: @Repository is a @Component stereotype (registers the bean), and it triggers PersistenceExceptionTranslationPostProcessor to convert provider-specific exceptions into DataAccessException. Spring Data proxies already do both, which is why the annotation is redundant there.

Memory hook: Extend the interface ⇒ discovered automatically. @Repository is for DAOs you write by hand.
Q13 · Which finder method name is wrong

findByAgeIsDifferentNull — there's no such keyword

Your answer: findTopByOrderByAgeDescthat one is valid: findTopBy with no filtering property just means "the first result after ordering".

The keyword vocabulary is finite, and everything outside it is invented:

KeywordExample
And / OrfindByFirstnameAndLastname
Is / EqualsfindByNameIs
BetweenfindByStartDateBetween
LessThan / GreaterThanfindByAgeLessThan
IsNull / IsNotNullfindByAgeIsNullnot IsDifferentNull
Like / NotLike / Containing / StartingWithfindByFirstnameLike
True / FalsefindByActiveTrue
In / NotInfindByAgeIn(Collection)
IgnoreCasefindByNameIgnoreCase
OrderBy…Asc/DescfindByLastnameOrderByFirstnameDesc
Top / FirstfindTop10ByOrderByAgeDesc

Structure to check against: subject (find, read, get, query, count, exists, delete, remove) + By + predicate. By is mandatory — deleteAgeLessThan is invalid, deleteByAgeLessThan is fine. And property names must match entity fields, so a bad name fails at application startup, not at call time.

Memory hook: subject + By + predicate. Null checks are IsNull / IsNotNull. findTopBy with no property is legal.
Q26 & Q79 · Adding custom behaviour to a repository two questions, two mechanisms

Per repository: a fragment interface + Impl class. Globally: repositoryBaseClass

Q26: you ticked "register the Impl as a bean", "use @Query" and "extend JpaRepository and declare the methods". Q79: you under-selected, taking one of the two valid mechanisms. Both questions are the same topic from opposite ends.

There are exactly two customisation levels, and the exam tests that you know both:

LevelMechanismScopeUse for
One repositoryfragment interface + <Name>Impl classthat repositoryCriteria API, native SQL, anything needing the EntityManager
All repositories@EnableJpaRepositories(repositoryBaseClass = …)everything in scan scopesoft delete, tenant filters, a common save override
// Fragment approach — three steps
interface CustomUserRepository {                       // 1. the fragment interface
    List<User> findUsersWithCustomQuery();
}

class CustomUserRepositoryImpl implements CustomUserRepository {   // 2. Impl suffix
    @PersistenceContext EntityManager em;             //    no @Repository needed
    public List<User> findUsersWithCustomQuery() { … }
}

interface UserRepository extends JpaRepository<User, Long>, CustomUserRepository { }  // 3. extend both

The convention is the whole mechanism. Spring Data finds the implementation by name — interface name + Impl — and it must live in a scanned package. No annotation, no bean registration. The suffix is configurable via @EnableJpaRepositories(repositoryImplementationPostfix = "…"). A custom base class must declare a (JpaEntityInformation, EntityManager) constructor or you get a BeanInstantiationException at startup.

And the resolution order inside the proxy: fragments win over derived queries — if a fragment method has the same signature as one Spring Data could derive, the fragment is called.

Memory hook: One repo ⇒ fragment + Impl (by naming convention). All repos ⇒ repositoryBaseClass. There is no @CustomRepository and no property for it.
Q27 · Statements about Spring Data repositories

@EnableJpaRepositories scans sub-packages too — and CRUD methods inherit SimpleJpaRepository's transactions

Your answer: you ticked "@EnableJpaRepositories scans the package but NOT its sub-packages". It scans both — the same convention as @ComponentScan.

Four facts, three of which were the correct options:

BehaviourDetail
Scanningthe annotated class's package and all sub-packages; override with basePackages
Enablingthe annotation activates repository infrastructure (Boot adds it for you)
Transactionsinherited from SimpleJpaRepository
Base classswap it with repositoryBaseClass

The transactional detail is worth knowing properly, because it explains real behaviour: SimpleJpaRepository is annotated @Transactional(readOnly = true) at class level, and its write methods (save, delete, deleteAll) override that with a plain @Transactional. So every repository call is transactional even if you never annotate anything — and a save() called with no surrounding transaction commits on its own. Wrap several calls in your own @Transactional service method to make them atomic.

Memory hook: Scans sub-packages. SimpleJpaRepository is the base: readOnly=true by default, writes override. Every repository call is already transactional.

@Query 2 misses

Q33 · Statements about @Query

JPQL by default · method-level only · supports SpEL

Your answer: you ticked "by default it will parse native SQL"only with nativeQuery = true. The default is JPQL, which operates on entities, not tables.

Everything the annotation does:

PropertyDetail
Default languageJPQLSELECT u FROM User u … (entity names, not table names)
Native SQLopt in with nativeQuery = true
Placementmethod level only — never on a class or field
SpELsupported, e.g. :#{#user.id} and #{#entityName}
Modifyingadd @Modifying for UPDATE/DELETE, inside a transaction
CountingcountQuery for paginated native queries
@Query("select u from User u where u.email = ?1")                       // JPQL
User findByEmail(String email);

@Query(value = "select * from users where email = ?1", nativeQuery = true)  // SQL
User findByEmailNative(String email);

@Modifying
@Query("update User u set u.active = false where u.lastLogin < ?1")
int deactivateStale(LocalDate cutoff);

The reason JPQL is the default: it's portable across JPA providers and databases, since it is written against your entity model. Native SQL buys you database-specific features at the cost of that portability, which is why it has to be requested explicitly.

Memory hook: JPQL by default, native only on request. Method level only. @Modifying for writes.
Q99 · What's invalid in this @Query method?

Nothing@Param isn't needed with indexed parameters

Your answer: "the method should be called findPersonByEmailAddress"with @Query present, the method name is irrelevant; the query is stated explicitly.

Two independent facts made "none of the above" correct:

ConcernRule
Method namingwith @Query, any name works — naming conventions apply only to derived queries
Indexed parameters ?1, ?2matched by positionno @Param
Named parameters :emailmatched by name — @Param required
// indexed — no @Param
@Query("select p from Person p where p.emailAddress = ?1")
Person findByEmailAddress(String emailAddress);

// named — @Param required
@Query("select p from Person p where p.emailAddress = :email")
Person findByEmailAddress(@Param("email") String emailAddress);

Prefer named parameters in anything non-trivial — they survive reordering and read far better — and never mix the two styles in one query. (Since Java 8, if you compile with -parameters, @Param can sometimes be inferred; don't rely on it in an exam answer.)

The wider lesson: when "None of the above" is an option, the exam is usually testing whether you'll invent a rule that doesn't exist. Check each proposed fault against a real rule before accepting it.

Memory hook: ?1 ⇒ positional, no @Param. :name ⇒ named, @Param required. With @Query the method name doesn't matter.

⑩ JPA & Boot configuration 4 misses

Q75 · What you must configure to use JPA with Spring

DataSource · EntityManagerFactory · JpaTransactionManager · entities · the JARs

Your answer: under-selectedfive of the six options were correct. The only invented one was a spring.jpa.support.enabled property.

Manual JPA configuration is four beans plus a classpath — and knowing the shape explains what Boot is doing for you:

PieceWhy
DataSourceJPA still issues JDBC underneath
LocalContainerEntityManagerFactoryBeanproduces the EntityManagerFactory
JpaTransactionManagerlets @Transactional drive the JPA transaction
@Entity classes with an @Idnothing to map otherwise
JARs: JPA API, spring-orm, a provider, a JDBC driverthe provider (Hibernate) does the work

There is no "enable JPA" flag. Spring wires a JPA provider into the container by declaring beans; Boot's HibernateJpaAutoConfiguration then creates all four automatically when spring-boot-starter-data-jpa is on the classpath. That's why the property option was invented — Boot triggers on classpath presence, not on a switch.

One naming trap: LocalEntityManagerFactoryBean (without Container) skips Spring's packages-to-scan support and requires a persistence.xml — almost never what you want.

Memory hook: DataSource → LocalContainerEMFBean → JpaTransactionManager → entities. No enabling property exists.
Q30 · Which datasource property can Boot infer?

spring.datasource.driver-class-name — deduced from the URL

Your answer: you also ticked username and passwordcredentials can't be inferred from anything. Only the driver is derivable, because the URL already names the vendor.

DataSourceBuilder reads the JDBC URL prefix and maps it to a driver class:

URL prefixInferred driver
jdbc:postgresql:…org.postgresql.Driver
jdbc:mysql:…com.mysql.cj.jdbc.Driver
jdbc:h2:mem:…org.h2.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=app
spring.datasource.password=secret
# driver-class-name: not needed

Set it explicitly only when auto-detection fails or you're deliberately overriding. And remember the neighbouring fact: with no spring.datasource.* at all and H2/HSQLDB/Derby on the classpath, Boot stands up an embedded database automatically — which is what @DataJpaTest exploits.

Memory hook: The URL names the vendor, so the driver is inferable. Credentials never are.
Q65 · spring.jpa.hibernate.ddl-auto=update

Alters the schema to match your entities at startup — additively, never dropping

Your answer: you also ticked "it drops the schema at startup and recreates it"that's create-drop. The word update is the clue: it changes what's there rather than replacing it.

All five values, and where each belongs:

ValueBehaviourUse in
nonedoes nothingproduction (with Flyway/Liquibase)
validatechecks the schema matches the entities; fails on mismatchproduction / staging
updatealters the schema to match — adds, never dropsdevelopment
createdrops and recreates at startupdevelopment / testing
create-dropcreates at startup, drops at shutdowntesting

Why update is unsafe in production — a favourite follow-up: it adds columns and tables but never removes or narrows anything, so schema drift accumulates silently and a renamed field leaves the old column behind, still NOT NULL. Real schema management is Flyway or Liquibase, with ddl-auto=validate as a safety net.

Boot's own defaults: create-drop for an embedded database and none otherwise. And note the neighbouring property from the same config block — spring.jpa.show-sql=true logs statements; it has nothing to do with DDL.

Memory hook: none · validate · update (additive) · create (at startup) · create-drop (and at shutdown). Boot: create-drop embedded, none otherwise.
@GeneratedValue with no strategy

GenerationType.AUTO — the provider then chooses

Your answer: you also ticked IDENTITYthat's often what AUTO resolves to, but the declared strategy is AUTO. The question asks what's declared.

Bare @GeneratedValue means @GeneratedValue(strategy = GenerationType.AUTO). In Hibernate 5, AUTO maps to SequenceStyleGenerator, which then picks per dialect:

StrategyMechanismNote
AUTOprovider choosesthe default; portable
IDENTITYauto-increment columnMySQL; defeats JDBC batching on insert
SEQUENCEa database sequencePostgres/Oracle; the efficient choice
TABLEa table simulating a sequenceportable but slow — rarely used
UUIDgenerated UUIDJPA 3.1+ / Hibernate-specific before that

The practical follow-up: IDENTITY forces Hibernate to execute the INSERT immediately on persist() to learn the generated key, which disables insert batching. SEQUENCE lets it pre-fetch ids and batch — which is why explicit SEQUENCE is recommended where the database supports it.

Memory hook: No strategy = AUTO. AUTO delegates to the dialect. IDENTITY kills batching; SEQUENCE is the efficient one.

The whole topic on one page

34 facts, one line each — this is your pre-exam pass
How to use this page (1) The two master tables first. Propagation and isolation are 8 of these 34 and about ten minutes of memorising. Cover the right-hand columns and reconstruct them from the level names alone — if you can do that, every propagation and isolation question on the exam is a lookup. (2) Then the drills, not the prose. There are 40 questions on this page. Reading an explanation creates recognition; answering a question creates recall, and the exam only tests recall. (3) Watch the answer count. On these 34 you ticked several options on a one-answer question repeatedly — four ticks on Q12 and Q37, three on Q1. Decide how many the stem is asking for before you read the options. (4) Come back on a three-day cycle. Data Management has swung 91 → 36 → 75 → 57 across four papers, and that is a schedule problem rather than a knowledge one. This page is the whole topic; re-covering it takes about twenty minutes.
I'm your teacher — ask me anything. Say "drill propagation" for a rapid-fire round on the seven levels, "drill isolation" for the anomaly grid, or "quiz me on data management" for all 34 interleaved in exam wording. If you want the same treatment for another topic bank, just point me at the file.
← Dashboard Lesson 8 · JDBC Lesson 9 · Transactions Lesson 10 · Spring Data JPA