2014-07-23 4 views
8

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 schrieb ich einen neuen Dienst in einem Bauteil-scan'd Paket:Wie führe ich einen Hintergrund-Thread korrekt aus, wenn Spring Data und Hibernate verwendet werden?

@Service 
public class BackgroundJobService { 
    @Autowired 
    private ThreadPoolTaskExecutor threadPoolTaskExecutor; 

    public void startJob(Runnable runnable) { 
     threadPoolTaskExecutor.execute(runnable); 
    } 
} 

Dann haben die ThreadPoolTaskExecutor konfiguriert im Frühjahr:

<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> 
    <property name="corePoolSize" value="5" /> 
    <property name="maxPoolSize" value="10" /> 
    <property name="queueCapacity" value="25" /> 
</bean> 

Das ist alles das Arbeiten groß. Das Problem kommt jedoch von Hibernate. Innerhalb meiner Runable, Abfragen nur die Hälfte arbeiten. Ich kann tun:

MyObject myObject = myObjectRepository.findOne() 
myObject.setSomething("something"); 
myObjectRepository.save(myObject); 

Aber wenn ich faul geladene Felder haben, ist es nicht:

MyObject myObject = myObjectRepository.findOne() 
List<Lazy> lazies = myObject.getLazies(); 
for(Lazy lazy : lazies) { // Exception 
    ... 
} 

Ich erhalte die folgende Fehlermeldung:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.stackoverflow.MyObject.lazies, could not initialize proxy - no Session 

So sieht es aus wie mir (Hibernate Neuling), dass der neue Thread keine Sitzung mit diesen selbst erstellten Threads hat, aber Spring Data automatisch neue Sitzungen für HTTP-Anforderungs-Threads erstellt.

  • Gibt es eine Möglichkeit, eine neue Sitzung manuell innerhalb der Sitzung zu starten?
  • Oder eine Möglichkeit, den Thread-Pool zu sagen, es für mich zu tun?
  • Was ist die Standard-Praxis für diese Art von Arbeit?

Ich habe in der Lage gewesen, um es an der Arbeit ein wenig von allem zu tun, aus dem Innern einer @Transactional Methode, aber ich lerne schnell, das ist nicht eine sehr gute Lösung, da dies nicht ich Methoden nicht verwenden lassen, die funktionieren Gut für Web-Anfragen.

Danke.

Antwort

0

Was passiert, ist wahrscheinlich, dass Sie Transaktion auf Ihrem DAO Code haben und Spring schließt die Sitzung beim Schließen der Transaktion.

Sie sollten alle Ihre Geschäftslogik in eine einzige Transaktion quetschen.

Sie können SessionFactory in Ihren Code injizieren und SessionFactory.openSession() Methode verwenden.
Das Problem ist, dass Sie Ihre Transaktionen verwalten müssen.

+0

Ein Teil des Problems ist, ich möchte in der Lage sein, den Status des Jobs zu aktualisieren, während es ausgeführt wird. Wenn ich also alles in eine einzige Transaktion einpacke, wird der Status erst aktualisiert, wenn die äußerste Commit-Anweisung abgeschlossen ist. Es sei denn, ich vermisse etwas hier ... was völlig möglich ist :) – Joel

+0

Könnten Sie Ihre Runnable-Implementierungsklasse und vielleicht Ihr ObjectRepository zeigen? Ich bin auch ein bisschen neugierig, wie und wo Sie Statusupdates veröffentlichen möchten. Es ist nicht so offensichtlich mit http-basierten Web-Anwendungen. – alobodzk

+0

Speziell für die Aktualisierung des Prozessstatus: Verwenden Sie eine verschachtelte Transaktion. Stellen Sie sicher, dass Ihr JPA-Anbieter sie unterstützt. – Virmundi

19

Mit Spring brauchen Sie keinen eigenen Executor. Eine einfache Anmerkung @Async wird die Arbeit für Sie erledigen. Kommentieren Sie einfach Ihren heavyMethod in Ihrem Service 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 vermuteten, aus dem 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 für mich selbst nicht @Async und @Transactional fusionieren, so kann ich den Dienst auf jede Art und Weise ausführen. Ich erstelle nur einen asynchronen Wrapper um den Service herum und benutze diesen stattdessen, falls nötig. (Dies vereinfacht das Testen zum Beispiel)

@Service 
public class AsyncService { 

    @Autowired 
    private Service service; 

    @Async 
    public void doAsync(int entityId) { 
     service.doHeavy(entityId); 
    } 
} 

@Service 
public class Service { 

    @PersistenceContext 
    private EntityManager em; 

    @Transactional 
    public void doHeavy(int entityId) { 
     // some long running work 
    } 
} 
+0

Diese Technik hat perfekt für meine ältere Spring Roo 1.3.x-Anwendung funktioniert. Ich musste die Legacy-App verbessern und es funktionierte wie ein Zauber. Vielen Dank!! – sunitkatkar

-1

Methode # 1: JPA Entity-Manager

In Hintergrund-Thread: entity Manager injizieren oder aus Spring Kontext bekommen oder es als einen Verweis:

@PersistenceContext 
private EntityManager entityManager;  

Dann eine neue Entität Manager erstellen, zu vermeiden, dass eine geteilte Verantwortung mit:

EntityManager em = entityManager.getEntityManagerFactory().createEntityManager(); 

Jetzt können Sie Transaktion und verwenden Frühling DAO, Repository, JPA, etc

private void save(EntityManager em) { 

    try 
    {   
     em.getTransaction().begin();     

     <your database changes> 

     em.getTransaction().commit();       
    } 
    catch(Throwable th) { 
     em.getTransaction().rollback(); 
     throw th; 
    }   
} 

Methode # 2 starten: JdbcTemplate

Falls Sie Low-Level-Änderungen oder Ihre Aufgabe ist einfach genug brauchen, Sie können es mit JDBC und Abfragen manuell tun:

@Autowired 
private JdbcTemplate jdbcTemplate; 

und dann irgendwo in Ihrer Methode:


Randbemerkung: Ich würde empfehlen, von @Transactional zu, wenn Sie JTA verwendet oder sie auf JpaTransactionManager.

+0

Das stimmt nicht! @Transactional benötigt kein JTA. Ein Transaktionsmanager kann auch ein org.springframework.orm.jpa.JpaTransactionManager sein, der den EntityManager zur Abwicklung der Transaktionen verwendet! –

+0

Vielen Dank für das Feedback. Ich habe jetzt die Empfehlung über @Transactional präzisiert. Es beeinflusst nicht den Inhalt und die Korrektheit der Arbeitsempfehlungen, wie das Problem in einem Hintergrundthread zu behandeln ist. – alexzender

+0

Ich bin immer noch nicht einverstanden, weil die Handhabung Ihrer eigenen Transaktion mühsam sein kann. In Ihrem Beispiel beim Aufruf von em.getTransaction(). Rollback(); Die Transaktion kann inaktiv sein. Das musst du überprüfen! –

Verwandte Themen