CodeGym /Kurse /Hibernate deep-dive /Native SQL – die Grenze von ORM

Native SQL – die Grenze von ORM

Hibernate deep-dive
Level 10, Lektion 3
Verfügbar

1. Die Rolle von native SQL neben JPQL/HQL

Bisher haben wir die Art und Weise geändert, wie wir eine Abfrage beschreiben, und sind dabei innerhalb des Entity-Modells geblieben: zuerst JPQL/HQL, dann programmatic where über Criteria und Specification. Aber es gibt eine andere Art von Schmerz: Die Leseform ist bereits tabellarisch – Aggregate, group by, vendor-spezifische Funktionen, weite Joins, Berichtszeilen.

Wenn Sie sich gerade erst an JPQL gewöhnt haben, kann die Idee, „SQL von Hand zu schreiben“, wie ein Rückschritt in die Steinzeit wirken. Aber in der Praxis ist es kein Rückschritt, sondern ein Gangwechsel: Manchmal fahren Sie in der Stadt (JPQL), manchmal brauchen Sie einen Geländewagen (native SQL). Hibernate verbietet Ihnen SQL nicht – es sagt ehrlich: „Okay, wenn du die Sprache der Tabellen und Spalten sprechen musst, dann sprich sie, ich führe sie im Rahmen der Transaktion aus.“

In seiner einfachsten Form ist native SQL eine Abfrage, die Sie Hibernate als SQL-Zeichenkette übergeben, ohne die Phase der „Übersetzung aus HQL/JPQL“. Das heißt, Sie schreiben nicht:

select o from PurchaseOrder o join o.customer c ...

sondern:

select o.id, o.order_number, c.email
from purchase_order o
join customer c on c.id = o.customer_id
...

Und hier ist es wichtig, den Kern des Wortes „ehrliche Grenze“ zu erfassen. JPQL/HQL versucht, objektorientiert zu sein und vom Mapping-Modell abzuhängen. Native SQL hängt direkt vom physischen Schema ab: Tabellen-, Spalten-, Index-, Typen- und Funktionsnamen der jeweiligen DB. In unserem Kurs ist das nicht abstrakt: Wir haben PostgreSQL, ein Schema unter Flyway, und wir sehen wirklich, was in die DB geht.

Um es Ihnen leichter zu machen, behalten Sie dieses kleine Schema im Kopf:

flowchart TD
    A[Service layer] --> B[EntityManager]
    B --> C{Welche Abfrage?}
    C -->|JPQL/HQL| D[Hibernate übersetzt in SQL]
    C -->|Native SQL| E[SQL bereits fertig, keine Übersetzung]
    D --> F[JDBC]
    E --> F[JDBC]
    F --> G[(PostgreSQL)]

Sowohl JPQL als auch native SQL erreichen letztendlich PostgreSQL. Der Unterschied liegt darin, wer für die „Form des SQL“ verantwortlich ist – Hibernate oder Sie.

2. Wann native SQL eine gute Wahl ist

Native SQL wird oft als „letzte Hoffnung, wenn Hibernate nervt“ wahrgenommen. Aber im normalen Ingenieursleben sollte es keine emotionale Entscheidung sein, sondern eine bewusste Wahl. Hier ist es sinnvoll, nicht über die Schönheit der API nachzudenken, sondern darüber, wie die richtige Form des Ergebnisses aussieht: welche Spalten Sie brauchen, welche Aggregate, welche Gruppierung, welche Sortierung, wie viele Joins – und wie natürlich sich dies durch das Entity-Modell beschreiben lässt.

Im Folgenden sind einige typische Situationen aufgeführt, in denen native SQL ehrlicher und einfacher erscheint. Ich werde dies nicht in eine „Liste aller möglichen SQL-Techniken auf dem Planeten“ verwandeln, aber wir werden die Intuition festigen, damit Sie keine Angst vor dieser Grenze haben.

Lese-Szenario im Commerce Persistence Lab Warum die ORM-Sprache Widerstand leistet Warum SQL „natürlicher“ ist
Bericht „Wie viele Bestellungen in jedem Status“ Man braucht group by, möchte genau zwei Ergebnisfelder, Entity-Graph wird nicht benötigt SQL drückt das Aggregat kurz aus und schleppt keine Entitäten mit
Bericht „Top-Kunden nach Bestellsumme im Zeitraum“ Join + Aggregation + Filter über Datumsbereich, Ergebnis ist keine Entität SQL beschreibt direkt das tabellarische Ergebnis
Komplexe Abfragen „wie in Berichten“: weiter Join, viele berechnete Spalten JPQL wird lang, schwer lesbar und unterstützt nicht immer die benötigten Funktionen SQL erlaubt es, die Form des Ergebnisses explizit zu steuern
DB-spezifische Funktionen (PostgreSQL): date_trunc, ILIKE, manchmal Fensterfunktionen JPQL ist standardisiert, vendor-Funktionen erfordern Workarounds In SQL sieht das normal und vorhersagbar aus

Daher sind die folgenden Beispiele bewusst berichtsorientiert. Bei der normalen Suche nach Bestellungen nach Status und E-Mail funktioniert native SQL zwar auch, aber seine Stärke zeigt sich dort weniger: In solchen Fällen zeigt es eher den Preis der direkten Kontrolle, als dass es das Standardwerkzeug wäre.

Die Hauptidee: Native SQL wird meistens für das Read-Modell benötigt, besonders wenn das Ergebnis „Berichtszeilen“ und kein Objektgraph ist. Dies passt sehr gut zu dem, was wir gestern gemacht haben (Projektionen): Anstatt eine managed-Entity zu laden, lesen Sie ein schmales Ergebnis.

Und noch ein wichtiger Punkt: Im Rahmen des Kurses machen wir aus native SQL keine Religion. Wir schreiben nicht „alles auf SQL um“. Wir lernen, den Moment zu erkennen, in dem ORM nicht mehr hilft, und wählen dann ehrlich ein anderes Werkzeug.

3. Der Preis von native SQL

Wenn Sie native SQL schreiben, kaufen Sie Kontrolle – aber wie bei jedem Kauf gibt es eine Rechnung. Und die Rechnung ist manchmal unangenehmer, als es auf den ersten freudigen „Hurra, ich habe SQL geschrieben, und es funktioniert“-Moment erscheint.

Erstens: Die Abfrage wird an das Schema gebunden. In JPQL schreiben Sie Product und p.status, in SQL schreiben Sie product und status. Wenn Sie morgen eine Spalte umbenennen, die Naming-Strategy ändern oder eine Tabelle aufteilen, wird die Abfrage brechen, und die IDE wird Ihnen nicht so beim Refactoring helfen, wie sie es bei Java-Code tun würde.

Zweitens: Das Ergebnis muss gemappt werden. In JPQL/Criteria können Sie eine DTO-Konstruktorprojektion sehr sauber zurückgeben. In native SQL erhalten Sie häufiger Object[] oder skalare Werte, und Sie sind selbst für die Typkonvertierung verantwortlich. Das ist nicht beängstigend, erfordert aber Disziplin: count(*) in PostgreSQL kommt leicht als BigInteger statt als Long, und wenn Sie versuchen, (Long) row[1] zu machen, wird Java Ihnen ehrlich eine „ClassCastException“ werfen, und das zu Recht.

Drittens: Die Portabilität zwischen Datenbanken sinkt. In unserem Kurs ist das kein Problem, da die Basislinie PostgreSQL ist und wir nicht „dasselbe auf Oracle“ spielen. Aber in realen Projekten kann dies ein Faktor sein.

Viertens: Native SQL hebt die Hibernate-Realität nicht auf. Die Abfrage wird weiterhin innerhalb einer Transaktionsgrenze ausgeführt, kann immer noch einen Flush auslösen, unterliegt immer noch Ihrer open-in-view=false-Einstellung und sollte immer noch aus dem SQL-Trace verständlich sein. Es ist also kein „paralleles Universum“ – es ist nur ein anderer Eingang in dasselbe System.

Deshalb ist es eine gute Praxis, native SQL lokal und ordentlich zu halten: im .../query-Paket, mit einem lesbaren Textblock von SQL, mit einem verständlichen Methodennamen, der ein DTO (und nicht „irgendetwas“) zurückgibt.

4. Wo native SQL im Projekt aufbewahrt wird

Im Commerce Persistence Lab haben wir eine Package-by-Feature-Architektur, und das hilft direkt, native SQL unter Kontrolle zu halten. Wenn Sie anfangen, SQL-Zeichenketten über die Services zu verstreuen, erhalten Sie in einer Woche einen „Friedhof der String-Konstanten“ und werden dann Angst haben, irgendeine Abfrage anzufassen, weil „der Bericht, den irgendjemand verwendet, sonst kaputtgeht“.

Der normale Weg ist, eine separate Query-Komponente im Feature anzulegen. Zum Beispiel für Bestellberichte:

- com.example.commerce.orders.dto – kleine DTOs/records für die Ergebnisse,
- com.example.commerce.orders.query – Klassen, die Abfragen ausführen und DTOs zurückgeben.

Beispiel für ein Grundgerüst (ohne Lombok – denken Sie an die Projektregeln):

package com.example.commerce.orders.query;

import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Repository;

@Repository
public class OrderReportQueryRepository {
    // EntityManager – unser Einstiegspunkt in JPA, über ihn führen wir native Queries aus
    private final EntityManager entityManager;

    public OrderReportQueryRepository(EntityManager entityManager) {
        // Wir injizieren den EntityManager über den Konstruktor, damit das Repository testbar ist
        this.entityManager = entityManager;
    }
}

Warum ist das gut? Weil die Service-Schicht „für das Geschäftsszenario“ zuständig bleibt und die Query-Schicht „für die Leseform“. Und native SQL gehört genau zum zweiten.

5. Rezept für native SQL: scalar + DTO

Der freundlichste Weg, mit native SQL zu beginnen, ist nicht, sofort das Ergebnis in eine Entity zu mappen (das führt oft zu Verwirrung), sondern ein skalares Ergebnis zu erzeugen: ein paar Spalten zu erhalten und diese dann in ein kleines DTO umzuwandeln. Das ist genau das, was man normalerweise für Berichte und Admin-Listen braucht.

DTO für das Ergebnis: Bestellungen nach Status zählen

Zuerst erstellen wir ein einfaches record für das Ergebnis:

package com.example.commerce.orders.dto;

/**
 * Eine Zeile des Berichts: Bestellstatus und Anzahl der Bestellungen in diesem Status.
 */
public record OrderStatusCountRow(String status, long count) { }

Jetzt schreiben wir native SQL. Angenommen, die Bestelltabelle heißt purchase_order und die Statuss palte status. (In einem echten Projekt hängen die Namen von Ihren Migrationen und der Naming-Strategy ab, aber die Idee ist dieselbe: In SQL sprechen wir mit Schemanamen, nicht mit Feldern der Java-Klasse.)

package com.example.commerce.orders.query;

import com.example.commerce.orders.dto.OrderStatusCountRow;
import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class OrderReportQueryRepository {
    private final EntityManager entityManager;

    public OrderReportQueryRepository(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public List<OrderStatusCountRow> countOrdersByStatus() {
        // SQL lässt sich besser in einem Textblock halten: liest sich fast wie in psql
        String sql = """
            select o.status, count(*) as cnt
            from purchase_order o
            group by o.status
            order by o.status
            """;

        // createNativeQuery gibt „rohe“ Zeilen zurück: normalerweise Object[] nach Spalten
        List<Object[]> rows = entityManager.createNativeQuery(sql).getResultList();

        return rows.stream()
            .map(r -> {
                // r[0] – Status (String), r[1] – Aggregat count(*)
                // Aggregate sollten besser über Number gecastet werden: JDBC-Treiber können verschiedene numerische Typen zurückgeben
                return new OrderStatusCountRow((String) r[0], ((Number) r[1]).longValue());
            })
            .toList();
    }
}

Beachten Sie den kleinen, aber sehr wichtigen Trick: Wir casten r[1] nicht zu Long, sondern zu Number. Dadurch können wir verschiedene JDBC-Typen überleben, die der Treiber für count(*) zurückgeben kann. Für einen Anfänger sieht das aus wie „warum so kompliziert“, aber in der Praxis ist es eine der häufigsten Quellen für plötzliche Abstürze zur Laufzeit.

Abfrage aus dem Service aufrufen (und die Transaktion im Kopf behalten)

Um dies wie eine normale Anwendung aussehen zu lassen, umschließen wir das Lesen in einem Service, der explizit die Transaktionsgrenze zeigt. Für einen Lesevorgang ist normalerweise readOnly=true angebracht.

package com.example.commerce.orders.service;

import com.example.commerce.orders.dto.OrderStatusCountRow;
import com.example.commerce.orders.query.OrderReportQueryRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class OrderReportService {
    private final OrderReportQueryRepository queryRepository;

    public OrderReportService(OrderReportQueryRepository queryRepository) {
        this.queryRepository = queryRepository;
    }

    @Transactional(readOnly = true)
    public List<OrderStatusCountRow> getOrderStatusStats() {
        // Der Service beschreibt die Geschäftsoperation, nicht die SQL-Details: wir geben einfach das DTO-Ergebnis zurück
        return queryRepository.countOrdersByStatus();
    }
}

Wichtig: Selbst wenn Sie ein DTO zurückgeben, ist die Transaktion immer noch nützlich. Sie sorgt für ein vorhersagbares Verhalten von EntityManager/Session und verhindert, dass Sie versehentlich in eine detached-Welt abtauchen, in der jemand Lazy Loading erwartet (obwohl wir in unserem Fall dies gerade bewusst vermeiden).

6. Joins und Aggregate in native SQL

Wenn Sie zum ersten Mal native SQL in die Hand nehmen, möchten Sie es am liebsten „für alles“ verwenden. Aber es ist besser, genau das Gegenteil zu tun: SQL dort zu verwenden, wo es wirklich in seinem Element ist. Das natürlichste Beispiel ist ein Bestellbericht, in dem berechnete Felder benötigt werden: Anzahl der Positionen, Summe der Positionen, E-Mail des Kunden, Datum der letzten Änderung usw.

Stellen wir uns eine Backoffice-Aufgabe vor: Wir möchten eine Liste der Bestellungen erhalten und für jede Bestellung berechnen, wie viele Positionen sie hat und wie hoch die Summe der Positionen ist. Ja, ein Teil dieser Daten könnte bereits in PurchaseOrder.totalAmount gespeichert sein, aber im Laborprojekt ist es nützlich zu zeigen, dass der Bericht auch aus order_item erstellt werden kann – um zu spüren, wie sich ORM und SQL im Ansatz unterscheiden.

DTO für die „Berichtszeile“

package com.example.commerce.orders.dto;

import java.math.BigDecimal;
import java.time.OffsetDateTime;

/**
 * Eine „Berichtszeile“ für eine Bestellung (keine Entity!).
 * Enthält genau die Felder, die auf dem Bildschirm/im Export benötigt werden.
 */
public record OrderSummaryRow(
    long orderId,
    String orderNumber,
    String customerEmail,
    long itemsCount,
    BigDecimal itemsTotal,
    OffsetDateTime createdAt
) { }

6.2. Native SQL-Abfrage

package com.example.commerce.orders.query;

import com.example.commerce.orders.dto.OrderSummaryRow;
import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.List;

@Repository
public class OrderSummaryQueryRepository {
    private final EntityManager entityManager;

    public OrderSummaryQueryRepository(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public List<OrderSummaryRow> findOrderSummaries() {
        // Hier wird die „Form des Ergebnisses“ direkt im SELECT beschrieben: Join + Aggregate + Sortierung
        String sql = """
            select
                o.id,
                o.order_number,
                c.email,
                count(oi.id) as items_count,
                coalesce(sum(oi.price_at_purchase_amount * oi.quantity), 0) as items_total,
                o.created_at
            from purchase_order o
            join customer c on c.id = o.customer_id
            left join order_item oi on oi.order_id = o.id
            group by o.id, o.order_number, c.email, o.created_at
            order by o.created_at desc
            """;

        List<Object[]> rows = entityManager.createNativeQuery(sql).getResultList();

        return rows.stream()
            .map(r -> new OrderSummaryRow(
                // id und Aggregate – über Number, um nicht von einem bestimmten JDBC-Typ abzuhängen
                ((Number) r[0]).longValue(),
                (String) r[1],
                (String) r[2],
                ((Number) r[3]).longValue(),
                // sum(...) in PostgreSQL wird normalerweise als BigDecimal zurückgegeben – das ist für Geld zu erwarten
                (BigDecimal) r[4],
                // Achten Sie darauf, welchen Datumstyp der Treiber zurückgibt (OffsetDateTime/ZonedDateTime/Timestamp)
                (OffsetDateTime) r[5]
            ))
            .toList();
    }
}

Ja, hier gibt es Details, die von Ihrem Schema abhängen. Ich zeige bewusst die Idee und nicht „universelles SQL für alle Zeiten“. In unserem Projekt können Money und die Summe anders gespeichert sein (z. B. amount + currency). Aber auf der Ideenebene ist alles ehrlich: SQL sieht Tabellen und Spalten, und Sie können die Berichtszeile genau dort aufbauen, wo sie natürlicherweise entsteht – in einem select mit join und group by.

Ein weiteres wichtiges Detail: Ich habe coalesce(sum(...), 0) verwendet, damit die Summe nicht null wird, wenn eine Bestellung keine Positionen hat (z. B. wenn die Bestellung gerade erst erstellt und noch leer ist). In JPQL können Sie etwas Ähnliches tun, aber in SQL liest es sich normalerweise einfacher und vertrauter.

7. Native SQL und der Persistenzkontext

Es gibt eine weit verbreitete Illusion: „Wenn die Abfrage native ist, hat Hibernate nichts damit zu tun.“ Das ist falsch. Hibernate hat sehr wohl damit zu tun – es übersetzt die Abfrage nur nicht, sondern führt sie aus.

7.1. Flush vor der Abfrage und „plötzliche INSERT/UPDATE“

Wir haben bereits flush besprochen: Hibernate kann Änderungen vor der Ausführung der Abfrage an die DB senden, damit die Abfrage korrekte Daten sieht. Das gilt für JPQL, Criteria und native SQL gleichermaßen.

Stellen Sie sich eine Szene vor (im Stil einer schlechten Sitcom): Sie haben innerhalb einer Transaktion den Status einer Bestellung geändert, aber noch nicht committet. Dann führen Sie eine berichtende native SQL-Abfrage aus, die Bestellungen nach Status zählt. Wenn Hibernate keinen flush durchführt, sieht das SQL die alten Status, und das Ergebnis ist „falsch“ in Bezug auf die aktuelle Unit of Work. Daher kann Hibernate entscheiden: „Zuerst synchronisieren wir den Kontext, dann lesen wir.“

Die praktische Schlussfolgerung ist ruhig und pragmatisch: Wenn Sie berichtende native Queries schreiben, versuchen Sie, sie in Szenarien auszuführen, in denen Sie keine schweren Schreibvorgänge und Berichtslesevorgänge in derselben Transaktion mischen, es sei denn, es ist notwendig. Andernfalls werden Sie sich wundern: „Ich lese doch nur, woher kommt das UPDATE?“

7.2. Rückgabe einer Entity aus native SQL

Manchmal möchte man Folgendes tun: „Ich schreibe SQL, aber Hibernate soll mir Product zurückgeben.“ Das ist möglich:

// Wir geben die Entity-Klasse zurück, nicht das DTO: das ist bereits der „ORM-Modus“ mit all seinen Nuancen
String sql = """
    select *
    from product
    where status = :status
    """;

List<?> products = entityManager
    // Wichtig: Als zweiten Parameter übergeben wir die Entity-Klasse, damit Hibernate versucht, die Entity zu erstellen
    .createNativeQuery(sql, Product.class)
    // Parameter werden immer gebunden, nicht konkateniert
    .setParameter("status", status)
    .getResultList();

Aber hier beginnen die Nuancen, wegen denen ein Anfänger besser sofort die Regel befolgen sollte: Native SQL verwenden wir in dieser Lektion als scalar/DTO-Read. Wenn Sie eine Entity zurückgeben, betreten Sie wieder die Welt der managed-Objekte, des Dirty Checkings, möglicher Lazy-Beziehungen und zufälliger N+1 nach der Abfrage. Außerdem müssen Sie die Spalten sorgfältig auswählen, damit Hibernate die Entity korrekt zusammenbauen kann.

In den meisten Berichtsszenarien brauchen Sie keine Entity. Sie brauchen eine „Berichtszeile“ – und das ist genau das DTO.

8. Disziplin für native SQL

Native SQL macht keine Angst, weil es komplex ist. Es macht Angst, weil es leicht in Chaos ausartet, wenn es keine Regeln gibt. Und ehrlich gesagt, das Chaos entsteht normalerweise nicht in der SQL-Zeichenkette selbst, sondern um sie herum: wo sie liegt, wie sie getestet wird, wie sie dokumentiert ist, wie sie gewartet wird.

Eine gute Disziplin für unseren Kurs sieht so aus.

Die SQL-Zeichenkette selbst sollte als SQL geschrieben sein, nicht als „Verkettung von fünf Zeilen und drei ifs“. In Java 25 sind Textblöcke (""") ein Geschenk des Himmels: Man kann eine Abfrage fast wie in psql schreiben, mit Einrückungen und Zeilenumbrüchen. Das erhöht die Lesbarkeit enorm.

Parameter sollten immer über setParameter(...) gebunden werden, nicht über "... where status = '" + status + "'". Erstens schützt das vor SQL-Injection (selbst wenn Sie denken, „es ist ein interner Service“ – er bleibt intern genau bis zum ersten Vorfall). Zweitens macht es den SQL-Trace verständlicher: Sie sehen die Parameter separat, nicht als „Brei“ in der Zeichenkette.

Das Abfrageergebnis sollte ein kleines DTO/record haben. Selbst wenn Sie denken, dass Object[] „auch so reicht“, ist in der Praxis ein DTO eine billige Versicherung: Sie fixieren den Ergebnisvertrag in Typen, und der nächste Leser des Codes versteht, welche „fünf Spalten“ zurückgegeben wurden.

Und schließlich: Native SQL sollte besser in der Query-Schicht gehalten werden, nicht im Service. Der Service sollte sagen: „gib mir List<OrderSummaryRow>“, nicht „gib mir List<Object[]>, und ich rate selbst, was darin steckt“.

9. Typische Fehler in native SQL

Im Folgenden sind die Fehler aufgeführt, die fast immer auftreten, wenn jemand zum ersten Mal native SQL in ein Hibernate-Projekt einfügt. Nicht weil die Leute dumm sind, sondern weil das Gehirn noch im Modus „ORM macht alles selbst“ lebt, und SQL erfordert etwas mehr manuelle Disziplin.

Fehler Nr. 1: In SQL wie in JPQL schreiben und Feldnamen mit Spaltennamen verwechseln.
Ein sehr häufiger Punkt: Im Kopf ist PurchaseOrder.createdAt, und die Hand schreibt createdAt in SQL. Aber SQL lebt im Schema: Höchstwahrscheinlich heißt die Spalte created_at. Infolgedessen lässt sich die Abfrage auf der DB-Seite nicht kompilieren, und Sie erhalten einen Fehler „column does not exist“. Die Abhilfe ist einfach: Bevor Sie SQL schreiben, schalten Sie gedanklich in den Modus „ich spreche mit Tabellen“ um, nicht mit Entitäten.

Fehler Nr. 2: select * verwenden und hoffen, dass es „einfacher“ ist.
select * erscheint bequem, solange das Schema klein ist. Dann kommt eine Spalte hinzu, dann ändern sich Typen, dann beginnt die Abfrage plötzlich, unnötige Daten zu schleppen, und Sie verstehen nicht mehr, was Sie genau lesen. In Berichtsszenarien ist select * fast immer schädlich: Sie wollen genau die Spalten, die für das Ergebnis benötigt werden. Das macht sowohl das SQL verständlicher als auch das Mapping stabiler.

Fehler Nr. 3: Typen zu „direkt“ casten und eine ClassCastException erhalten.
count(*) wurde als BigInteger zurückgegeben, sum(...) als BigDecimal, und Sie erwarten Long oder Integer. Die Abhilfe ist eine einfache Regel: Aggregate casten Sie häufiger über Number, und Summen speichern Sie in BigDecimal, selbst wenn „im UI trotzdem long“ steht.

Fehler Nr. 4: Erwarten, dass das Ergebnis von native SQL managed Entity-Objekte sind (und dass sie einen Lazy-Graphen haben).
Wenn Sie Object[] oder DTO lesen, handelt es sich nicht um managed Entities. Es sind nur Daten. Sie haben kein Lazy Loading, Dirty Checking, Persistenzkontext und andere „ORM-Magie“. Das ist ein Plus, kein Minus: DTOs können sicher nach außen gegeben, gespeichert, protokolliert, verglichen werden, und sie beginnen nicht plötzlich, SQL auszulösen, wenn toString() aufgerufen wird.

Fehler Nr. 5: Sich über den flush vor der Abfrage wundern („ich lese doch nur, woher kommt das UPDATE?“).
Flush ist Teil der Unit of Work und kann vor einer Abfrage ausgelöst werden, damit die Abfrage korrekt ist. Native SQL hebt das flush-Modell von Hibernate nicht auf. Wenn Sie innerhalb einer Transaktion managed-Entitäten ändern und dann eine native Query ausführen – seien Sie darauf gefasst, ein SQL für einen Schreibvorgang früher zu sehen, als Sie erwartet haben. In ORM-Kursen ist dies einer der nützlichsten Momente für die Reifung: SQL wird nicht nach „Laune des Entwicklers“ ausgeführt, sondern nach den Regeln der Konsistenz.

1
Aufgabe
Hibernate deep-dive, Level 10, Lektion 3
Gesperrt
Bericht über den Status von Produkten via native SQL
Bericht über den Status von Produkten via native SQL
1
Aufgabe
Hibernate deep-dive, Level 10, Lektion 3
Gesperrt
Kundenbestellungsliste via native SQL und manuelles Mapping
Kundenbestellungsliste via native SQL und manuelles Mapping
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION