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.
The headline: five separate questions asked you to name a propagation
level, and you missed all five — NOT_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.
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".
| Propagation | Outer transaction exists | No outer transaction | Remember it as |
|---|---|---|---|
REQUIRED (default) | join it | start a new one | "always transactional" — 99% of methods |
REQUIRES_NEW | suspend it, start a new one | start a new one | "always my own transaction" |
NESTED | savepoint inside it | start a new one | "partial rollback" — JDBC savepoints only |
SUPPORTS | join it | run non-transactionally | "I don't mind either way" |
NOT_SUPPORTED | suspend it, run non-transactionally | run non-transactionally | "never transactional, but polite about it" |
MANDATORY | join it | throw IllegalTransactionStateException | "you must give me one" |
NEVER | throw IllegalTransactionStateException | run 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.
Three of your 34 were this table asked from three different angles. It is four rows.
| Isolation level | Dirty read | Non-repeatable read | Phantom read |
|---|---|---|---|
READ_UNCOMMITTED | possible | possible | possible |
READ_COMMITTED | prevented | possible | possible |
REPEATABLE_READ | prevented | prevented | possible |
SERIALIZABLE | prevented | prevented | prevented |
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.
| Anomaly | One sentence |
|---|---|
| Dirty read | you read a change another transaction hasn't committed yet, and it might be rolled back |
| Non-repeatable read | you read the same row twice and it changed — someone updated it and committed in between |
| Phantom read | you 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.
All five are answered by master table 1. Read them as five angles on one table rather than five separate facts.
NOT_SUPPORTEDTake the stem apart clause by clause — the exam builds these definitions from exactly two facts:
| Clause | Rules out | Leaves |
|---|---|---|
| "executes non-transactionally" | REQUIRED, REQUIRES_NEW, NESTED, MANDATORY | SUPPORTS, 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.
NEVERMANDATORY 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 exists | No outer transaction | |
|---|---|---|
MANDATORY | join it | throws |
NEVER | throws | runs 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.
MANDATORY — and calling it with no transaction throws IllegalTransactionStateExceptionREQUIRED.The distinction Q95 was really testing is MANDATORY vs REQUIRED,
and it is a single word:
| No outer transaction | |
|---|---|
REQUIRED | creates one |
MANDATORY | throws — 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.
REQUIREDREQUIRED 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.
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.
@Transactional(noRollbackFor = CustomUncheckedException.class)noRollbackFor exists precisely to override.The default rule, and the four attributes that bend it:
| Exception type | Default | Override with |
|---|---|---|
RuntimeException / Error (unchecked) | rolls back | noRollbackFor, noRollbackForClassName |
| Checked exception | commits | rollbackFor, 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.
@Transactional attributesisolation and propagation — writeOnly, readWrite and nestedTransaction don't existwriteOnly — an 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:
| Attribute | Type | Default |
|---|---|---|
propagation | Propagation enum | REQUIRED |
isolation | Isolation enum | DEFAULT (the datastore's) |
timeout | int seconds | -1 (none) |
readOnly | boolean | false |
rollbackFor / rollbackForClassName | classes / strings | — |
noRollbackFor / noRollbackForClassName | classes / strings | — |
transactionManager (alias value) | bean name | the 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.
Two kinds of transaction, and the manager each one needs:
| Type | Scope | Manager |
|---|---|---|
| Local | one resource — a single database | DataSourceTransactionManager, JpaTransactionManager |
| Global | several resources — databases and message queues | JtaTransactionManager |
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).
Three questions, one hierarchy. Learn the four class names and the interface above them and all three are free.
PlatformTransactionManager — getTransaction(), commit(), rollback()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:
| Type | What it is |
|---|---|
TransactionManager | an empty marker interface (Spring 5.2+) — the common parent, no methods |
PlatformTransactionManager | the central SPI for imperative transactions: three methods |
ReactiveTransactionManager | the reactive sibling |
TransactionTemplate | a helper that delegates to a PlatformTransactionManager |
TransactionInterceptor | the 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.
DataSourceDataSourceTransactionManagerJDBCPlatformTransactionManager — an 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:
| Implementation | Manages | Auto-configured when |
|---|---|---|
DataSourceTransactionManager | a JDBC Connection | only JDBC on the classpath |
JpaTransactionManager | a JPA EntityManager (and its JDBC connection) | JPA on the classpath |
HibernateTransactionManager | a Hibernate Session | plain Hibernate, no JPA |
JtaTransactionManager | distributed / XA resources | a JTA starter |
R2dbcTransactionManager | reactive connections | R2DBC — 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").
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".
READ_UNCOMMITTED and READ_COMMITTEDSERIALIZABLE — the 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":
| Level | Non-repeatable read |
|---|---|
READ_UNCOMMITTED | can occur |
READ_COMMITTED | can occur |
REPEATABLE_READ | prevented — it's in the name |
SERIALIZABLE | prevented |
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.
REPEATABLE_READ and SERIALIZABLETreat "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 form | Answer |
|---|---|
| "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.
READ_COMMITTEDREPEATABLE_READ and
SERIALIZABLE — both prevent more than the question described. The stem
specifies exactly one row of the table.Three constraints, and each one eliminates:
| Clause | Eliminates |
|---|---|
| "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.
update() — and it returns the number of rows affectedqueryForList, 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:
| Method | SQL | Returns |
|---|---|---|
update() | DML — INSERT, UPDATE, DELETE | int — rows affected |
batchUpdate() | the same, many times | int[] |
query() | SELECT, many rows | List<T> via a RowMapper |
queryForObject() | SELECT, exactly one row | T |
queryForList() | SELECT, many rows | List<Map<String,Object>> |
execute() | DDL — CREATE, ALTER, DROP | void |
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))");
queryForObject(sql, RowMapper<T>, args…)queryForMap — close, 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:
| Method | Returns |
|---|---|
queryForObject(sql, RowMapper, args) | your mapped domain object |
queryForObject(sql, Class<T>, args) | a scalar — Integer, 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.
JdbcTemplate does for youThe four responsibilities, which is what "what does JdbcTemplate do" questions enumerate:
| Responsibility | Detail |
|---|---|
| Resource management | acquires and releases connections, statements, result sets — even on exception |
| SQL execution | queries, updates, batch updates, stored procedure calls |
| Core JDBC workflow | statement creation, parameter binding, execution |
| Exception translation | checked SQLException → unchecked 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.
Three questions about the same three interfaces. The distinction is scope (one row vs the whole result set) and whether you return anything.
RowMapper returns · RowCallbackHandler doesn't · ResultSetExtractor drives the cursorRowMapper. Q53: you ticked RowCallbackHandler for "must call
next()". Both times you swapped two members of the same family.One table settles all three questions:
| Interface | Called | Returns | Calls next()? | Use for |
|---|---|---|---|---|
RowMapper<T> | once per row | T — collected into a List<T> | no — cursor already positioned | ordinary object mapping |
RowCallbackHandler | once per row | void — publishes by side effect | no | streaming to a file, running totals |
ResultSetExtractor<T> | once for the whole ResultSet | a single T | yes — you drive it | one-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.
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 objects —
RowMapper, ResultSetExtractor, PreparedStatementCreator — so the variable
part arrives by composition:
| Aspect | GoF Template Method | Spring's callback approach |
|---|---|---|
| Extension | subclass overrides abstract methods | pass a callback object or lambda |
| Coupling | tight — subclass bound to the superclass contract | loose — depends only on a small interface |
| Reuse | limited by single inheritance | compose callbacks freely |
| Testing | instantiate a concrete subclass | stub the interface directly |
| Java 8+ | verbose anonymous subclass | single-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.
DuplicateKeyExceptionDataAccessResourceFailureException. Each subclass names one cause; that's the
point of the hierarchy.The subclasses that actually appear in questions, each mapped to its cause:
| Exception | Thrown when |
|---|---|
DuplicateKeyException | a primary key or unique constraint is violated |
DataIntegrityViolationException | any constraint violation — FK, NOT NULL (the parent of the above) |
EmptyResultDataAccessException | queryForObject() found 0 rows |
IncorrectResultSizeDataAccessException | the wrong number of rows came back |
DataAccessResourceFailureException | connection failure or timeout |
DeadlockLoserDataAccessException | this transaction was chosen as the deadlock victim |
CannotAcquireLockException | a lock could not be obtained in time |
The chain for this one: DuplicateKeyException →
DataIntegrityViolationException → NonTransientDataAccessException →
DataAccessException → RuntimeException. 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.
JpaRepository interface need @Repository?@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.
| What | Needs @Repository? |
|---|---|
An interface extending JpaRepository/CrudRepository | no |
An interface annotated @RepositoryDefinition | no |
| A hand-written DAO class you wrote yourself | yes — 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.
findByAgeIsDifferentNull — there's no such keywordfindTopByOrderByAgeDesc — that 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:
| Keyword | Example |
|---|---|
And / Or | findByFirstnameAndLastname |
Is / Equals | findByNameIs |
Between | findByStartDateBetween |
LessThan / GreaterThan | findByAgeLessThan |
IsNull / IsNotNull | findByAgeIsNull — not IsDifferentNull |
Like / NotLike / Containing / StartingWith | findByFirstnameLike |
True / False | findByActiveTrue |
In / NotIn | findByAgeIn(Collection) |
IgnoreCase | findByNameIgnoreCase |
OrderBy…Asc/Desc | findByLastnameOrderByFirstnameDesc |
Top / First | findTop10ByOrderByAgeDesc |
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.
Impl class. Globally: repositoryBaseClassThere are exactly two customisation levels, and the exam tests that you know both:
| Level | Mechanism | Scope | Use for |
|---|---|---|---|
| One repository | fragment interface + <Name>Impl class | that repository | Criteria API, native SQL, anything needing the EntityManager |
| All repositories | @EnableJpaRepositories(repositoryBaseClass = …) | everything in scan scope | soft 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.
@EnableJpaRepositories scans sub-packages too — and CRUD methods inherit SimpleJpaRepository's transactions@ComponentScan.Four facts, three of which were the correct options:
| Behaviour | Detail |
|---|---|
| Scanning | the annotated class's package and all sub-packages; override with basePackages |
| Enabling | the annotation activates repository infrastructure (Boot adds it for you) |
| Transactions | inherited from SimpleJpaRepository |
| Base class | swap 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.
@Query 2 misses@QuerynativeQuery = true. The default is JPQL, which operates on
entities, not tables.Everything the annotation does:
| Property | Detail |
|---|---|
| Default language | JPQL — SELECT u FROM User u … (entity names, not table names) |
| Native SQL | opt in with nativeQuery = true |
| Placement | method level only — never on a class or field |
| SpEL | supported, e.g. :#{#user.id} and #{#entityName} |
| Modifying | add @Modifying for UPDATE/DELETE, inside a transaction |
| Counting | countQuery 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.
@Query method?@Param isn't needed with indexed parameters@Query present, the method name is irrelevant; the query is stated
explicitly.Two independent facts made "none of the above" correct:
| Concern | Rule |
|---|---|
| Method naming | with @Query, any name works — naming conventions apply only to derived queries |
Indexed parameters ?1, ?2 | matched by position — no @Param |
Named parameters :email | matched 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.
?1 ⇒ positional, no @Param. :name ⇒ named, @Param required. With @Query the method name doesn't matter.EntityManagerFactory · JpaTransactionManager · entities · the JARsspring.jpa.support.enabled property.Manual JPA configuration is four beans plus a classpath — and knowing the shape explains what Boot is doing for you:
| Piece | Why |
|---|---|
DataSource | JPA still issues JDBC underneath |
LocalContainerEntityManagerFactoryBean | produces the EntityManagerFactory |
JpaTransactionManager | lets @Transactional drive the JPA transaction |
@Entity classes with an @Id | nothing to map otherwise |
JARs: JPA API, spring-orm, a provider, a JDBC driver | the 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.
spring.datasource.driver-class-name — deduced from the URLusername and password —
credentials 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 prefix | Inferred 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.
spring.jpa.hibernate.ddl-auto=updatecreate-drop. The word update is the clue: it changes what's there
rather than replacing it.All five values, and where each belongs:
| Value | Behaviour | Use in |
|---|---|---|
none | does nothing | production (with Flyway/Liquibase) |
validate | checks the schema matches the entities; fails on mismatch | production / staging |
update | alters the schema to match — adds, never drops | development |
create | drops and recreates at startup | development / testing |
create-drop | creates at startup, drops at shutdown | testing |
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.
GenerationType.AUTO — the provider then choosesIDENTITY — that'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:
| Strategy | Mechanism | Note |
|---|---|---|
AUTO | provider chooses | the default; portable |
IDENTITY | auto-increment column | MySQL; defeats JDBC batching on insert |
SEQUENCE | a database sequence | Postgres/Oracle; the efficient choice |
TABLE | a table simulating a sequence | portable but slow — rarely used |
UUID | generated UUID | JPA 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.
IllegalTransactionStateExceptionDataSourceTransactionManager, not JTAwriteOnlyTransactionManager is an empty markerupdate = DML (row count) · query* = SELECT · execute = DDLSQLException → unchecked DataAccessException; it never rethrows as-isnext()JdbcTemplate uses callbacks insteadDataIntegrityViolationException; transient vs non-transient is the retry hintJpaRepository is enoughBy + predicate; nulls are IsNull/IsNotNull; bad names fail at startupImpl for one repo · repositoryBaseClass for allSimpleJpaRepository (readOnly=true, writes override)nativeQuery=true for SQL, method level only, supports SpEL, @Modifying for writes?1 needs no @Param; :name does — and with @Query the method name is freeLocalContainerEntityManagerFactoryBean + JpaTransactionManager + entities; no enabling property