Zum Hauptinhalt springen

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

  • JpaRepository vs CrudRepository vs ListCrudRepository?
  • 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

  • JpaRepository vs CrudRepository vs ListCrudRepository?
  • Wann steigst du auf EntityManager ab?

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: find by id does not hit the DB twice
  • Dirty checking: at flush, Hibernate compares snapshots and issues UPDATEs — you do not always call save for 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

  • persist vs merge vs Spring Data save?
  • 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: find per id trifft die DB nicht zweimal
  • Dirty checking: beim flush vergleicht Hibernate Snapshots und schickt UPDATEs — bei schon managed Entities rufst du nicht immer save auf
  • 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

  • persist vs merge vs Spring Data save?
  • 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 fetch in JPQL / @EntityGraph for that use case
  • DTO projection / select new so 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.LAZY on @ManyToOne in 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 fetch in JPQL / @EntityGraph fü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.LAZY auf @ManyToOne in 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 / final methods?

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 / final Methoden?

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_NEW see 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_NEW uncommitted ä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:

PhenomenonREAD COMMITTEDREPEATABLE READSERIALIZABLE
Dirty readnonono
Non-repeatable readpossibleprevented*prevented
Phantompossibledepends on DBprevented

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 SERIALIZABLE transaction 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änomenREAD COMMITTEDREPEATABLE READSERIALIZABLE
Dirty readneinneinnein
Non-repeatable readmöglichverhindert*verhindert
Phantommöglichhängt von der DB abverhindert

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

  • @Version on 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

  • @Version auf 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 @OneToMany as 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 @OneToMany als 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

  • @EntityGraph vs join fetch?
  • Why count queries 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

  • @EntityGraph vs join 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 @Version unsaved): 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 isNew work? (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 @Version noch 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 @Transactional aufrufen
  • 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 @BatchSize hiding 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 @BatchSize versteckt 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 protected ist? (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