1. Managed-Lesen ist manchmal teuer
Wenn man Hibernate „nach Gefühl“ liest, sieht alles einfach aus: man macht ein select, bekommt Java-Objekte, lebt weiter. Aber Hibernate ist keine Bibliothek zum „Daten holen“, sondern eine Engine für unit of work. Selbst wenn Sie nichts ändern, bereitet es sich darauf vor, dass Sie es sich doch anders überlegen, es nehmen und ändern. Und diese Bereitschaft kostet auch Geld: CPU, Speicher und manchmal unerwartete Nebeneffekte.
Wenn Sie eine Entität als gewöhnliche managed laden, legt Hibernate sie in den persistence context und erstellt in der Regel einen Snapshot – vereinfacht gesagt, „fotografiert“ den Zustand beim Eintritt. Am Ende der Transaktion (oder früher, wenn flush() ausgelöst wird) vergleicht die Engine die aktuellen Felder der Entität mit dem Snapshot und entscheidet, ob ein UPDATE nötig ist. In einem kleinen CRUD-Service sieht das nicht wie ein Problem aus. Aber sobald Sie ein Szenario haben „5.000 Produkte für Export/Bericht/Neuindizierung lesen“, hält Hibernate ehrlich 5.000 managed-Objekte und bereitet sich potenziell darauf vor, sie zu überprüfen.
Wichtig ist, nicht zu verwechseln: Der Read-only-Modus geht nicht um die Reduzierung der Anzahl von SELECT. Er repariert nicht N+1, macht die Abfrage nicht „schneller“. Er geht vielmehr darum, dass Hibernate beim Lesen keine unnötige Arbeit im Zusammenhang mit „falls Sie doch etwas aktualisieren“ macht.
Unten sehen Sie ein einfaches Diagramm, warum gewöhnliches managed-Laden teurer ist als es scheint:
%% (8) Umschließen Beschriftungen mit ":" in Anführungszeichen, damit Mermaid nicht über Sonderzeichen stolpert
flowchart TD
A[SELECT aus der DB] --> B[Entity im persistence context]
B --> C[Snapshot für Dirty Checking]
C --> D[Sie tun etwas mit dem Objekt]
D --> E["Flush: Vergleich von Snapshot und aktuellem Zustand"]
E --> F[UPDATE falls Änderungen vorliegen]
Read-only ist der Versuch, Hibernate ehrlich zu sagen: „Kumpel, heute sind wir im Museum. Wir schauen, fassen nicht an.“
2. Read-only in Hibernate ist ein Modus
Das Wort „read-only“ führt viele in die Irre, weil es sich anhört wie „Objekt darf nicht geändert werden“. Aber in Java können Sie jederzeit einen Setter aufrufen, ein Feld zuweisen oder eine Referenz ersetzen – die JVM fragt nicht um Erlaubnis bei Hibernate. Read-only in ORM ist keine Handschellen an Ihren Händen, sondern eine Änderung dessen, wie ORM mit den geladenen Daten umgeht.
Auf Hibernate-Ebene bedeutet read-only meistens, dass die Entität vom Dirty Checking ausgeschlossen wird. Das heißt, Sie können das Objekt im Speicher ändern, aber Hibernate muss es nicht bemerken und muss kein UPDATE generieren. Dadurch ergeben sich zwei Effekte. Erstens wird der Overhead für Snapshots und Änderungsprüfungen reduziert. Zweitens schützen Sie sich vor versehentlichen „Oh, der Setter hat sich bewegt“ und unerwarteten UPDATE am Ende der Transaktion – aber nur, wenn read-only richtig und auf der richtigen Ebene aktiviert ist.
Hier eine ähnliche, aber eher „read-only“-Schema:
%% (8) Umschließen Beschriftungen mit ":" in Anführungszeichen, damit Mermaid nicht über Sonderzeichen stolpert
flowchart TD
A[SELECT aus der DB] --> B[Entity als read-only geladen]
B --> C[Änderungen am Java-Objekt möglich]
C --> D["Flush: Dirty Checking überspringt Entität"]
D --> E[UPDATE wird nicht erstellt]
Und hier beginnt das Interessanteste: In Hibernate und um ihn herum gibt es mehrere „Schichten“, auf denen read-only aktiviert werden kann. Sie ähneln sich vom Namen her, unterscheiden sich aber in Wirkung und Wirkungsbereich. Daher werden wir im Folgenden nicht von „read-only im Allgemeinen“ sprechen, sondern von „read-only auf Abfrage-/Session-/Transaktionsebene“.
3. Drei Ebenen von Read-only: Abfrage, Session, Transaktion
Wenn ein Entwickler zum ersten Mal „read-only“ hört, stellt er sich oft ein magisches Häkchen „Lesen beschleunigen“ vor. In Wirklichkeit sind es drei verschiedene Werkzeuge, und jedes beantwortet seine eigene Frage: Wollen wir ein bestimmtes Ergebnis einer bestimmten Abfrage markieren, wollen wir einen ganzen Use Case in den Lesemodus versetzen, oder wollen wir auf Ebene der Spring-Transaktion erklären, dass die Methode nur zum Lesen ist.
Um dies nicht als wirren Haufen im Kopf zu behalten, ist es praktisch, einmal einen Vergleich festzuhalten. Dies ist nicht „die Wahrheit in allen Weltversionen“, sondern eine praktische Karte für unseren Kurs und unser Projekt:
| Ebene | Wie aktiviert | Wirkungsbereich | Was bringt es | Wo angebracht | Hauptrisiko |
|---|---|---|---|---|---|
| Query-level read-only | hint "org.hibernate.readOnly" oder Query#setReadOnly(true) | Nur Ergebnisse der spezifischen Abfrage | Weniger Dirty Checking für diese Entitäten | Eine schwere Abfrage in der Mitte einer normalen Session | Man könnte ein „Änderungsverbot“ erwarten, aber es ist kein Verbot, sondern ein Ignorieren |
| Session-level default read-only | Session#setDefaultReadOnly(true) | Alle nachfolgenden Ladevorgänge in dieser Session (bis ausgeschaltet) | Praktisch für „reinen Read Use Case“ | Bericht/Export/Suche, wo definitiv kein Schreiben | Wenn man vergisst, zurückzusetzen – das Schreiben „hört plötzlich auf zu speichern“ |
| Transaction-level read-only | @Transactional(readOnly = true) | Transaktionsgrenze (unit of work) | Hinweis an die Infrastruktur, senkt oft die Flush-Aktivität | Service-Methoden „nur Lese“ | Annehmen, dass dies ein „eisernes Schreibverbot“ sei (ist es nicht) |
Im Folgenden werden wir jede Ebene einzeln besprechen, und das Wichtigste – nicht nur „wie aktivieren“, sondern auch „was genau ändert sich im Verhalten von Hibernate“ und „wo kann man leicht einen Fehler machen“.
4. Query-level Read-only: Modus für die Abfrage
Die verständlichste (und für die Psyche sicherste) Variante ist, read-only genau dort zu aktivieren, wo es benötigt wird: für eine einzelne spezifische Abfrage. Dieser Ansatz ist wie ein Skalpell: Sie „versetzen nicht die gesamte Operation in read-only“, sondern sagen zu Hibernate: „Dieses Ergebnis werde ich definitiv nicht ändern, verschwende keine unnötige Energie darauf.“
Read-only hint über JPA setHint(...)
Hibernate unterstützt einen Hibernate-spezifischen Hint "org.hibernate.readOnly". Aus Sicht von JPA ist es nur ein String-Hint, aber aus Sicht von Hibernate ein Signal, die geladenen Entitäten als read-only zu markieren.
Beispiel in unserer Domäne (Product) könnte so aussehen:
import jakarta.persistence.EntityManager;
import java.util.List;
// Wichtig: Hint beeinflusst das Verhalten von Hibernate (Dirty Checking), nicht "beschleunigt SELECT" magisch.
List<Product> products = entityManager.createQuery(
"select p from Product p where p.deleted = false order by p.name",
Product.class
)
// Hibernate-spezifischer Hint: markiert geladene Entitäten als read-only.
.setHint("org.hibernate.readOnly", true)
// Immer noch eine normale Abfrage: Ergebnis kommt in den persistence context (identity map).
.getResultList();
Hier ist es wichtig, die Magie nicht zu überschätzen: Die Abfrage wird trotzdem ein SELECT in der DB ausführen, die Entitäten landen trotzdem im persistence context (d. h. sie sind „managed“ im Sinne der identity map), aber Hibernate muss sie nicht so in den Dirty-Checking-Mechanismus einbeziehen, wie es normale managed-Entitäten einbeziehen würde.
readOnly bedeutet nicht „Java-Objekt darf nicht geändert werden“
Der zweite Punkt, der Anfänger verwirrt: Sie können das Objekt im Speicher ändern, auch wenn es read-only ist. Hibernate wird diese Änderung höchstwahrscheinlich nicht in die Datenbank schreiben.
Ein anschauliches Beispiel (mit einem kleinen Kommentar dazu, was passieren wird):
import jakarta.persistence.EntityManager;
// Entität wird über eine Abfrage mit read-only Hint geladen.
Product product = entityManager.createQuery(
"select p from Product p where p.id = :id",
Product.class
)
// Parametrisierung der Abfrage: übliche Praxis, read-only hat nichts damit zu tun.
.setParameter("id", 1L)
// Markiert genau das Ergebnis dieser Abfrage als read-only.
.setHint("org.hibernate.readOnly", true)
.getSingleResult();
// Sie können das Java-Objekt ändern – die JVM fragt Hibernate nicht.
// UPDATE wird normalerweise nicht erfolgen, weil die Entität vom Dirty Checking ausgeschlossen ist.
product.setName("Changed only in memory");
Wenn Sie danach die Transaktion committen, können Sie durchaus sehen, dass kein UPDATE product ... erschienen ist. Für „reines Lesen“ ist das ein Plus. Für „Ich dachte, ich hätte gespeichert“ ist es eine hervorragende kleine Realitätstherapie.
Prüfung des Read-only-Status
Manchmal ist es nützlich, nicht nach SQL-Log zu raten, sondern Hibernate direkt zu fragen: „Ist die Entität read-only oder nicht?“. Dazu kann man für die Dauer einer Laboranalyse Session#isReadOnly(...) verwenden.
import jakarta.persistence.EntityManager;
import org.hibernate.Session;
// In Spring/JPA starten wir meist mit EntityManager, aber die Hibernate Session ist per unwrap verfügbar.
Session session = entityManager.unwrap(Session.class);
Product p = entityManager.createQuery(
"select p from Product p where p.id = :id", Product.class)
.setParameter("id", 1L)
.setHint("org.hibernate.readOnly", true)
.getSingleResult();
// Direkte Prüfung bei Hibernate: so raten wir nicht nach Logs und "Gefühlen".
System.out.println(session.isReadOnly(p)); // true
Beachten Sie die Feinheit im Zusammenhang mit der ersten Vorlesung des Tages. Wenn die Entität mit dieser ID bereits früher als normale managed in den persistence context geladen wurde, gibt Hibernate dieselbe Instanz zurück (identity map), und „sie mit einer anderen Abfrage read-only neu zu laden“ funktioniert möglicherweise nicht wie erwartet. Innerhalb eines persistence context kann es nicht zwei verschiedene Versionen „eine Zeile – zwei Entitäten mit unterschiedlichem Modus“ geben. Das ist eine direkte Folge der identity map.
Read-only und Größe des persistence context
Read-only reduziert die Kosten des Dirty Checking, verwandelt aber das Laden von 10.000 Entitäten nicht in „nichts“. Sie leben immer noch im first-level cache, belegen Speicher und nehmen an der Navigation von Assoziationen teil. Wenn Ihr Ziel ist, „sehr viel zu lesen und es nicht im Kontext zu halten“, ist read-only nicht das einzige Werkzeug (und kann sogar unwichtig sein). Aber im Rahmen dieser Vorlesung halten wir die Idee fest: Query-level read-only ist nützlich, wenn SQL bereits in Ordnung ist und Sie unnötige „Schreibbereitschaft“ für ein bestimmtes Ergebnis entfernen möchten.
5. Session-level Read-only: Read-only standardmäßig
Es gibt auch eine andere Art von Aufgaben: Sie wissen genau, dass die aktuelle Session/Transaktion reines Lesen ist. Zum Beispiel erstellen Sie einen Katalogexport, erstellen einen Bericht, sammeln Daten für Analysen. Dort möchten Sie nicht jede Abfrage mit Hints markieren – Sie möchten read-only „standardmäßig“ für alle Ladevorgänge in dieser Session aktivieren.
Session#setDefaultReadOnly(true) und try/finally
Hibernate erlaubt es, auf der Ebene der Session einen „Standard“-Read-only-Modus für alle Entitäten zu setzen, die später geladen werden. In einer Spring-Anwendung starten wir meist mit EntityManager, aber zur Session gelangt man über unwrap().
Die wichtigste Praxis hier ist langweilig, aber lebensrettend: Wenn Sie defaultReadOnly umgeschaltet haben, setzen Sie es unbedingt zurück. Sonst kann das nächste Code-Stück in derselben Session plötzlich aufhören, Änderungen zu speichern, und Sie werden es wie einen Trick mit einer verschwindenden Münze ansehen.
import jakarta.persistence.EntityManager;
import org.hibernate.Session;
// Rufen die Hibernate Session ab, um den Standard-Read-only-Modus auf Session-Ebene zu verwalten.
Session session = entityManager.unwrap(Session.class);
// Merken uns den vorherigen Wert, um ihn garantiert wiederherzustellen.
boolean prev = session.isDefaultReadOnly();
// Standardmäßig read-only für alle nachfolgenden Ladevorgänge in dieser Session aktivieren.
session.setDefaultReadOnly(true);
try {
// Alle Entitäten, die durch diese Abfrage geladen werden, sind read-only (wenn sie nicht vorher im Kontext waren).
return session.createQuery("from Product p order by p.id", Product.class)
.getResultList();
} finally {
// Kritisch: Modus zurücksetzen, sonst kann die nächste Schreiblogik "leise" aufhören zu funktionieren.
session.setDefaultReadOnly(prev);
}
Ja, das sieht nach „unnötigem Tanz“ aus. Aber das sind die Tänze, die später Stunden sparen, in denen man fragt: „Warum werden die Daten nicht aktualisiert, obwohl der Setter aufgerufen wurde?“.
Versuch, Daten in einer read-only Session zu ändern
Wenn Entitäten als read-only geladen sind, können Sie sie im Speicher ändern – und diese Änderung wird höchstwahrscheinlich nicht in die Datenbank gehen. Das Problem ist nicht, dass Hibernate „böse“ ist. Das Problem ist, dass Sie selbst gesagt haben: „Ich lese.“ Read-only auf Session-Ebene ist also keine lokale Optimierung mehr, sondern eine Vereinbarung: Dieser Teil des unit of work sollte keine versteckten Schreibvorgänge haben.
In der Praxis hilft das nicht nur der Leistung, sondern auch der Disziplin. Wenn Sie bewusst „Lesen“ von „Schreiben“ trennen, verringert sich die Wahrscheinlichkeit versehentlicher UPDATE an unerwarteten Stellen. Allerdings entsteht eine neue Möglichkeit: „Ich dachte, ich schreibe, aber es war eine read-only Session.“ Also nochmal: try/finally, und verwenden Sie dies nur an Stellen, wo das Szenario wirklich „reines Lesen“ ist.
6. Transaction-level Read-only in Spring
In Spring greift die Hand zum bequemsten und „offiziellsten“ Weg: @Transactional(readOnly = true) setzen und denken, die Welt sei besser geworden. Setzen kann und sollte man es – aber wichtig ist zu verstehen, dass dies in erster Linie ein Vertrag und ein Hinweis ist, nicht ein „eisernes Schreibverbot“. Sonst erhält man sehr seltsame Bugs: mal wurden Daten aktualisiert, mal nicht, mal nur, weil jemand saveAndFlush() aufgerufen hat.
Mentales Modell: Read-only-Transaktion ist ein Versprechen
Aus architektonischer Sicht macht @Transactional(readOnly = true) Ihre Servicemethode ehrlicher. Er sagt: „Dieser Use Case liest nur.“ Das hilft sofort beim Lesen des Codes, bei Code-Reviews und beim Auffinden von Stellen, wo jemand versucht hat, Lesen und Schreiben zu vermischen.
In einigen Konfigurationen von Spring + Hibernate kann eine read-only Transaktion auch die Flush-Aktivität reduzieren (z. B. den flush-Modus in einen „trägeren“ Modus für den aktuellen unit of work versetzen). Aber für den Kurs ist etwas anderes wichtiger: Selbst wenn die Infrastruktur nichts optimiert hat, ist allein die Tatsache des Vertrags nützlich, weil man beginnt, die Grenzen von Lesen und Schreiben bewusst zu gestalten.
Beispiel für einen Read-only Use Case im Commerce Persistence Lab
Stellen wir uns vor, wir haben einen „katalogartigen“ Read Use Case: eine Liste aktiver Produkte für das Backoffice (oder für Export) ausgeben. In dieser Methode ändern wir definitiv nichts.
import jakarta.persistence.EntityManager;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Transactional(readOnly = true) // Vertrag: Methode sollte keine Schreiblogik enthalten.
public List<Product> loadActiveProductsForBackoffice() {
// JPQL als Textblock ist bequemer zu lesen und zu bearbeiten, besonders wenn die Abfrage mehrzeilig wird.
// Dabei ist es immer noch ein normaler SELECT: readOnly=true auf der Transaktion ändert nicht "magisch" das SQL.
return entityManager.createQuery(
"""
select p
from Product p
where p.deleted = false
order by p.name
""",
Product.class
)
.getResultList();
}
Das ist eine minimale, „korrekte“ Ebene: ein Vertrag auf dem Service. Wenn sich später herausstellt, dass die Last wirklich hoch ist und das Dirty Checking spürbar ist, können Sie punktuell mit einem query-level Hint verstärken. Aber beginnen sollte man besser mit der Grenze: Read-only auf der Transaktion diszipliniert zuerst.
@Transactional(readOnly = true) – kein „Schreibverbot“
Man möchte gerne denken, dass Spring Ihnen jetzt nicht erlaubt, save() aufzurufen. Aber die Annotation macht Ihren Code nicht zur heiligen Kuh: Sie können immer noch das Repository aufrufen, Sie können immer noch UPDATE/INSERT ausführen, und vieles hängt davon ab, wie der Stack genau konfiguriert ist, welche Methoden Sie aufrufen und wo der Flush stattfindet.
Daher ist die Regel einfach und erwachsen: Wenn eine Methode als read-only markiert ist, sollte sie keine Schreiblogik enthalten. Nicht „wahrscheinlich nicht“, nicht „normalerweise schreibt er nicht“, sondern direkt nicht. Wenn Sie plötzlich einen Grund zum Schreiben finden, dann handelt es sich um einen anderen Use Case, der eine eigene Transaktionsgrenze braucht.
Kombination der Ebenen
Manchmal ist die effektivste Variante die Kombination von Transaktionsvertrag und query-level read-only für eine schwere Abfrage. Wichtig ist, dies nicht zur Religion „readOnly überall setzen“ zu machen. Tun Sie es dort, wo es wirklich sinnvoll ist: große Listen, Berichtslesungen, Export, „scanning“-Operationen.
import jakarta.persistence.EntityManager;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Transactional(readOnly = true) // Vertrag: Use Case ist lesend.
public List<Product> loadProductsReadOnly() {
return entityManager.createQuery("select p from Product p", Product.class)
// Markiert explizit das Abfrageergebnis als read-only, damit Hibernate keinen Aufwand für Dirty Checking betreibt.
.setHint("org.hibernate.readOnly", true)
.getResultList();
}
Der Sinn der Kombination ist: @Transactional(readOnly = true) fixiert den Vertrag des Use Cases, und der Hint macht die Optimierung und das Verhalten der Entitäten expliziter für diese Abfrage.
7. Auswahl der Read-only-Ebene
Nach alledem kann man leicht zur falschen Schlussfolgerung kommen: „Aha! Also muss alles read-only gelesen werden.“ Nicht nötig. Read-only ist ein gutes Werkzeug, wenn Sie verstehen, was genau Sie optimieren und welche Nebenwirkungen Sie in Kauf nehmen. In unserem Kurs haben wir bereits besprochen, dass für Listen oft Projections besser geeignet sind, weil sie die managed-Entity-Semantik gar nicht erst dorthin schleppen, wo sie nicht benötigt wird. Read-only ist ein Schritt innerhalb der Welt des Entity-Loadings, während Projection ein Schritt ist, „die Welt der Entität dort zu verlassen, wo sie nicht benötigt wird“.
Wenn Sie eine einfache Abfrage über 20 Zeilen machen und sofort ein DTO zurückgeben, werden Sie meistens keinen Nutzen von read-only sehen. Dafür können Sie durchaus einen unerwarteten Effekt erleben: Jemand hat die Entität im Speicher geändert, aber sie wurde nicht gespeichert, und das wird als „Hibernate ist kaputt“ wahrgenommen.
Eine nützliche Ingenieurslogik (ohne Fanatismus) ist: Zuerst reparieren Sie die Form der Abfrage, beseitigen N+1, wählen eine Projection, wo sie passt, und erst dann, wenn das Lesen immer noch schwer ist, betrachten Sie read-only als lokale Senkung der Kosten des Dirty Checking. Read-only ist also keine Erste Hilfe, sondern eine sorgfältige Feinabstimmung einer bereits normalen Lösung.
Überprüfung, ob read-only funktioniert hat
In Hibernate gibt es zwei schnelle Wege, sich nicht selbst zu täuschen. Erster: Schauen Sie ins SQL-Log und überzeugen Sie sich, dass wirklich kein unerwartetes UPDATE stattfindet. Zweiter: Prüfen Sie den Read-only-Status der Entität über die Hibernate API. Beide Methoden sind gut, weil sie das Gespräch von „scheint schneller“ zu „hier ist das beobachtete Verhalten“ verschieben.
Ein Beispiel zur Statusprüfung über Session#isReadOnly(...) haben wir bereits gesehen, aber hier ist das Prinzip wichtig: Wenn Sie eine Optimierung durchführen, sollten Sie sie in Begriffen des beobachtbaren Effekts erklären können. Man muss nicht Nanosekunden messen. Es reicht zu verstehen, dass die Entität vom Dirty Checking ausgeschlossen ist, und das kann man nicht nur durch den Glauben an die Annotation bestätigen.
Eine weitere nützliche Überprüfung ist, sich an die Grenze des persistence context zu erinnern. Read-only macht Entitäten nicht zu „Geistern“. Sie können immer noch im first-level cache leben, was bedeutet, dass Sie bei langen Transaktionen und großen Abfragen trotzdem an Speichergrenzen stoßen können. Daher harmoniert read-only gut mit der Disziplin „keine riesigen persistence contexts dort, wo sie nicht nötig sind“, aber allein löst es das nicht.
8. Typische Fehler bei der Arbeit mit Read-only
Fehler Nr. 1: Read-only als Verbot, Setter aufzurufen, wahrnehmen.
Auf Java-Ebene können Sie das Objekt beliebig ändern, weil es ein normales Objekt ist. Read-only ist ein Vertrag mit Hibernate darüber, ob er Änderungen nachverfolgt. Daher ist „Ich habe das Feld geändert“ nicht gleich „Ich habe die Änderung gespeichert.“ Das ist besonders unangenehm, wenn der Code logisch aussieht, die Datenbank aber „aus irgendeinem Grund nicht aktualisiert“ wurde.
Fehler Nr. 2: @Transactional(readOnly = true) als eisernes Schreibverbot ansehen.
Eine read-only Transaktion in Spring ist in erster Linie ein Vertrag und ein Hinweis. Sie verbessert die Disziplin und optimiert manchmal das Flush-Verhalten, macht Ihre Datenbank aber nicht zu einem Kloster, in das man kein UPDATE bringen kann. Wenn in einer solchen Methode plötzlich ein save() auftaucht, ist das keine „mutige Optimierung“, sondern ein architektonischer Fehler der Use-Case-Grenze.
Fehler Nr. 3: Read-only aktivieren anstelle eines normalen Read-Modells (Projection).
Wenn Ihr Use Case eine Liste im Backoffice, eine Tabelle, ein Export, ein Bericht ist, dann brauchen Sie sehr oft gar keine Entität. Eine read-only Entität ist immer noch eine Entität im persistence context mit eigenen Assoziationen, Proxies und Lebenszyklus. Projections und DTO-Abfragen sind oft einfacher, billiger und vorhersagbarer als „Entität, aber read-only“.
Fehler Nr. 4: Read-only Laden und Schreiben im selben unit of work mischen.
Manchmal scheint es praktisch: „Jetzt lese ich das read-only, dann korrigiere ich ein wenig und speichere.“ In der Praxis wird daraus eine schwer debuggbare Geschichte, bei der einige Entitäten read-only sind, andere nicht, und die identity map auch noch dasselbe Objekt aus dem first-level cache unterschiebt. Wenn das Szenario schreibt, machen Sie daraus ein Write-Szenario und verkomplizieren Sie es nicht mit Read-only-Magie.
Fehler Nr. 5: Session-level default read-only aktivieren und vergessen auszuschalten.
Das ist ein Klassiker. Der Code funktioniert, solange Sie den Bericht erstellen. Dann versuchen Sie im selben Thread (oder in derselben Session), eine Entität zu ändern – und die Änderungen „werden nicht gespeichert“. Es beginnen Verdächtigungen gegen Hibernate, Spring, Transaktionen, Mondphasen und das WLAN des Nachbarn. Die Ursache ist eine: Sie haben setDefaultReadOnly(false) nicht zurückgesetzt (oder den vorherigen Wert nicht wiederhergestellt) im finally.
GO TO FULL VERSION