2008-09-19 18 views
26

Ho un qualcosa di simile senza stato di fagioli:EJB3 transazione Propagazione

@Stateless 
public class MyStatelessBean implements MyStatelessLocal, MyStatelessRemote { 
    @PersistenceContext(unitName="myPC") 
    private EntityManager mgr; 

    @TransationAttribute(TransactionAttributeType.SUPPORTED) 
    public void processObjects(List<Object> objs) { 
     // this method just processes the data; no need for a transaction 
     for(Object obj : objs) { 
      this.process(obj); 
     } 
    } 

    @TransationAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void process(Object obj) { 
     // do some work with obj that must be in the scope of a transaction 

     this.mgr.merge(obj); 
     // ... 
     this.mgr.merge(obj); 
     // ... 
     this.mgr.flush(); 
    } 
} 

La tipicamente utilizzo, allora è il client chiamerebbe processObjects (...), che in realtà non interagisce con il gestore di entità. Fa ciò che deve fare e chiama il processo (...) individualmente per ogni oggetto da elaborare. La durata del processo (...) è relativamente breve, ma processObjects (...) potrebbe impiegare molto tempo per eseguire tutto. Quindi non voglio che mantenga una transazione aperta. I do richiedono le singole operazioni del processo (...) per operare all'interno della propria transazione. Questa dovrebbe essere una nuova transazione per ogni chiamata. Infine, vorrei mantenere l'opzione aperta per il client per chiamare il processo (...) direttamente.

Ho provato un certo numero di diversi tipi di transazione: mai, non supportato, supportato (su processObjects) e richiesto, richiede nuovo (processo) ma ricevo TransactionRequiredException ogni volta che viene chiamato merge().

Sono stato in grado di farlo funzionare suddividendo i metodi in due fagioli differenti:

@Stateless 
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED) 
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 { 
    @EJB 
    private MyStatelessBean2 myBean2; 

    public void processObjects(List<Object> objs) { 
     // this method just processes the data; no need for a transaction 
     for(Object obj : objs) { 
      this.myBean2.process(obj); 
     } 
    } 
} 

@Stateless 
public class MyStatelessBean2 implements MyStatelessLocal2, MyStatelessRemote2 { 
    @PersistenceContext(unitName="myPC") 
    private EntityManager mgr; 

    @TransationAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void process(Object obj) { 
     // do some work with obj that must be in the scope of a transaction 

     this.mgr.merge(obj); 
     // ... 
     this.mgr.merge(obj); 
     // ... 
     this.mgr.flush(); 
    } 
} 

ma io sono ancora curioso di sapere se è possibile ottenere questo risultato in una classe. Mi sembra che il gestore delle transazioni funzioni solo a livello di bean, anche quando ai singoli metodi vengono fornite annotazioni più specifiche. Quindi, se contrassegno un metodo in modo da impedire che la transazione inizi a chiamare altri metodi all'interno della stessa istanza, non creerà una transazione, indipendentemente dal modo in cui vengono contrassegnati?

Utilizzo JBoss Application Server 4.2.1.GA, ma le risposte non specifiche sono benvenute/preferite.

risposta

24

Un altro modo per farlo è avere entrambi i metodi sullo stesso bean e avere un riferimento a @EJB! Qualcosa del genere:

// supposing processObjects defined on MyStatelessRemote1 and process defined on MyStatelessLocal1 
@Stateless 
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED) 
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 { 
    @EJB 
    private MyStatelessLocal1 myBean2; 

    public void processObjects(List<Object> objs) { 
     // this method just processes the data; no need for a transaction 
     for(Object obj : objs) { 
      this.myBean2.process(obj); 
     } 
    } 


    @TransationAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void process(Object obj) { 
     // do some work with obj that must be in the scope of a transaction 

     this.mgr.merge(obj); 
     // ... 
     this.mgr.merge(obj); 
     // ... 
     this.mgr.flush(); 
    } 
} 

In questo modo è in realtà 'forza' il metodo process() a cui accedere tramite la pila ejb delle deleghe, quindi prendendo la @TransactionAttribute in effetti - e mantenendo una sola classe. Accidenti!

+0

Ho solo pensato di passare alla discussione e, visto che ho fatto questa domanda, questo problema è emerso diverse volte (incidentalmente, il mio capo ha detto di aver trovato questa domanda via Google una volta mentre cercava di risolverlo da sé.) Abbiamo usato questa soluzione un certo numero di volte, quindi grazie ancora. –

+0

Penso che questa sia una buona domanda da sapere. Ti aspetti queste semantiche se mai dovessi fare qualcosa dal mondo EJB2, ma potrebbero sembrare un po 'straniere se hai lavorato solo in EJB3. Un altro modo per vedere le cose è che devi assicurarti che il metodo che stai chiamando sia chiamato * attraverso l'interfaccia EJB *. – cwash

0

Credo che abbia a che fare con la @TransationAttribute (TransactionAttributeType.Never) sul metodo di processObjects.

TransactionAttributeType.Never

http://docs.sun.com/app/docs/doc/819-3669/6n5sg7cm3?a=view

Se il client è in esecuzione all'interno di una transazione e richiama il metodo l'impresa del bean, il contenitore getta una RemoteException . Se il client non è associato a una transazione, il contenitore non avvia una nuova transazione prima di eseguire il metodo.

presumo che sei cliente il metodo processObjects dal codice client. Poiché probabilmente il tuo cliente non è associato a una transazione, la chiamata al metodo con TransactionAttributeType.Never è felice in primo luogo. Quindi si chiama il metodo processo da processObjects che pur avendo l'annotazione TransactionAttributeType.Required non era una chiamata al metodo bean e il criterio di transazione non viene applicato. Quando chiami unione ottieni l'eccezione perché non sei ancora associato a una transazione.

Provare a utilizzare TransactionAttributeType.Required per entrambi i metodi di bean per verificare se risolve il problema.

+0

A quanto ho capito, hai sentito il punto. Matt non vuole che gli oggetti di processo vengano eseguiti in una transazione (che sarebbe il caso se imposta TransactionAttributeType.Required su processObjects), desidera molte transazioni individuali per il metodo di processo. –

1

Matt, per quello che vale, sono arrivato esattamente alla tua stessa conclusione.

TransactionAttributeTypes vengono presi in considerazione solo quando si superano i confini di Bean. Quando si chiamano metodi nello stesso bean TransactionAttributeTypes non hanno alcun effetto, indipendentemente dal tipo di tipi inseriti nei metodi.

Per quanto posso vedere, non c'è nulla nella Spec. Persistenza EJB che specifica quale dovrebbe essere il comportamento in queste circostanze.

Ho anche sperimentato questo in JBoss. Farò anche un tentativo in Glassfish e ti farò sapere i risultati.

2

Penso che il problema è che ogni bean è avvolto in un proxy che controlla il comportamento della transazione. Quando chiami da un bean a un altro, stai passando attraverso il proxy di quel bean e il comportamento della transazione può essere modificato dal proxy.

Ma quando un bean chiama un metodo su se stesso con un attributo di transazione diverso, la chiamata non passa attraverso il proxy, quindi il comportamento non cambia.

+0

In questo modo, il bean potrebbe chiamarsi per ottenere quel comportamento, come se provenisse da un altro bean. – Loki

4

Matt, la domanda che si pone è piuttosto classica, penso che la soluzione di riferimento di Herval/Pascal sia accurata. C'è una soluzione più generale non menzionata qui.

Questo è un caso per le transazioni "utente" EJB. Dato che ci si trova in un bean di sessione, è possibile ottenere la transazione dell'utente dal contesto della sessione. Ecco come il vostro codice sarà con transazioni degli utenti:

// supposing processObjects defined on MyStatelessRemote1 and process defined on MyStatelessLocal1 
@Stateless 
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED) 
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 { 

    @Resource 
    private SessionContext ctx; 

    @EJB 
    private MyStatelessLocal1 myBean2; 

    public void processObjects(List<Object> objs) { 
     // this method just processes the data; no need for a transaction 
     for(Object obj : objs) { 
      this.myBean2.process(obj); 
     } 
    } 


    public void process(Object obj) { 

     UserTransaction tx = ctx.getUserTransaction(); 

     tx.begin(); 

     // do some work with obj that must be in the scope of a transaction 

     this.mgr.merge(obj); 
     // ... 
     this.mgr.merge(obj); 
     // ... 
     this.mgr.flush(); 

     tx.commit(); 
    } 
} 
+0

Non mi piace questa soluzione. Sconfigge uno dei vantaggi delle TX gestite dal contenitore EJB, ovvero riducendo la gestione TX manuale e il codice boilerplate associato. Non c'è bisogno di questo in questo caso. Basta seguire la risposta accettata, OPPURE, è possibile utilizzare il contesto di sessione in questo modo: ctx.getBusinessObject (MyStatelessLocal1.class) .process (obj); e avrai lo stesso effetto di iniettarti te stesso. – NBW

1

Nel caso qualcuno si imbatte in questo un giorno:

da evitare dipendenze circolari (che consente di riferimento di auto, per esempio) in JBoss usare l'annotazione 'IgnoreDependency' per esempio:

@IgnoreDependency @EJB stesso me stessoRef;

+0

Questa soluzione è stata una soluzione solo per JBoss. È andato via da AS6. – NBW

0

Ho avuto questi problemi di dipendenza circolare che Kevin ha menzionato. Tuttavia, l'annotazione proposta @IgnoreDependency è un'annotazione specifica di jboss e non vi è alcuna controparte in e.g Glassfish.

Poiché non funziona con il riferimento EJB predefinito, mi sono sentito un po 'a disagio con questa soluzione.

Pertanto, ho dato una possibilità alla soluzione di bluecarbon, iniziando così la transazione interna "a mano".

Oltre a questo, non vedo alcuna soluzione se non quella di implementare il processo interno() in un altro bean che è anche brutto perché vogliamo semplicemente disturbare il nostro modello di classe per tali dettagli tecnici.

+0

@IgnoreDependency non si applica più a JBoss, era una specie di soluzione alternativa e non è più necessaria a partire da AS6. Glassfish non ha mai avuto questo problema e non ha mai avuto bisogno di una simile soluzione. – NBW

1

Non l'ho ancora provato (sto per farlo), ma un'alternativa all'iniezione di auto-riferimento tramite l'annotazione @EJB è il metodo SessionContext.getBusinessObject(). Questo sarebbe un altro modo per evitare la possibilità di un riferimento circolare che fa saltare su di te - anche se almeno per l'iniezione di fagioli senza stato sembra funzionare.

Sto lavorando su un grande sistema in cui entrambe le tecniche sono utilizzate (presumibilmente da diversi sviluppatori), ma non sono sicuro di quale sia il modo "corretto" per farlo.

+0

In entrambi i casi va bene. – NBW