2014-07-23 10 views
8

Sto creando una semplice webapp Tomcat che utilizza Spring Data e Hibernate. C'è un punto finale che fa molto lavoro, quindi voglio scaricare il lavoro su un thread in background in modo che la richiesta web non si blocchi per 10+ minuti mentre il lavoro è in corso. Così ho scritto un nuovo servizio in un pacchetto di componenti-scan'd:Come faccio a fare correttamente un thread in background quando si usano Spring Data e Hibernate?

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

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

quindi avere la ThreadPoolTaskExecutor configurato in primavera:

<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> 

Questo è tutto grande lavoro. Tuttavia, il problema deriva da Hibernate. All'interno del mio runnable, le query solo a metà lavoro. Posso fare:

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

Ma se devo campi caricati pigri, viene a mancare:

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

ottengo il seguente errore:

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

Quindi sembra che a me (Hibernate newbie) che il nuovo thread non ha una sessione su questi thread fatti in casa, ma Spring Data crea automaticamente nuove sessioni per i thread HTTP Request.

  • C'è un modo per avviare manualmente una nuova sessione all'interno della sessione?
  • O un modo per dire al pool di thread di farlo per me?
  • Qual è la pratica standard per fare questo tipo di lavoro?

Sono stato in grado di lavorare intorno ad esso un po 'facendo tutto da all'interno di un metodo @Transactional, ma sto imparando in fretta che non è una soluzione molto buona, come questo non mi lascia usare metodi che funzionano va bene per le richieste web.

Grazie.

risposta

0

Quello che succede è, probabilmente, hai una transazione sul tuo pezzo di codice DAO e Spring sta chiudendo la sessione alla chiusura della transazione.

È necessario spremere tutta la logica aziendale in un'unica transazione.

È possibile inserire SessionFactory nel codice e utilizzare il metodo SessionFactory.openSession().
Il problema è che dovrai gestire le tue transazioni.

+0

Parte del problema è che voglio essere in grado di aggiornare lo stato del lavoro mentre è in esecuzione. Quindi, se lo avvolgo tutto in un'unica transazione, lo stato non si aggiorna fino a quando non si commette il commit più esterno. A meno che non manchi qualcosa qui ... che è totalmente possibile :) – Joel

+0

Puoi mostrare la tua classe di esecuzione Runnable e forse il tuo ObjectRepository? Sono anche un po 'curioso di sapere come e dove intendi pubblicare aggiornamenti di stato. Non è così ovvio con le app web basate su http. – alobodzk

+0

In particolare per l'aggiornamento dello stato del processo: utilizzare una transazione nidificata. Assicurati che il tuo provider JPA li supporti. – Virmundi

19

Con Spring non è necessario il proprio esecutore. Una semplice annotazione @Async farà il lavoro per te. Annota semplicemente il tuo heavyMethod al tuo servizio con esso e restituisci void o un oggetto Future e otterrai un thread in background. Eviterei di usare l'annotazione asincrona a livello di controller, in quanto ciò creerebbe un thread asincrono nel executor del pool di richieste e potreste esaurire 'request acceptors'.

Il problema con la tua eccezione lazy viene come sospetti dal nuovo thread che non ha una sessione. Per evitare questo problema il tuo metodo asincrono dovrebbe gestire il lavoro completo. Non fornire entità precedentemente caricate come parametri. Il servizio può utilizzare un EntityManager e può anche essere transazionale.

I per me non si uniscono @Async e @Transactional così posso eseguire il servizio in entrambi i modi. Creo semplicemente wrapper asincrono per il servizio e uso questo, se necessario. (Ciò semplifica test per esempio)

@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

Questa tecnica ha funzionato perfettamente per la mia applicazione legacy Spring Roo 1.3.x. Ho dovuto migliorare l'app legacy e ha funzionato come un fascino. Grazie!! – sunitkatkar

-1

Metodo # 1: JPA Entity Gestore

In thread in background: Iniettare gestore di entità o scarica dal contesto Spring o farli come riferimento:

@PersistenceContext 
private EntityManager entityManager;  

quindi creare un nuovo gestore di entità, di evitare l'uso condiviso uno:

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

Ora è possibile avviare la transazione e l'uso molla DAO, Repository, JPA, ecc

private void save(EntityManager em) { 

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

     <your database changes> 

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

Metodo # 2: JdbcTemplate

Nel caso in cui avete bisogno di cambiamenti di basso livello o il vostro compito è abbastanza semplice, si può fare con JDBC e le query manualmente:

@Autowired 
private JdbcTemplate jdbcTemplate; 

e poi da qualche parte nel metodo:

jdbcTemplate.update("update task set `status`=? where id = ?", task.getStatus(), task.getId()); 

Nota a margine: mi sento di raccomandare di stare lontano da @Transactional a meno di utilizzare JTA o affidamento su JpaTransactionManager.

+0

Non è corretto! @Transactional non ha bisogno di JTA. Un gestore transazioni può anche essere un org.springframework.orm.jpa.JpaTransactionManager che utilizza EntityManager per gestire le transazioni! –

+0

Grazie per il feedback. Ho reso la raccomandazione su @Transactional più specifica ora. Non influenza il contenuto e la correttezza delle raccomandazioni di lavoro su come gestire il problema in un thread in background. – alexzender

+0

Ancora non sono d'accordo perché gestire la tua transazione può essere complicato. Nel tuo esempio quando chiami em.getTransaction(). Rollback(); la transazione può essere inattiva. Devi controllare questo! –

Problemi correlati