2016-04-06 10 views
8

La mia applicazione carica un elenco di entità che devono essere elaborate. Questo accade in una classe che utilizza uno schedulerDevo passare un'entità gestita a un metodo che richiede una nuova transazione?

@Component 
class TaskScheduler { 

    @Autowired 
    private TaskRepository taskRepository; 

    @Autowired 
    private HandlingService handlingService; 

    @Scheduled(fixedRate = 15000) 
    @Transactional 
    public void triggerTransactionStatusChangeHandling() { 
     taskRepository.findByStatus(Status.OPEN).stream() 
           .forEach(handlingService::handle); 
    } 
} 

Nei miei HandlingService processi ogni attività nel issolation usando REQUIRES_NEW per il livello di propagazione.

@Component 
class HandlingService { 

    @Transactional(propagation = Propagation.REQUIRES_NEW) 
    public void handle(Task task) { 
     try { 
      processTask(task); // here the actual processing would take place 
      task.setStatus(Status.PROCCESED); 
     } catch (RuntimeException e) { 
      task.setStatus(Status.ERROR); 
     } 
    } 
} 

Il codice funziona solo perché ho iniziato la transazione padre sul TaskScheduler classe. Se rimuovo l'annotazione @Transactional le entità non sono più gestite e l'aggiornamento all'entità dell'attività non viene propagato al db.Non trovo naturale rendere transazionale il metodo pianificato.

Da quello che vedo ho due opzioni:

1. Mantenere il codice come lo è oggi.

  • Forse è solo io e questo è un corretto approccio.
  • Questa variante ha il minor numero di viaggi nel database.

2. Rimuovere il @Transactional annotazione dal Scheduler, passare l'ID del compito e ricaricare l'entità compito nel HandlingService.

@Component 
class HandlingService { 

    @Autowired 
    private TaskRepository taskRepository; 

    @Transactional(propagation = Propagation.REQUIRES_NEW) 
    public void handle(Long taskId) { 
     Task task = taskRepository.findOne(taskId); 
     try { 
      processTask(task); // here the actual processing would take place 
      task.setStatus(Status.PROCCESED); 
     } catch (RuntimeException e) { 
      task.setStatus(Status.ERROR); 
     } 
    } 
} 
  • ha più viaggi al database (una query extra/elemento)
  • può essere eseguito utilizzando @Async

Per favore, puoi offrire la tua opinione su quale sia il modo corretto di affrontare questo tipo di problemi, magari con un altro metodo di cui non ero a conoscenza?

risposta

9

Se l'intenzione è di elaborare ogni attività in una transazione separata, il primo approccio in realtà non funziona perché tutto è impegnato alla fine della transazione dello scheduler.

Il motivo è che nelle transazioni nidificate le istanze Task sono sostanzialmente entità distaccate (le transazioni nidificate avviate (Session non sono a conoscenza di tali istanze). Alla fine della transazione dello scheduler, Hibernate esegue un controllo sporco sulle istanze gestite e sincronizza le modifiche con il database.

Questo approccio è anche molto rischioso, perché potrebbero esserci problemi se si tenta di accedere a un proxy non inizializzato su un'istanza Task nella transazione nidificata. E ci possono essere problemi se si modifica il grafico dell'oggetto Task nella transazione nidificata aggiungendovi un'altra istanza di entità caricata nella transazione nidificata (poiché tale istanza verrà ora scollegata quando il controllo ritorna alla transazione dello schedulatore).

D'altra parte, il tuo secondo approccio è corretto e diretto e aiuta ad evitare tutte le insidie ​​sopra. Solo, vorrei leggere gli id ​​e commettere la transazione (non è necessario tenerlo sospeso mentre le attività sono in fase di elaborazione).Il modo più semplice per ottenerlo è rimuovere l'annotazione Transactional dallo scheduler e rendere transazionale il metodo del repository (se non è già transazionale).

Se (e solo se) le prestazioni del secondo approccio sono un problema, come già accennato si potrebbe andare con l'elaborazione asincrona o addirittura parallelizzare l'elaborazione in una certa misura. Inoltre, potresti dare un'occhiata allo extended sessions (conversazioni), forse potresti trovarlo adatto al tuo caso d'uso.

+0

In questo esempio, la cache delle entità di sessione della transazione nidificata verrà sincronizzata con la sessione della transazione esterna? Ad esempio, se l'entità "Attività" viene modificata all'interno della transazione nidificata, tale modifica verrà applicata anche alla sessione della transazione in uscita? – froi

+0

Seguito alla mia domanda, poiché la sessione della transazione esterna viene svuotata, significa che le modifiche all'attività verranno considerate come modifiche "obsolete"? – froi

1

Supponendo che processTask(task); è un metodo nella classe HandlingService (uguale handle(task) metodo), quindi rimuovendo @Transactional in HandlingService non funziona a causa del comportamento naturale di delega dinamica Spring.

Citando spring.io forum:

Quando Primavera carica la TestService è avvolgerlo con un proxy. Se si chiama un metodo di TestService al di fuori del TestService, il proxy verrà invocato e la transazione verrà gestita correttamente. Tuttavia, se si chiama il metodo transazionale in un metodo nello stesso oggetto, non si invocherà il proxy ma direttamente la destinazione del proxy e non si eseguirà il codice del servizio per gestire la transazione.

This is one of SO thread su questo argomento, e qui ci sono alcuni articoli su questo:

  1. http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html
  2. http://tutorials.jenkov.com/java-persistence/advanced-connection-and-transaction-demarcation-and-propagation.html
  3. http://blog.jhades.org/how-does-spring-transactional-really-work/

Se davvero non ti piace l'aggiunta di annotazioni @Transaction nel tuo @Scheduled incontrato hod, si potrebbe ottenere transazione da EntityManager e gestire transazioni a livello di codice, ad esempio:

UserTransaction tx = entityManager.getTransaction(); 
try { 
    processTask(task); 
    task.setStatus(Status.PROCCESED); 
    tx.commit(); 
} catch (Exception e) { 
    tx.rollback(); 
} 

ma dubito che si avrà in questo modo (beh, non lo vorrei). Alla fine,

Potete per favore offrire il vostro parere in merito che è il modo corretto di affrontare questo tipo di problemi

Non c'è corretto modo in questo caso. La mia opinione personale è, l'annotazione (ad esempio, @Transactional) è solo un indicatore ed è necessario un processore di annotazione (molla, in questo caso) per eseguire il lavoro @Transactional. L'annotazione non avrà alcun impatto senza il suo processore.

I Will più preoccupare, per esempio, il motivo per cui ho processTask(task) e task.setStatus(Status.PROCESSED); fuori dal vivo del processTask(task) se sembra che fa la stessa cosa, ecc

HTH.

+0

Anche se il metodo 'processTask' è un metodo privato all'interno di' HandlingService', tutti gli aggiornamenti dovrebbero essere propagati al db, perché una nuova transazione è stata aperta quando è stato chiamato il metodo 'handle' (sebbene sia un proxy Spring). Nella seconda variante ho proposto di rimuovere '@ Transctional' dallo scheduler e passò id al servizio di gestione (che aprirà le transazioni per ogni attività elaborata) – mvlupan

2

Il codice corrente elabora l'attività nella transazione nidificata, ma aggiorna lo stato dell'attività nella transazione esterna (poiché l'oggetto Attività è gestito dalla transazione esterna). Poiché si tratta di transazioni diverse, è possibile che uno abbia esito positivo mentre l'altro non riesce, lasciando il database in uno stato incoerente.In particolare, con questo codice, le attività completate rimangono nello stato aperto se l'elaborazione di un'altra attività genera un'eccezione o il server viene riavviato prima che tutte le attività siano state elaborate.

Come mostra il vostro esempio, il passaggio di entità gestite ad un'altra transazione rende ambigua quale transazione dovrebbe aggiornare queste entità, ed è quindi meglio evitare. Invece, dovresti passare id (o entità distaccate) ed evitare l'annidamento non necessario delle transazioni.

Problemi correlati