English + German
English stays on the page. Click the button to show the German text under each question.
Data, JPA, and Transactions
This is the round that fails mid-level candidates. Interviewers want persistence context, proxy transactions, and N+1 — not “I use @Transactional everywhere”.
Deep chapters: Week 5.
Deutsch
Data, JPA und Transaktionen
Das ist die Runde, an der Mid-Level-Kandidaten scheitern. Interviewer wollen persistence context, proxy transactions und N+1 — nicht „ich setze überall @Transactional“.
Die Kapitel dazu: Woche 5.
1. What does Spring Data JPA actually do?
Deutsch
Was macht Spring Data JPA eigentlich?
Level: Mid · Listen for: repository proxy, EntityManager, not a new ORM
Niveau: Mid · Darauf hören sie: repository proxy, EntityManager, kein neues ORM
Model answer
JPA (Hibernate) is the ORM: entities, persistence context, SQL. Spring Data JPA is a layer on top: you declare an interface (JpaRepository<Order, Long>), Spring creates a proxy that implements query methods, wraps EntityManager, and participates in Spring transactions.
save / findById are inherited. findByEmail is parsed from the method name or backed by @Query. You still have to think about SQL, indexes, and fetch plans.
Follow-ups
JpaRepositoryvsCrudRepositoryvsListCrudRepository?- When do you drop to
EntityManager?
Trap: “Spring Data replaces Hibernate.”
Memory sentence: Hibernate persists; Spring Data is a repository proxy over it.
Study: JPA mental model
Musterantwort
JPA (Hibernate) ist das ORM: Entities, persistence context, SQL. Spring Data JPA ist eine Schicht darüber: du deklarierst ein Interface (JpaRepository<Order, Long>), Spring erzeugt einen proxy, der Query-Methoden implementiert, den EntityManager kapselt und an Spring-Transaktionen teilnimmt.
save / findById kommen per Vererbung. findByEmail wird aus dem Methodennamen geparst oder von @Query getragen. Über SQL, Indexes und Fetch Plans musst du trotzdem nachdenken.
Nachfragen
JpaRepositoryvsCrudRepositoryvsListCrudRepository?- Wann steigst du auf
EntityManagerab?
Falle: „Spring Data ersetzt Hibernate.“
Merksatz: Hibernate persistiert; Spring Data ist ein repository proxy darüber.
Lesen: JPA Mental Model
2. What is the persistence context? First-level cache?
Deutsch
Was ist der persistence context? First-level cache?
Level: Mid · Listen for: identity guarantee; dirty checking; session scoped to the transaction
Niveau: Mid · Darauf hören sie: Identity-Garantie; dirty checking; Session an die Transaktion gebunden
Model answer
The persistence context (Hibernate Session) is the set of managed entities for a unit of work, usually one transaction.
- Identity: the same row is the same Java object inside that context
- First-level cache:
findby id does not hit the DB twice - Dirty checking: at flush, Hibernate compares snapshots and issues
UPDATEs — you do not always callsavefor already managed entities - Flush: before query, at commit, or
flush()
Once the context closes, entities become detached. Touching a lazy association then throws LazyInitializationException.
Follow-ups
persistvsmergevs Spring Datasave?- What does
clear()do?
Trap: calling save on every mutation as if the context did not exist.
Memory sentence: The persistence context is the transactional first-level cache and the dirty checker.
Study: JPA mental model
Musterantwort
Der persistence context (Hibernate Session) ist die Menge der managed Entities für eine Unit of Work, meist eine Transaktion.
- Identity: dieselbe Zeile ist dasselbe Java-Objekt in diesem persistence context
- First-level cache:
findper id trifft die DB nicht zweimal - Dirty checking: beim flush vergleicht Hibernate Snapshots und schickt
UPDATEs — bei schon managed Entities rufst du nicht immersaveauf - Flush: vor einer Query, beim commit, oder
flush()
Sobald der persistence context schließt, werden Entities detached. Fasst du dann eine lazy Association an, fliegt LazyInitializationException.
Nachfragen
persistvsmergevs Spring Datasave?- Was macht
clear()?
Falle: bei jeder Mutation save aufrufen, als gäbe es den persistence context nicht.
Merksatz: Der persistence context ist der transaktionale first-level cache und der dirty checker.
Lesen: JPA Mental Model
3. Lazy vs eager? Why is N+1 a default accident?
Deutsch
Lazy vs eager? Warum ist N+1 ein Default-Unfall?
Level: Mid · Listen for: default LAZY on collections; one query plus N; fix with fetch join / entity graph
Niveau: Mid · Darauf hören sie: Default LAZY auf Collections; eine Query plus N; beheben mit fetch join / entity graph
Model answer
@ManyToOne was historically EAGER; collections are LAZY. Lazy means a proxy; the SQL runs when you touch the association.
N+1: 1 query for a list of orders, then 1 query per order for customer or lines when you serialize or map them. It often appears only when you add a field to a DTO.
Fixes:
join fetchin JPQL /@EntityGraphfor that use case- DTO projection /
select newso you do not load graphs - batch size (
@BatchSize) as a band-aid
Do not make everything EAGER. That just always loads too much.
Follow-ups
FetchType.LAZYon@ManyToOnein recent mappings?- Why can two fetch joins of collections cartesian-explode?
Trap: open-in-view=true so lazy works in the view layer.
Memory sentence: Lazy defers SQL; looping associations is N+1 unless you fetch for that use case.
Study: JPA performance
Musterantwort
@ManyToOne war historisch EAGER; Collections sind LAZY. Lazy heißt proxy; das SQL läuft, wenn du die Association anfasst.
N+1: 1 Query für eine Liste von Orders, dann 1 Query pro Order für customer oder lines, wenn du serialisierst oder mapst. Es taucht oft erst auf, wenn du ein Feld ins DTO nimmst.
Dagegen:
join fetchin JPQL /@EntityGraphfür diesen Use Case- DTO projection /
select new, damit du keine Graphs lädst - batch size (
@BatchSize) als Pflaster
Mach nicht alles EAGER. Dann lädst du nur immer zu viel.
Nachfragen
FetchType.LAZYauf@ManyToOnein aktuellen Mappings?- Warum können zwei fetch joins von Collections zum kartesischen Produkt explodieren?
Falle: open-in-view=true, damit lazy in der View-Schicht funktioniert.
Merksatz: Lazy schiebt SQL auf; Schleifen über Associations sind N+1, außer du lädst per fetch für den Use Case.
Lesen: JPA Performance
4. How does @Transactional work? Why self-invocation fails?
Deutsch
Wie funktioniert @Transactional? Warum scheitert Self-Invocation?
Level: Mid · Listen for: proxy, rollback on runtime exceptions, self-invocation
Niveau: Mid · Darauf hören sie: proxy, Rollback bei runtime exceptions, Self-Invocation
Model answer
@Transactional is AOP. A proxy (or AspectJ, rarely) starts a transaction, binds a persistence context, then commits or rolls back.
Rules that matter:
- public methods on a Spring bean, called through the proxy
this.otherMethod()is self-invocation → no new intercept → no transaction (or the inner one does not apply)- default rollback: unchecked exceptions; checked exceptions do not roll back unless
rollbackFor - default propagation:
REQUIRED(join or create)
I put it on service use-case methods, not on controllers and not on every repository method (Spring Data already is transactional per call).
Follow-ups
readOnly = true— what does Hibernate actually skip?private/finalmethods?
Trap: “the annotation on the class covers internal calls.”
Memory sentence: Transactions start at the proxy; this is not the proxy.
Study: Transactions
Musterantwort
@Transactional ist AOP. Ein proxy (oder AspectJ, selten) startet eine Transaktion, bindet einen persistence context und macht danach commit oder rollback.
Regeln, die zählen:
- public Methoden auf einem Spring bean, aufgerufen über den proxy
this.otherMethod()ist Self-Invocation → kein neuer intercept → keine Transaktion (oder die innere greift nicht)- Default-Rollback: unchecked Exceptions; checked Exceptions rollen nicht zurück, außer
rollbackFor - Default-Propagation:
REQUIRED(beitreten oder erzeugen)
Ich setze es auf Service-Use-Case-Methoden, nicht auf Controller und nicht auf jede Repository-Methode (Spring Data ist schon transaktional pro Aufruf).
Nachfragen
readOnly = true— was überspringt Hibernate wirklich?private/finalMethoden?
Falle: „die Annotation auf der Klasse deckt interne Aufrufe ab.“
Merksatz: Transaktionen starten am proxy; this ist nicht der proxy.
Lesen: Transaktionen
5. REQUIRED vs REQUIRES_NEW vs NESTED?
Deutsch
REQUIRED vs REQUIRES_NEW vs NESTED?
Level: Mid · Listen for: same connection vs suspend; independent commit
Niveau: Mid · Darauf hören sie: gleiche Connection vs suspend; unabhängiger commit
Model answer
REQUIRED: join the current transaction or start one. Default. One commit, one rollback for the whole use case.REQUIRES_NEW: suspend the current one, start an independent transaction, commit even if the outer later rolls back. Uses another connection from the pool.NESTED: savepoint inside the same transaction (JDBC savepoints). Inner rollback does not kill the outer, but the outer commit still commits the inner work.
I use REQUIRES_NEW for things that must persist regardless (audit log) — and I am careful about pool exhaustion and visibility of uncommitted outer rows.
Follow-ups
- Can
REQUIRES_NEWsee uncommitted outer inserts? (isolation: usually no) - Deadlock with a depleted pool?
Trap: REQUIRES_NEW on every repository “to be safe”.
Memory sentence: Default join; REQUIRES_NEW is a second transaction, not a stronger REQUIRED.
Study: Transactions
Musterantwort
REQUIRED: der aktuellen Transaktion beitreten oder eine starten. Default. Ein commit, ein rollback für den ganzen Use Case.REQUIRES_NEW: die aktuelle suspendieren, eine unabhängige Transaktion starten, committen, auch wenn die äußere später zurückrollt. Nimmt eine andere Connection aus dem Pool.NESTED: Savepoint in derselben Transaktion (JDBC savepoints). Inneres rollback reißt die äußere nicht mit, aber der äußere commit schreibt die innere Arbeit trotzdem fest.
Ich nutze REQUIRES_NEW für Dinge, die unbedingt persistieren müssen (audit log) — und ich bin vorsichtig bei Pool-Erschöpfung und bei der Sichtbarkeit uncommitted äußerer Zeilen.
Nachfragen
- Kann
REQUIRES_NEWuncommitted äußere inserts sehen? (isolation: meist nein) - Deadlock bei leerem Pool?
Falle: REQUIRES_NEW auf jedem Repository „um sicher zu gehen“.
Merksatz: Default heißt beitreten; REQUIRES_NEW ist eine zweite Transaktion, kein stärkeres REQUIRED.
Lesen: Transaktionen
6. Isolation levels — what actually goes wrong?
Deutsch
Isolation-Level — was geht wirklich schief?
Level: Senior · Listen for: dirty / non-repeatable / phantom; Postgres vs MySQL; don’t default to SERIALIZABLE
Niveau: Senior · Darauf hören sie: dirty / non-repeatable / phantom; Postgres vs MySQL; nicht default SERIALIZABLE
Model answer
Isolation trades consistency for concurrency:
| Phenomenon | READ COMMITTED | REPEATABLE READ | SERIALIZABLE |
|---|---|---|---|
| Dirty read | no | no | no |
| Non-repeatable read | possible | prevented* | prevented |
| Phantom | possible | depends on DB | prevented |
Postgres default is READ COMMITTED. MySQL InnoDB default is REPEATABLE READ. The names are standard; the implementations differ (MVCC, gap locks).
I stay at the database default unless I can name the anomaly. Then I use optimistic locking, SELECT FOR UPDATE, or a narrower critical transaction — not a global SERIALIZABLE.
Follow-ups
- Write skew?
- Why is a long
SERIALIZABLEtransaction a product outage?
Trap: reciting the table without saying which database.
Memory sentence: Name the anomaly and the database; do not sprinkle isolation annotations.
Study: Transactions
Musterantwort
Isolation tauscht Konsistenz gegen Nebenläufigkeit:
| Phänomen | READ COMMITTED | REPEATABLE READ | SERIALIZABLE |
|---|---|---|---|
| Dirty read | nein | nein | nein |
| Non-repeatable read | möglich | verhindert* | verhindert |
| Phantom | möglich | hängt von der DB ab | verhindert |
Postgres-Default ist READ COMMITTED. MySQL InnoDB-Default ist REPEATABLE READ. Die Namen sind Standard; die Implementierungen unterscheiden sich (MVCC, gap locks).
Ich bleibe beim Datenbank-Default, außer ich kann die Anomalie benennen. Dann nutze ich optimistic locking, SELECT FOR UPDATE oder eine engere kritische Transaktion — kein globales SERIALIZABLE.
Nachfragen
- Write skew?
- Warum ist eine lange
SERIALIZABLE-Transaktion ein Ausfall im Produkt?
Falle: die Tabelle aufsagen, ohne die Datenbank zu nennen.
Merksatz: Anomalie und Datenbank benennen; Isolation-Annotations nicht streuen.
Lesen: Transaktionen
7. Optimistic vs pessimistic locking?
Deutsch
Optimistic vs pessimistic locking?
Level: Mid · Listen for: @Version; lost update; when to FOR UPDATE
Niveau: Mid · Darauf hören sie: @Version; lost update; wann FOR UPDATE
Model answer
Optimistic: @Version column. Update includes WHERE version = ?. Zero rows → OptimisticLockException. The user retries. Best for low-conflict, read-mostly data.
Pessimistic: SELECT … FOR UPDATE (LockModeType.PESSIMISTIC_WRITE). Blocks other writers. Use for short, high-contention critical sections (inventory decrement) and keep the transaction tiny.
Without either, two transactions can lost-update a balance.
Follow-ups
@Versionon a DTO round-trip?- What HTTP status for optimistic failure? (409)
Trap: pessimistic lock held while calling a remote HTTP API.
Memory sentence: Version for rare conflicts; FOR UPDATE for short, hot rows.
Study: Transactions
Musterantwort
Optimistic: @Version-Spalte. Update enthält WHERE version = ?. Keine Treffer → OptimisticLockException. Der User versucht es erneut. Am besten bei wenig Konflikt, read-mostly Daten.
Pessimistic: SELECT … FOR UPDATE (LockModeType.PESSIMISTIC_WRITE). Blockiert andere Writer. Für kurze, heiß umkämpfte critical sections (Bestand verringern), Transaktion winzig halten.
Ohne beides können zwei Transaktionen einen Saldo per lost update überschreiben.
Nachfragen
@Versionauf einem DTO-Round-Trip?- Welcher HTTP-Status bei einem optimistic-locking-Fehler? (409)
Falle: pessimistic lock halten, während du eine entfernte HTTP-API aufrufst.
Merksatz: Version für seltene Konflikte; FOR UPDATE für kurze, heiße Rows.
Lesen: Transaktionen
8. Owning side of a relationship?
Deutsch
Owning side einer Beziehung?
Level: Mid · Listen for: who has the foreign key; mappedBy
Niveau: Mid · Darauf hören sie: wer den foreign key hat; mappedBy
Model answer
In JPA, one side owns the FK. @ManyToOne is usually the owner. @OneToMany(mappedBy = "order") is the inverse: it is not how the FK is written.
If you only order.getLines().add(line) and never line.setOrder(order), Hibernate may not persist the FK. Keep a helper order.addLine(line) that sets both sides.
@ManyToMany with two lists and no owner discipline duplicates join-table rows.
Follow-ups
- Why is
@OneToManyas the owning side (join column on parent) usually a bad schema? - Cascade
ALL+orphanRemoval?
Trap: treating mappedBy as “the important side”.
Memory sentence: The owning side is the side that writes the foreign key.
Study: Entity relationships
Musterantwort
In JPA besitzt eine Seite den FK — die owning side. @ManyToOne ist meist der Owner. @OneToMany(mappedBy = "order") ist die Inverse: so wird der FK nicht geschrieben.
Wenn du nur order.getLines().add(line) machst und nie line.setOrder(order), persistiert Hibernate den FK oft nicht. Nimm einen Helper order.addLine(line), der beide Seiten setzt.
@ManyToMany mit zwei Listen und ohne klare owning side dupliziert Join-Table-Rows.
Nachfragen
- Warum ist
@OneToManyals owning side (join column am Parent) meist ein schlechtes Schema? - Cascade
ALL+orphanRemoval?
Falle: mappedBy als „die wichtige Seite“ behandeln.
Merksatz: Die owning side ist die Seite, die den foreign key schreibt.
Lesen: Entity Relationships
9. What is Open Session in View, and why do people disable it?
Deutsch
Was ist Open Session in View, und warum schalten Leute es ab?
Level: Senior · Listen for: persistence context open until the view is rendered; hidden N+1
Niveau: Senior · Darauf hören sie: persistence context offen bis die View gerendert ist; verstecktes N+1
Model answer
With spring.jpa.open-in-view=true (Boot default, with a warning), the persistence context stays open for the whole web request, including JSON serialization. Lazy fields in a controller DTO mapper (or worse, Jackson on an entity) silently run SQL after the service transaction committed.
That hides LazyInitializationException and turns your HTTP layer into a query engine. I prefer OSIV off, explicit fetch in the service, and DTO mapping before return. Failures then show up in tests, not as production N+1.
Follow-ups
- When is OSIV acceptable? (legacy MVC + templates, carefully)
- How do you prove N+1 in tests? (
@DataJpaTest+ query counts / datasource-proxy)
Trap: enabling OSIV to “fix” lazy exceptions without looking at SQL.
Memory sentence: OSIV keeps the session open so the view can trigger SQL — that is the bug.
Study: JPA performance
Musterantwort
Mit spring.jpa.open-in-view=true (Boot-Default, mit Warnung) bleibt der persistence context für den ganzen Web-Request offen, inklusive JSON-Serialisierung. Lazy-Felder in einem Controller-DTO-Mapper (oder schlimmer: Jackson auf einer Entity) lösen unbemerkt SQL aus, nachdem die Service-Transaktion committed hat.
Das versteckt LazyInitializationException und macht aus deiner HTTP-Schicht eine Query-Engine. Ich schalte OSIV lieber aus, lade im Service explizit per fetch und mappe auf DTOs, bevor ich zurückgebe. Fehler landen dann in Tests, nicht als N+1 in Produktion.
Nachfragen
- Wann ist OSIV akzeptabel? (Legacy MVC + Templates, vorsichtig)
- Wie beweist du N+1 in Tests? (
@DataJpaTest+ Query-Counts / datasource-proxy)
Falle: OSIV einschalten, um lazy Exceptions zu „fixen“, ohne auf SQL zu schauen.
Merksatz: OSIV hält die Session offen, damit die View SQL auslösen kann — das ist der Bug.
Lesen: JPA Performance
10. Derived queries vs @Query vs Specifications vs native SQL?
Deutsch
Derived queries vs @Query vs Specifications vs native SQL?
Level: Mid · Listen for: when names lie; pagination + fetch join limits
Niveau: Mid · Darauf hören sie: wenn Namen lügen; Pagination + fetch-join Grenzen
Model answer
Method names (findByEmailAndStatus) are fine for 2–3 fields. They become unreadable and can generate surprising SQL (especially In + IgnoreCase + And).
@Query (JPQL) when I need a fetch join or an explicit select. Specifications/Querydsl when the filter is a dynamic UI. Native SQL when I need a DB feature (upsert, window functions) and I accept the coupling.
Pagination + join fetch of collections is a known Hibernate trap (HQL limitation / InMemory pagination). I use a two-query pattern or a DTO query instead of pretending Page<Entity> + collection fetch is free.
Follow-ups
@EntityGraphvsjoin fetch?- Why
countqueries explode with fetch joins?
Trap: a 20-token derived method name as a public API.
Memory sentence: Names for simple lookups; JPQL when you must see the SQL shape.
Study: Repository query methods
Musterantwort
Methodennamen (findByEmailAndStatus) sind ok für 2–3 Felder. Danach unlesbar, und sie können überraschendes SQL erzeugen (besonders In + IgnoreCase + And).
@Query (JPQL), wenn ich einen fetch join oder ein explizites select brauche. Specifications/Querydsl, wenn der Filter eine dynamische UI ist. Native SQL, wenn ich ein DB-Feature brauche (upsert, window functions) und die Kopplung akzeptiere.
Pagination + join fetch von Collections ist eine bekannte Hibernate-Falle (HQL-Limitation / InMemory-Pagination). Ich nutze ein Two-Query-Pattern oder eine DTO-Query, statt so zu tun, als wäre Page<Entity> + Collection-Fetch umsonst.
Nachfragen
@EntityGraphvsjoin fetch?- Warum explodieren
count-Queries mit fetch joins?
Falle: ein abgeleiteter Methodenname mit 20 Tokens als public API.
Merksatz: Namen für einfache Abfragen; JPQL, wenn du die SQL-Form sehen musst.
Lesen: Repository Query Methods
11. save on a managed entity vs a new one?
Deutsch
save auf einer managed Entity vs einer neuen?
Level: Mid · Listen for: persist vs merge; extra SELECT
Niveau: Mid · Darauf hören sie: persist vs merge; extra SELECT
Model answer
Spring Data save:
- new (null id, or
@Versionunsaved):persist/ insert - detached with an id: often
merge, which may SELECT then UPDATE
If the entity is already managed, mutating fields is enough; save is redundant (and can be confusing). For a REST update I load inside the transaction, map fields, and let flush write.
saveAndFlush / flush exist for when the next statement must see the row (id generation, constraint errors before the method ends).
Follow-ups
- Assigned ids (not generated) — how does
isNewwork? (Persistable) - Why can merge copy a stale detached graph over a managed one?
Trap: findById + new Entity(id) + save as an update (that is a merge of an empty object).
Memory sentence: Load, mutate, flush; save is not an UPDATE keyword.
Study: JPA mental model
Musterantwort
Spring Data save:
- neu (id null, oder
@Versionnoch nicht gespeichert):persist/ insert - detached mit id: oft
merge, das kann SELECT then UPDATE machen
Ist die Entity schon managed, reicht Mutieren der Felder; save ist redundant (und kann verwirren). Bei einem REST-Update lade ich in der Transaktion, mappe Felder und lasse flush schreiben.
saveAndFlush / flush gibt es, wenn das nächste Statement die Zeile sehen muss (Id-Generierung, Constraint-Fehler vor Methodenende).
Nachfragen
- Zugewiesene ids (nicht generated) — wie funktioniert
isNew? (Persistable) - Warum kann merge einen veralteten detached Graph über einen managed Graph kopieren?
Falle: findById + new Entity(id) + save als Update (das ist ein merge eines leeren Objekts).
Merksatz: Laden, mutieren, flush; save ist kein UPDATE-Keyword.
Lesen: JPA Mental Model
12. Second-level cache?
Deutsch
Second-level cache?
Level: Senior · Listen for: SessionFactory cache, not first-level; invalidation is the hard part
Niveau: Senior · Darauf hören sie: SessionFactory-Cache, nicht first-level; Invalidierung ist der harte Teil
Model answer
First-level cache is the persistence context (per transaction). Second-level cache is shared across sessions (Ehcache, Caffeine, Redis via integrations) for entities/collections/queries.
It helps read-mostly reference data. It hurts when writes are frequent: you must configure eviction and you can serve stale data. Clustered caches need a coherent strategy.
I do not turn on hibernate.cache.use_second_level_cache as a first performance move. I fix N+1 and indexes first. For API-level caching I often prefer Spring Cache / HTTP cache on DTOs.
Follow-ups
- Query cache vs entity cache?
- Why is the query cache easy to get wrong?
Trap: second-level cache as a bandage for missing indexes.
Memory sentence: L2 is a shared entity cache; invalidation is the product.
Musterantwort
First-level cache ist der persistence context (pro Transaktion). Second-level cache ist über Sessions geteilt (Ehcache, Caffeine, Redis über Integrationen) für Entities/Collections/Queries.
Gut für read-mostly Referenzdaten. Schlecht, wenn viel geschrieben wird: du musst Eviction konfigurieren und kannst veraltete Daten ausliefern. Caches im Cluster brauchen eine kohärente Strategie.
Ich schalte hibernate.cache.use_second_level_cache nicht als ersten Performance-Schritt an. Zuerst N+1 und Indexes. Für API-Caching bevorzuge ich oft Spring Cache / HTTP-Cache auf DTOs.
Nachfragen
- Query cache vs entity cache?
- Warum geht der query cache so leicht schief?
Falle: second-level cache als Pflaster für fehlende Indexes.
Merksatz: L2 ist ein geteilter entity cache; Invalidierung ist das eigentliche Problem.
13. How do you run schema changes in production?
Deutsch
Wie fährst du Schema-Änderungen in Produktion?
Level: Mid · Listen for: Flyway/Liquibase, not ddl-auto=update
Niveau: Mid · Darauf hören sie: Flyway/Liquibase, nicht ddl-auto=update
Model answer
spring.jpa.hibernate.ddl-auto=update is for experiments. In production I use Flyway or Liquibase migrations in version control, run on deploy, backward compatible (expand/contract): add column nullable → deploy code → backfill → constrain.
Hibernate validate in prod is a good safety net: the entities must match the migrated schema.
I never drop columns in the same release that still reads them.
Follow-ups
- Expand/contract for renaming a column?
- Why do migrations belong in CI?
Trap: create-drop on a shared dev database.
Memory sentence: Migrations are code; ddl-auto=update is not a release process.
Musterantwort
spring.jpa.hibernate.ddl-auto=update ist für Experimente. In Produktion nutze ich Flyway- oder Liquibase-Migrationen in der Versionskontrolle, die beim Deploy laufen, rückwärtskompatibel (expand/contract): Spalte nullable hinzufügen → Code deployen → Daten nachziehen → Constraint setzen.
Hibernate validate in Prod ist ein gutes Sicherheitsnetz: die Entities müssen zum migrierten Schema passen.
Ich lösche nie Spalten im selben Release, das sie noch liest.
Nachfragen
- Expand/contract beim Umbenennen einer Spalte?
- Warum gehören Migrationen in CI?
Falle: create-drop auf einer geteilten Dev-Datenbank.
Merksatz: Migrationen sind Code; ddl-auto=update ist kein Release-Prozess.
14. How do you keep a transaction short?
Deutsch
Wie hältst du eine Transaktion kurz?
Level: Senior · Listen for: no HTTP inside a transaction; no user think-time
Niveau: Senior · Darauf hören sie: kein HTTP in der Transaktion; keine User-Denkzeit
Model answer
A transaction holds a DB connection and possibly row locks. I do not:
- call external HTTP/APIs inside
@Transactional - read a file, hash a password with bcrypt, or process images
- wait for user input
- start a transaction in a filter and leave it open until the view renders (OSIV-ish)
Pattern: load what you need, compute outside, then a short write transaction. For “call payment provider, then mark paid”, I use an idempotent state machine and messages, not a transaction around the HTTP call.
Follow-ups
- Hikari pool exhaustion symptoms?
- Outbox pattern?
Trap: @Transactional on a method that sends email and charges a card.
Memory sentence: Transactions are for the database work, not for the whole business process.
Study: Transactions
Musterantwort
Eine Transaktion hält eine DB-Connection und oft Row-Locks. Ich tue nicht:
- externe HTTP/APIs innerhalb von
@Transactionalaufrufen - eine Datei lesen, ein Passwort mit bcrypt hashen oder Bilder verarbeiten
- auf User-Input warten
- eine Transaktion in einem Filter starten und offen lassen, bis die View gerendert wird (OSIV-mäßig)
Vorgehen: laden, was du brauchst, rechnen außerhalb, dann eine kurze Write-Transaktion. Für „Payment-Provider aufrufen, dann als bezahlt markieren“ nutze ich eine idempotente State Machine und Messages, keine Transaktion um den HTTP-Call.
Nachfragen
- Symptome von Hikari-Pool-Erschöpfung?
- Outbox-Pattern?
Falle: @Transactional auf einer Methode, die E-Mail schickt und eine Karte belastet.
Merksatz: Transaktionen sind für die Datenbankarbeit, nicht für den ganzen Geschäftsprozess.
Lesen: Transaktionen
15. How do you detect N+1 in tests and production?
Deutsch
Wie erkennst du N+1 in Tests und Produktion?
Level: Senior · Listen for: query count assertions, logs, traces
Niveau: Senior · Darauf hören sie: Query-Count-Assertions, Logs, Traces
Model answer
Tests: datasource-proxy / hibernate.stat / a wrapper that fails if query count > N for a given use case. One test per endpoint that is known to load a graph.
Dev: spring.jpa.show-sql is noisy; better a log of bind parameters + p6spy or datasource-proxy.
Prod: JDBC metrics, tracing spans per query, slow query log. A latency spike on one endpoint plus query count is N+1 until proven otherwise.
Follow-ups
- Why can
findAll+ DTO mapper be N+1 even with OSIV off if you lazy-touch in the mapper? - Hibernate
@BatchSizehiding N+1 as N/batch?
Trap: looking only at the first SQL in the log.
Memory sentence: Count the queries for the use case; do not trust a single select.
Study: JPA performance
Musterantwort
Tests: datasource-proxy / hibernate.stat / ein Wrapper, der fehlschlägt, wenn query count > N für einen Use Case. Ein Test pro Endpoint, der bekanntermaßen einen Graph lädt.
Dev: spring.jpa.show-sql ist laut; besser ein Log der Bind-Parameter + p6spy oder datasource-proxy.
Prod: JDBC-Metriken, Tracing-Spans pro Query, Slow-Query-Log. Eine Latenzspitze auf einem Endpoint plus Query-Count ist N+1, bis das Gegenteil feststeht.
Nachfragen
- Warum kann
findAll+ DTO-Mapper N+1 sein, selbst mit OSIV aus, wenn du im Mapper lazy anfasst? - Hibernate
@BatchSizeversteckt N+1 als N/batch?
Falle: nur das erste SQL im Log anschauen.
Merksatz: Queries für den Use Case zählen; einem einzelnen select nicht trauen.
Lesen: JPA Performance
16. @Transactional on a repository vs on a service?
Deutsch
@Transactional auf einem Repository vs auf einem Service?
Level: Mid · Listen for: one use case = one unit of work
Niveau: Mid · Darauf hören sie: ein Use Case = eine Unit of Work
Model answer
Spring Data repository methods are transactional individually (read-only for find, write for save). If a service calls save three times without its own @Transactional, that is three transactions: partial success is possible.
The service method is the use case: one transaction, all or nothing. Repositories stay transactional so they work when called alone (or from a non-transactional caller).
I do not put @Transactional on the controller. HTTP mapping is not a unit of work.
Follow-ups
- What if the service is
protected? (proxy may skip) - Class-level vs method-level?
Trap: assuming three repository calls share a transaction because they are in the same class.
Memory sentence: One service method, one transaction, unless you can name why not.
Study: Transactions
Musterantwort
Spring-Data-Repository-Methoden sind einzeln transaktional (read-only für find, write für save). Ruft ein Service dreimal save ohne eigenes @Transactional auf, sind das drei Transaktionen: Teilerfolg ist möglich.
Die Service-Methode ist der Use Case: eine Transaktion, alles oder nichts. Repositories bleiben transaktional, damit sie allein funktionieren (oder von einem nicht-transaktionalen Aufrufer).
Ich setze @Transactional nicht auf den Controller. HTTP-Mapping ist keine Unit of Work.
Nachfragen
- Was, wenn der Service
protectedist? (proxy kann überspringen) - Auf der Klasse vs auf der Methode?
Falle: annehmen, drei Repository-Aufrufe teilen sich eine Transaktion, weil sie in derselben Klasse stehen.
Merksatz: Eine Service-Methode, eine Transaktion, außer du kannst sagen, warum nicht.
Lesen: Transaktionen