Ich erstelle eine einfache Tomcat-Webanwendung, die Spring Data und Hibernate verwendet. Es gibt ein Endpunkt, der viel Arbeit macht, daher möchte ich die Arbeit in einen Hintergrund-Thread verlagern, so dass die Web-Anfrage nicht länger als 10 Minuten hängen bleibt, während die Arbeit erledigt wird. Also habe ich einen neuen Service in einem component-scan'd Paket geschrieben:
%Vor% Dann haben Sie die ThreadPoolTaskExecutor
im Frühjahr konfiguriert:
Das funktioniert alles super. Das Problem kommt jedoch von Hibernate. Innerhalb meiner Runable, Abfragen nur die Hälfte arbeiten. Ich kann tun:
%Vor%Aber wenn ich Felder lazy geladen habe, schlägt es fehl:
%Vor%Ich erhalte den folgenden Fehler:
%Vor%So sieht es für mich aus (Hibernate-Neuling), dass der neue Thread keine Sitzung mit diesen selbst erstellten Threads hat, aber Spring Data automatisch neue Sitzungen für HTTP-Request-Threads erstellt.
Ich konnte etwas damit umgehen, indem ich alles innerhalb einer @Transactional
-Methode mache, aber ich lerne schnell, dass das keine sehr gute Lösung ist, da ich dadurch keine Methoden verwenden kann, die einfach funktionieren gut für Web-Anfragen.
Danke.
Mit Spring brauchst du keinen eigenen Executor. Eine einfache Anmerkung @Async
erledigt die Arbeit für Sie. Beschriften Sie einfach Ihre heavyMethod
in Ihrem Dienst damit und geben Sie void oder ein Future
-Objekt zurück und Sie erhalten einen Hintergrund-Thread. Ich würde vermeiden, die asynchrone Annotation auf der Controller-Ebene zu verwenden, da dies einen asynchronen Thread im Executor des Anfragepools erzeugt und Sie möglicherweise keine 'Anforderungsakzeptoren' mehr haben.
Das Problem mit Ihrer verzögerten Ausnahme kommt, wie Sie vermutet haben, vom neuen Thread, der keine Sitzung hat. Um dieses Problem zu vermeiden, sollte Ihre asynchrone Methode die gesamte Arbeit behandeln. Stellen Sie keine zuvor geladenen Entitäten als Parameter bereit. Der Service kann einen EntityManager verwenden und kann auch transaktional sein.
Ich selbst füge @Async
und @Transactional
nicht zusammen, damit ich den Service auf jede Art ausführen kann. Ich erstelle nur einen asynchronen Wrapper um den Service herum und benutze diesen stattdessen, falls nötig. (Dies vereinfacht zum Beispiel das Testen)
Wahrscheinlich haben Sie eine Transaktion in Ihrem DAO-Code und Spring schließt die Sitzung beim Schließen der Transaktion.
Sie sollten Ihre gesamte Geschäftslogik in eine einzige Transaktion komprimieren.
Sie können SessionFactory
in Ihren Code einfügen und SessionFactory.openSession()
method verwenden.
Das Problem ist, dass Sie Ihre Transaktionen verwalten müssen.
Methode # 1: JPA Entity Manager
Im Hintergrund-Thread: Injizieren Sie den Entity Manager oder holen Sie ihn aus dem Spring-Kontext oder übergeben Sie ihn als Referenz:
%Vor%Erstellen Sie anschließend einen neuen Entity Manager, um die Verwendung eines gemeinsamen Entity Managers zu vermeiden:
%Vor%Jetzt können Sie die Transaktion starten und Spring DAO, Repository, JPA, etc
verwenden %Vor%Methode 2: JdbcTemplate
Falls Sie Änderungen auf niedriger Ebene benötigen oder Ihre Aufgabe einfach genug ist, können Sie dies manuell mit JDBC und Abfragen tun:
%Vor%und dann irgendwo in deiner Methode:
%Vor%Randnotiz: Ich würde empfehlen, @Transactional nicht zu verwenden, es sei denn, Sie verwenden JTA oder vertrauen auf den JpaTransactionManager.
Tags und Links java multithreading hibernate spring spring-data