How to control a BMT transaction outside of the EJB container? - java

Just out of curiosity, is it possible to directly control an EJB transaction from the Web Container?
To illustrate I made this simple example initiating a UserTransaction in the Web Container(using a Servlet), but the transaction is not bound to the EJB Container (in this case a BMT SFSB).
Why is it? Is there a way to do it?
Stateful Session Bean using BMT
#Stateful
#TransactionManagement(TransactionManagementType.BEAN)
public class CustomerBean implements CustomerBeanLocal{
#PersistenceContext(type=PersistenceContextType.EXTENDED)
private EntityManager em;
#Override
public Integer createCustomer(String name) {
Customer customer = new Customer();
customer.setId(1);
customer.setName(name);
em.persist(customer);
//em.flush();
return customer.getId();
}
}
UserTransaction is initiated in the Servlet, but the Session Bean doesn't persist
The Customer is not persisted to the database.
public class BMTServlet extends HttpServlet {
#EJB
private CustomerBeanLocal customerBean;
#Resource
private UserTransaction userTransaction;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
userTransaction.begin();
customerBean.createCustomer("Tars");
userTransaction.commit();
} catch (Exception e) {
throw new ServletException(e);
}
}
}
If we uncomment the em.flush(); then we get the following exception:
javax.persistence.TransactionRequiredException: no transaction is in progress
org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:792)
org.jboss.ejb3.jpa.integration.JPA1EntityManagerDelegator.flush(JPA1EntityManagerDelegator.java:86)
bean.CustomerBean.createCustomer(CustomerBean.java:25)

BMT will not work in your scenario, as BMT bean will be handling transaction by itself and will not participate in the transaction started in the web module (the container transaction). To control transaction from servlet using UserTransaction, the bean must be CMT.

Related

Transactional CDI bean: How to force transaction rollback

I have a #RequestScoped, #Transactional CDI bean injected in my REST interface:
#RequestScoped
#Transactional
public class myRestCall
{
#Inject
EntityHandlerService ehs; // contains #PersistenceContext
try
{
// execute business logic, access DB via ehs (JPA / Hibernate)
}
catch(Throwable t)
{
// log exception
// -> rollback transaction
}
}
Now I like to have a try / catch around the call of the business logic that I can log the exception properly. But I need to rollback the transaction manually, unless I throw the exception again what I do not like. So how can I force the transasction rollback here? I know how to do it, if it would be a EJB: We could do
#Resource
private SessionContext ctx;
and then
ctx.setRollbackOnly();
in the catch close.
However, it is not an EJB and I cannot make an EJB out of it due to resource limits.
In case anyone is still looking for an answer, you can add the #Resource TransactionSynchronizationRegistry and then call setRollbackOnly();
#RequestScoped
class MyBean {
#Resource
private TransactionSynchronizationRegistry reg;
#Transactional
void yourmethod()
{
try {
// do stuff
} catch (Exception e) {
reg.setRollbackOnly();
}
}
}
If you can't use #Resource, you can lookup the trx resgistry with jndi:
java:comp/TransactionSynchronizationRegistry
You can include a parameter rollbackOn="Exception.class" asociated to the #Transactional annotation.
But if yo do so, you must quit the "try-catch" block

Is it possible to propagate a transaction through different EJB 3.1 and several database servers?

The problem is this, I have three EJB, the EJB_A have connection to the server database 1, the EJB_B have connection to the server database 2 and the third EJB_C which is responsible for calling the first two.
When there is an error in the EJB_A or EJB_B only rollback is done in the EJB where there is error, on the other EJB saved properly done, eventhough it should propagate the rollback to the two EJB.
EJB_C:
#Stateless
#LocalBean
#TransactionManagement( TransactionManagementType.BEAN )
public class EJB_C {
private EJB_A ejbA;
private EJB_B ejbB;
#PostConstruct
public void init(){
Context context = null;
try {
context = new InitialContext();
this.ayudanteSessionBean = (AyudanteSessionBean) context.lookup("java:global/Trans1/Trans1DBA/EJB_A");
this.ayudanteSessionBean = (AyudanteSessionBean) context.lookup("java:global/Trans1/Trans1DBA/EJB_B");
} catch (NamingException ex) {
Logger.getLogger(OrquestadorSessionBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void saveTwoEJBs(EntityA, a, EntityB b){
try{
ejbA.save(a);
ejbB.save(b);
}catch (Exception ex) {
}
}
}
EJB_A:
#Stateless
#LocalBean
public class EJB_A {
#PersistenceContext( unitName = "persistA" )
private EntityManager em;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void guardarAmigo(EntityA a){
em.persist(a);
}
}
EJB_B:
#Stateless
#LocalBean
public class EJB_B {
#PersistenceContext( unitName = "persistB" )
private EntityManager em;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void guardarAmigo(EntityB b){
em.persist(b);
}
}
Could you help me?
Well, to make it work you will need to setup distributed transactions (XA) properly:
Check that all datasources are XA-ready. Typically this means different jdbc driver class name and different type of connection pool during setup.
The process is vendor-specified, but always described in documentation of your application server.
You need to remove annotation:
#TransactionManagement( TransactionManagementType.BEAN )
to let your container (actually his transaction broker) to manage transactions instead of bean.
3. You need to add #TransactionAttribute annotation on method saveTwoEJBs :
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void saveTwoEJBs(EntityA, a, EntityB b)
It's not required but there is no reason to lookup EJB beans via JNDI context, just inject them via #EJB annotation:
#EJB
private EJB_A ejbA;
#EJB
private EJB_B ejbB;
Some links to go:
http://docs.oracle.com/cd/E13222_01/wls/docs100/ejb30/examples.html
http://docs.oracle.com/cd/E16439_01/doc.1013/e13981/servtran001.htm
http://entjavastuff.blogspot.ru/2011/02/ejb-transaction-management-going-deeper.html

rollback failed inside #ManagedBean class

I use Bean-managed transaction inside #ManagedBean class to manually manage transactions,
#ManagedBean(name = "clients")
#ViewScoped
#URLMapping(id = "sousc", pattern = "/ccf_sicavs_customers", viewId = "/customers/PgSouscripteurs.jsf")
#TransactionManagement(TransactionManagementType.BEAN)
public class SouscripteursBean extends Referentiel implements Serializable {
#Resource
private UserTransaction ut;
#EJB
private CustomerDaoLocal ejbCust;
public void createCustomer() {
try {
LOGGER.info("Debut de la transaction");
ut.begin();
LOGGER.info("begin() succeded");
currentMorale.setCodeSouscripteur("25");
saved=ejbCust.createCustomer(currentMorale);
ut.commit();
LOGGER.info("commit() succeded");
} catch (Exception e) {
saved=false;
try {
ut.rollback();
LOGGER.info("rollback() succeded");
} catch (Exception ex) {
LOGGER.log(Level.SEVERE,null,ex);
}
}
}
}
but if an exception is thrown thrown before the commit(), the rollback() failed
and throw this java.lang.IllegalStateException:
SEVERE: java.lang.IllegalStateException: Transaction is not active in the current thread.
at com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate.validateTransactionManager(JavaEETransactionManagerJTSDelegate.java:447)
at com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate.rollbackDistributedTransaction(JavaEETransactionManagerJTSDelegate.java:208)
at com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.rollback(JavaEETransactionManagerSimplified.java:899)
at com.sun.enterprise.transaction.UserTransactionImpl.rollback(UserTransactionImpl.java:234)
I think you have that exception because the transaction is outside the EJB Container scope.
JSF application is the EJB service client and the application is in WEB Container scope.
Normally you don't do any business logic in JSF managed bean. Managed beans are just controllers in the JSF ModelViewController architecture. The business logic should be done inside ejb's and inject them in JSF managed beans and only call the EJB methods with the functionality. Transactions, persistence, security etc should be done inside EJB.
Bottom line, the transaction should be done inside createCustomer() method from that EJB, not in managed bean.
Try that!

Best practice to get EntityManger and UserTransaction from JSP

I am currently trying to figure out the best way to get a entity manager and a usertransaction in my application.
In JBoss 5.1 I was able to inject it directly into the JSP file, but this is not permitted anymore:
<%!#PersistenceContext(unitName = "unitname")
public EntityManager em;
#Resource
UserTransaction utx;
%>
I have to access em and utx from different locations in my application such as Servlets and Controller classes. So it would be great to have it in one place and to access it globally, but I did not figure out yet how to do it. Any hint would be appreciated.
I found out how to get the EntityManager and the UserTransaction in Servlets, Controller Classes and JSP files.
Let's start with SessionBeans. I redefined all my controller classes as Stateless SessionBeans. Session Beans allow ressource injection. This is how I did it:
#Stateless
public class UserHandling {
#PersistenceContext(unitName = "SSIS2")
private static EntityManager em;
#Resource
private UserTransaction utx;
public User getUser(int userId) {
User userObject = em.find(User.class, userId);
return userObject;
}
}
If another Session Bean is needed in a Session Bean Class, it can be injected with the #EJB annotation:
#Stateless
public class UserHandling {
#PersistenceContext(unitName = "SSIS2")
private static EntityManager em;
#Resource
private UserTransaction utx;
#EJB
UserHandling uh; RoleHandling rh;
public User getUser(int userId) {
User userObject = em.find(User.class, userId);
return userObject;
}
}
In JSP files, you can get the Session Bean Controller classes by lookup the InitialContext:
<%
InitialContext ic = new InitialContext();
UserHandling uh = (UserHandling) ic.lookup("java:app/" + application.getContextPath() + "/UserHandling");
%>
The Issue being Solved
Servlets and JSPs must be stateless because they are shared across multiple threads. An EntityManager does keep state, and so a single instance cannot be shared by concurrent threads.
We'd like a smooth/seamless mechanism for obtaining an EntityManager, preferably managed by the Servlet container.
Servlet-Container Managed Persistence Context
Let's introduce a ContainerManagedPersistenceContext into the Servlet/JSP runtime.
We'll define it in a moment. Let's first look at how it can be used to inject an EntityManager into JSP:
<%! #Inject
#ContainerManagedPersistenceContext.Qualifier
public EntityManager em;
%>
or, better yet into a controller (because we do want to separate data recovery/business logic from our JSP, right?):
#Named
#SessionScoped
public class SessionController implements Serializable
{
...
#Inject
#ContainerManagedPersistenceContext.Qualifier
private EntityManager em;
}
But I don't (yet) have CDI available
If you don't have CDI, but you do have JSF, then the context can be injected as an old-style standard JSF #ManagedProperty:
#Named
#SessionScoped
public class SessionController implements Serializable
{
...
#ManagedProperty(value = "#{containerManagedPersistenceContext}")
ContainerManagedPersistenceContext cmpContext;
...
public void myMethod() {
EntityManager em = cmpContext.getEntityManager();
try {
...
} finally {
em.close();
}
}
}
Remember that - for all the same reasons that we have to go to this effort in the first place - the EntityManager must never be cached/preserved anywhere.
Transactions
Use the EntityTransaction provided by the EntityManager for begin/commit/rollback:
EntityTransaction transaction = em.getTransaction();
ContainerManagedPersistenceContext
This is defined as an application scoped controller and a PersistenceContext:
#PersistenceContext(name = ContainerManagedPersistenceContext.NAME,
unitName = ContainerManagedPersistenceContext.UNIT_NAME)
#ApplicationScoped
public class ContainerManagedPersistenceContext implements Serializable
{
private static final long serialVersionUID = 1L;
// UNITNAME must match persistence.xml: <persistence-unit name="myUnitName">
public static final String UNITNAME = "myUnitName";
public static final String NAME = "persistence/" + UNIT_NAME;
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD,
ElementType.PARAMETER, ElementType.TYPE})
public static #interface Qualifier { }
// Servlets must be stateless (shared across multiple threads).
// EntityManager is not stateless (cannot be shared across threads).
// Obtain Container Managed EntityManager - and do NOT cache.
#Produces #Qualifier
public static EntityManager getEntityManager() throws NamingException
{
EntityManager lookup = InitialContext.doLookup("java:comp/env/" + NAME);
return lookup;
}
}
Limitations
As written, this defines a specifically named PersistenceContext for the Servlet container. Since the unitName isn't parameterized, it doesn't provide the level of flexibility as:
#PersistenceContext(unitName = "unitname")
public EntityManager em;
Alternatives
Define a PersistenceContext on your Servlet, and use JNDI name lookup.
Well i think you should see the problem from a different point of view?
Why do you need to call an EJB from JSP page?
JSP page shouldn't contain codes and it is used only for presentation.
I suggest to you to add a Servlet or a JSF framework and let the Servlet or the ManagedBean to call EJB and then pass the parameters to the JSP.
Hope it helps you
You can use the following snippet to retrieve EntityManager and/or UserTransaction via JNDI lookup:
try {
Context ic = (Context) new InitialContext();
EntityManager em = (EntityManager) ic.lookup("java:comp/env/*<persistence-context-name>*");
UserTransaction ut = (UserTransaction) ic.lookup("java:comp/env/UserTransaction");
} catch (NamingException ne) {...}

Best practice to get EntityManagerFactory

What is the best approach to get EntityManagerFactory in web app(jsp/servlets)?
Is this a good way When should EntityManagerFactory instance be created/opened?,
or is it better to get it from JNDI, or something else?
They're heavyweight and they're supposed to be in the application scope. So, you need to open them on application startup and close them on application shutdown.
How to do that depends on your target container. Does it support EJB 3.x (Glassfish, JBoss AS, etc)? If so, then you don't need to worry about opening/closing them (neither about transactions) at all if you just do the JPA job in EJBs with #PersistenceContext the usual way:
#Stateless
public class FooService {
#PersistenceContext
private EntityManager em;
public Foo find(Long id) {
return em.find(Foo.class, id);
}
// ...
}
If your target container doesn't support EJBs (e.g. Tomcat, Jetty, etc) and an EJB add-on like OpenEJB is also not an option for some reason, and you're thus manually fiddling with creating EntityManagers (and transactions) yourself, then your best bet is a ServletContextListener. Here's a basic kickoff example:
#WebListener
public class EMF implements ServletContextListener {
private static EntityManagerFactory emf;
#Override
public void contextInitialized(ServletContextEvent event) {
emf = Persistence.createEntityManagerFactory("unitname");
}
#Override
public void contextDestroyed(ServletContextEvent event) {
emf.close();
}
public static EntityManager createEntityManager() {
if (emf == null) {
throw new IllegalStateException("Context is not initialized yet.");
}
return emf.createEntityManager();
}
}
(note: before Servlet 3.0, this class needs to be registered by <listener> in web.xml instead of #WebListener)
Which can be used as:
EntityManager em = EMF.createEntityManager();
// ...

Categories