Ich habe eine Beziehung zwischen drei Modellobjekten in meinem Projekt (Modell- und Repository-Snippets am Ende des Beitrags.
Wenn ich anrufe PlaceRepository.findById
, werden drei ausgewählte Abfragen ausgelöst:
("sql")
SELECT * FROM place p where id = arg
SELECT * FROM user u where u.id = place.user.id
SELECT * FROM city c LEFT OUTER JOIN state s on c.woj_id = s.id where c.id = place.city.id
Das ist eher ungewöhnliches Verhalten (für mich). Soweit ich nach dem Lesen der Hibernate-Dokumentation feststellen kann, sollten immer JOIN-Abfragen verwendet werden. Es gibt keinen Unterschied zwischen den Abfragen, wenn FetchType.LAZY
sie FetchType.EAGER
in der Place
Klasse geändert werden (Abfrage mit zusätzlichem SELECT). Dies gilt auch für die City
Klasse, wenn sie FetchType.LAZY
geändert wird FetchType.EAGER
(Abfrage mit JOIN).
Wenn ich die CityRepository.findById
Unterdrückung von Bränden verwende, werden zwei Optionen ausgewählt:
SELECT * FROM city c where id = arg
SELECT * FROM state s where id = city.state.id
Mein Ziel ist es, in allen Situationen das gleiche Verhalten zu haben (entweder immer JOIN oder SELECT, JOIN jedoch bevorzugt).
Modelldefinitionen:
Platz:
@Entity
@Table(name = "place")
public class Place extends Identified {
@Fetch(FetchMode.JOIN)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_user_author")
private User author;
@Fetch(FetchMode.JOIN)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "area_city_id")
private City city;
//getters and setters
}
Stadt:
@Entity
@Table(name = "area_city")
public class City extends Identified {
@Fetch(FetchMode.JOIN)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "area_woj_id")
private State state;
//getters and setters
}
Repositories:
PlaceRepository
public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {
Place findById(int id);
}
UserRepository:
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findAll();
User findById(int id);
}
CityRepository:
public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom {
City findById(int id);
}
Antworten:
Ich denke, dass Spring Data den FetchMode ignoriert. Ich verwende immer die Anmerkungen
@NamedEntityGraph
und,@EntityGraph
wenn ich mit Spring Data arbeiteÜberprüfen Sie die Dokumentation hier
quelle
List<Place> findAll();
endet aber mit der Ausnahmeorg.springframework.data.mapping.PropertyReferenceException: No property find found for type Place!
. Es funktioniert, wenn ich manuell hinzufüge@Query("select p from Place p")
. Scheint jedoch eine Problemumgehung zu sein.@EntityGraph
fast ununsable in realen Szenarien ist , da sie angegeben werden können nicht , welche Art vonFetch
uns verwenden möchten (JOIN
,SUBSELECT
,SELECT
,BATCH
). Dies in Kombination mit der@OneToMany
Zuordnung und bewirkt, dass der Ruhezustand die gesamte Tabelle in den Speicher abruft, selbst wenn Abfrage verwendet wirdMaxResults
.Erstens
@Fetch(FetchMode.JOIN)
und@ManyToOne(fetch = FetchType.LAZY)
antagonistisch, weist einer einen EAGER-Abruf an, während der andere einen LAZY-Abruf vorschlägt.Eifriges Abrufen ist selten eine gute Wahl, und für ein vorhersehbares Verhalten ist es besser, die
JOIN FETCH
Direktive für die Abfragezeit zu verwenden :quelle
Spring-jpa erstellt die Abfrage mithilfe des Entitätsmanagers, und der Ruhezustand ignoriert den Abrufmodus, wenn die Abfrage vom Entitätsmanager erstellt wurde.
Das Folgende ist die Arbeit, die ich verwendet habe:
Implementieren Sie ein benutzerdefiniertes Repository, das von SimpleJpaRepository erbt
Überschreiben Sie die Methode
getQuery(Specification<T> spec, Sort sort)
:Fügen Sie in der Mitte der Methode add hinzu
applyFetchMode(root);
, um den Abrufmodus anzuwenden, damit der Ruhezustand die Abfrage mit dem richtigen Join erstellt.(Leider müssen wir die gesamte Methode und die zugehörigen privaten Methoden aus der Basisklasse kopieren, da es keinen anderen Erweiterungspunkt gab.)
Implementieren
applyFetchMode
:quelle
"
FetchType.LAZY
" wird nur für den Primärtisch ausgelöst. Wenn Sie in Ihrem Code eine andere Methode aufrufen, die eine übergeordnete Tabellenabhängigkeit aufweist, wird eine Abfrage ausgelöst, um diese Tabelleninformationen abzurufen. (FIRES MULTIPLE SELECT)"
FetchType.EAGER
" erstellt direkt einen Join aller Tabellen einschließlich der relevanten übergeordneten Tabellen. (USESJOIN
)Verwendungszweck: Angenommen, Sie müssen die abhängige übergeordnete Tabelleninformation zwangsweise verwenden und dann auswählen
FetchType.EAGER
. Wenn Sie nur Informationen für bestimmte Datensätze benötigen, verwenden SieFetchType.LAZY
.Denken Sie daran, dass
FetchType.LAZY
an der Stelle in Ihrem Code eine aktive Datenbank-Session-Factory erforderlich ist, an der Sie Informationen zur übergeordneten Tabelle abrufen können.ZB für
LAZY
:Zusätzliche Referenz
quelle
NamedEntityGraph
da ich ein nicht hydratisiertes Objektdiagramm wollte.Der Abrufmodus funktioniert nur, wenn das Objekt anhand seiner ID ausgewählt wird
entityManager.find()
. Da Spring Data immer eine Abfrage erstellt, hat die Konfiguration des Abrufmodus für Sie keine Verwendung. Sie können entweder dedizierte Abfragen mit Abrufverknüpfungen oder Entitätsdiagramme verwenden.Wenn Sie die beste Leistung erzielen möchten, sollten Sie nur die Teilmenge der Daten auswählen, die Sie wirklich benötigen. Zu diesem Zweck wird im Allgemeinen empfohlen, einen DTO-Ansatz zu verwenden, um zu vermeiden, dass unnötige Daten abgerufen werden müssen. Dies führt jedoch normalerweise zu einer Menge fehleranfälligen Boilerplate-Codes, da Sie eine dedizierte Abfrage definieren müssen, die Ihr DTO-Modell über JPQL erstellt Konstruktorausdruck.
Spring Data-Projektionen können hier helfen, aber irgendwann benötigen Sie eine Lösung wie Blaze-Persistence Entity Views , die dies ziemlich einfach macht und viel mehr Funktionen in der Hülle hat, die sich als nützlich erweisen werden! Sie erstellen lediglich eine DTO-Schnittstelle pro Entität, in der die Getter die Teilmenge der Daten darstellen, die Sie benötigen. Eine Lösung für Ihr Problem könnte so aussehen
Haftungsausschluss, ich bin der Autor von Blaze-Persistence, daher bin ich möglicherweise voreingenommen.
quelle
Ich habe die Antwort dream83619 ausgearbeitet , damit verschachtelte
@Fetch
Annotationen im Ruhezustand verarbeitet werden können . Ich habe eine rekursive Methode verwendet, um Anmerkungen in verschachtelten zugeordneten Klassen zu finden.Sie müssen also ein benutzerdefiniertes Repository implementieren und die
getQuery(spec, domainClass, sort)
Methode überschreiben . Leider müssen Sie auch alle referenzierten privaten Methoden kopieren :(.Hier ist der Code,
kopierte private Methoden werden weggelassen.BEARBEITEN: Verbleibende private Methoden hinzugefügt.
quelle
http://jdpgrailsdev.github.io/blog/2014/09/09/spring_data_hibernate_join.html
über diesen Link:
Wenn Sie JPA über dem Ruhezustand verwenden, gibt es keine Möglichkeit, den von Hibernate verwendeten FetchMode auf JOIN zu setzen. Wenn Sie JPA über Hibernate verwenden, können Sie den von Hibernate verwendeten FetchMode jedoch nicht auf JOIN setzen.
Die Spring Data JPA-Bibliothek bietet eine API für domänengesteuerte Entwurfsspezifikationen, mit der Sie das Verhalten der generierten Abfrage steuern können.
quelle
Laut Vlad Mihalcea (siehe https://vladmihalcea.com/hibernate-facts-the-importance-of-fetch-strategy/ ):
Es scheint, dass die JPQL-Abfrage Ihre deklarierte Abrufstrategie möglicherweise überschreibt, sodass Sie sie verwenden
join fetch
müssen, um eine referenzierte Entität eifrig zu laden, oder einfach mit EntityManager nach ID laden (was Ihrer Abrufstrategie entspricht, aber möglicherweise keine Lösung für Ihren Anwendungsfall darstellt ).quelle