-
1. Re: Transaction for normal beans
wujek Mar 5, 2010 5:17 PM (in response to adamw)Hi,
you might find these links helpful:
http://smokeandice.blogspot.com/2009/12/cdi-and-declarative-transactions.html
http://smokeandice.blogspot.com/2009/12/jsr-299-tx-interceptor-code.html
http://relation.to/Bloggers/OnCDIInterceptorBindings
http://relation.to/Bloggers/MoreOnCDIInterceptorBindingsIt is very informative read by Matt Corey and Gavin King.
Regards,
Wujek -
2. Re: Transaction for normal beans
adamw Mar 5, 2010 5:47 PM (in response to adamw)But I wanted to have @TransactionRequired (or equivalent) by default, like in EJB3 or Seam. But looks like it's not possible. Well, maybe except writing a Filter, though I was wondering if there's a CDI-solution.
Adam
-
3. Re: Transaction for normal beans
pmuir Mar 9, 2010 11:39 AM (in response to adamw)Adam, want to help out on the Persistence/Transactions module for Seam 3 where we will implement this sort of thing? Email me if you are interested.
-
4. Re: Transaction for normal beans
adamw Mar 9, 2010 4:43 PM (in response to adamw)Sure, why not :)
So far I found a problem with the Filter approach, namely that if an extended persistence context is used (e.g. in long-running conversations and postbacks), the EM doesn's join the transaction; moreover if I inject the EM in the filter, it's a different one, so callling joinTransaction there has no effect.
I investigated a bit and found it's normally done with a PhaseListener. And I think this won't change much in Seam3? (At least according to http://seamframework.org/Documentation/Seam3FacesModuleDesignNotes).
Looked in the repo, but I guess there's not much there yet :).
Adam
-
5. Re: Transaction for normal beans
swd847 Mar 9, 2010 11:03 PM (in response to adamw)The old ManagedPeristenceContext used the manager component pattern with @Unwrap to manage the entityManager. Every time you injected the entityManager it would check to make sure that the em was enlisted in the transaction.
I am not sure what the best way to do something similar is in weld, possibly decorate the EM with a decorator that makes sure it is enlisted every time? -
6. Re: Transaction for normal beans
adamw Mar 11, 2010 10:26 AM (in response to adamw)I see. Weld has a different injection model and I'm not sure @Unwrap can be easily simulated. But here's what I have that seems to work fine (so far at least :) ):
1. Transactions are started/stopped in a phase listener. There's 1 tx for a GET and 2 txs for a POST (one thing I haven't yet checked is how this behaves with AJAX):
package util; import javax.faces.FacesException; import javax.faces.context.FacesContext; import javax.faces.event.*; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.transaction.Status; import javax.transaction.UserTransaction; /** * 1 transaction for GET: (before RESTORE_VIEW, after RENDER_RESPONSE) * 2 transactions for POST: (before RESTORE_VIEW, after INVOKE_APPLICATION), (before RENDER_RESPONSE, after RENDER_RESPONSE) * @author Adam Warski (adam at warski dot org) */ public class TransactionPhaseListener implements PhaseListener { @Override public void beforePhase(PhaseEvent event) { // Always starting before RESTORE_VIEW if (PhaseId.RESTORE_VIEW.equals(event.getPhaseId())) { startTransaction(event.getFacesContext()); } // Starting before RENDER_RESPONSE in case this is not a postback if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()) && !event.getFacesContext().isPostback()) { startTransaction(event.getFacesContext()); } } @Override public void afterPhase(PhaseEvent event) { // Always commiting after RENDER_RESPONSE if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId())) { commitTransaction(event.getFacesContext()); } // Commiting after INVOKE_APPLICATION in case of a postback if (PhaseId.INVOKE_APPLICATION.equals(event.getPhaseId()) && event.getFacesContext().isPostback()) { commitTransaction(event.getFacesContext()); } } @Override public PhaseId getPhaseId() { return PhaseId.ANY_PHASE; } private UserTransaction getUserTransaction() throws NamingException { return (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); } private static final String STARTED_TX_KEY = "_started_tx_"; private void startTransaction(FacesContext facesContext) { try { UserTransaction utx = getUserTransaction(); if (utx.getStatus() != Status.STATUS_ACTIVE) { utx.begin(); facesContext.getAttributes().put(STARTED_TX_KEY, new Object()); } } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new FacesException(e); } } private void commitTransaction(FacesContext facesContext) { try { UserTransaction utx = getUserTransaction(); if (utx.getStatus() == Status.STATUS_ACTIVE && facesContext.getAttributes().containsKey(STARTED_TX_KEY)) { utx.commit(); facesContext.getAttributes().remove(STARTED_TX_KEY); } } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new FacesException(e); } } }
2. You need to add the phase listener to faces-config.xml:
<lifecycle> <phase-listener>util.TransactionPhaseListener</phase-listener> </lifecycle>
3. The EntityManager is managed by a producer. There's a wrapper for the EM which ensures that the EM is enlisted in a transaction before any operation is done:
package util; import javax.enterprise.inject.Produces; import javax.persistence.*; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.metamodel.Metamodel; import java.util.Map; /** * @author Adam Warski (adam at warski dot org) */ public class EntityManagerProducer { @PersistenceContext private EntityManager entityManager; @Produces public EntityManager getEntityManager() { entityManager.joinTransaction(); return new EntityManagerTxWrapper(entityManager); } public static class EntityManagerTxWrapper implements EntityManager { private EntityManager delegate; private EntityManagerTxWrapper(EntityManager delegate) { this.delegate = delegate; } public void persist(Object entity) { delegate.joinTransaction(); delegate.persist(entity); } // etc. for all methods } }
4. You can then inject the EM freely:
@Inject private EntityManager em;
Adam
-
7. Re: Transaction for normal beans
adamw Mar 11, 2010 10:59 AM (in response to adamw)Of course the TX should be started before render response on a postback, not when the request is not a postback.
A corrected version below:
package util; import javax.faces.FacesException; import javax.faces.context.FacesContext; import javax.faces.event.*; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.transaction.Status; import javax.transaction.UserTransaction; /** * 1 transaction for GET: (before RESTORE_VIEW, after RENDER_RESPONSE) * 2 transactions for POST: (before RESTORE_VIEW, after INVOKE_APPLICATION), (before RENDER_RESPONSE, after RENDER_RESPONSE) * @author Adam Warski (adam at warski dot org) */ public class TransactionPhaseListener implements PhaseListener { @Override public void beforePhase(PhaseEvent event) { // Always starting before RESTORE_VIEW if (PhaseId.RESTORE_VIEW.equals(event.getPhaseId())) { startTransaction(event.getFacesContext()); } // Starting before RENDER_RESPONSE in case this is a postback if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()) && event.getFacesContext().isPostback()) { startTransaction(event.getFacesContext()); } } @Override public void afterPhase(PhaseEvent event) { // Always commiting after RENDER_RESPONSE if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId())) { commitTransaction(event.getFacesContext()); } // Commiting after INVOKE_APPLICATION in case of a postback if (PhaseId.INVOKE_APPLICATION.equals(event.getPhaseId()) && event.getFacesContext().isPostback()) { commitTransaction(event.getFacesContext()); } } @Override public PhaseId getPhaseId() { return PhaseId.ANY_PHASE; } private UserTransaction getUserTransaction() throws NamingException { return (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); } private static final String STARTED_TX_KEY = "_started_tx_"; private void startTransaction(FacesContext facesContext) { try { UserTransaction utx = getUserTransaction(); if (utx.getStatus() != Status.STATUS_ACTIVE) { utx.begin(); facesContext.getAttributes().put(STARTED_TX_KEY, new Object()); } } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new FacesException(e); } } private void commitTransaction(FacesContext facesContext) { try { UserTransaction utx = getUserTransaction(); if (utx.getStatus() == Status.STATUS_ACTIVE && facesContext.getAttributes().containsKey(STARTED_TX_KEY)) { utx.commit(); facesContext.getAttributes().remove(STARTED_TX_KEY); } } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new FacesException(e); } } }
Adam
-
8. Re: Transaction for normal beans
pmuir Apr 9, 2010 7:53 PM (in response to adamw)Adam, this generally looks very good. I would comment that the entity manager enlister should probably be a decorator, not a producer as this applies the pattern to an existing entity manager (which the user will likely want to produce themselves to give themselves control over the unit name etc.)
I think you are working with Dan on getting this committed?
-
9. Re: Transaction for normal beans
adamw Apr 11, 2010 5:44 PM (in response to adamw)
Pete Muir wrote on Apr 09, 2010 19:53:
Adam, this generally looks very good. I would comment that the entity manager enlister should probably be a decorator, not a producer as this applies the pattern to an existing entity manager (which the user will likely want to produce themselves to give themselves control over the unit name etc.)Ah, right. However, it seems that if a bean is produced, decorators aren't applied to it? I tried this both with EntityManager and a simple
Weld SE
example, and it doesn't work:public class EntityManagerProducer { @PersistenceContext private EntityManager entityManager; @Produces public EntityManager getEntityManager() { return entityManager; } }
@Decorator public class EntityManagerTransactionDecorator implements EntityManager { @Inject @Delegate @Any private EntityManager delegate; public void persist(Object entity) { delegate.joinTransaction(); delegate.persist(entity); } // ...
Is it by specification? Are decorators only applied to
top-level
beans?I already asked a similar question for interceptors, and it turned out that they can't be applied to produced beans.
I think you are working with Dan on getting this committed?Yes, I'll contact him and work on this :)
Adam
-
10. Re: Transaction for normal beans
pmuir Apr 12, 2010 12:34 PM (in response to adamw)
Adam Warski wrote on Apr 11, 2010 17:44:
Pete Muir wrote on Apr 09, 2010 19:53:
Adam, this generally looks very good. I would comment that the entity manager enlister should probably be a decorator, not a producer as this applies the pattern to an existing entity manager (which the user will likely want to produce themselves to give themselves control over the unit name etc.)
Ah, right. However, it seems that if a bean is produced, decorators aren't applied to it? I tried this both with EntityManager and a simpleWeld SE
example, and it doesn't work:Ah, yes, good point, so producer it is.
-
11. Re: Transaction for normal beans
dan.j.allen Apr 15, 2010 1:28 AM (in response to adamw)
Adam Warski wrote on Apr 11, 2010 17:44:
Pete Muir wrote on Apr 09, 2010 19:53:
I think you are working with Dan on getting this committed?
Yes, I'll contact him and work on this :)
AdamI'm on it. I'll pull in Lincoln as well since he is Mr. Faces ;)