Skip to main content

EntityManager.persist() throws TransactionRequiredException in a servlet?

Posted by ss141213 on December 5, 2005 at 9:37 AM PST

In my last blog I discussed about using Java Persistence API in a web application. In this article I shall talk about a very common mistake that a web-app developer commits and how to fix it. Java Persistence API is part of Java EE 5 platform which is being reference implemented in open source project called glassfish.

Code that does not work:
Given below is the code snippet of a servlet which uses an entity bean called UserCredential using a container managed EntityManager.

public class RegistrationServlet extends HttpServlet {
    // This injects the default persistence unit.
    @PersistenceUnit private EntityManagerFactory emf;
    public void service (HttpServletRequest req , HttpServletResponse resp)
                    throws ServletException, IOException {
        try {
            resp.setContentType("text/html");
            PrintWriter out = resp.getWriter();
            ...
            String name = req.getParameter("name");
            String password = req.getParameter("password");
            UserCredential credential = new UserCredential(name, password);
            EntityManager em = emf.getEntityManager(); // container managed em
            // em.persist makes the new object as persistent and managed.
            em.persist(credential);
            out.println("Successfully created the new user. ");
        } catch (Exception nse) {
            throw new ServletException(nse);
        }
    }
    // other servlet methods like init etc. are omitted for bravity.
}

Exception Details:
When the service() method gets executed, this servlet gets the following exception:
javax.persistence.TransactionRequiredException:
Exception Description: Error marking externally managed transaction for rollback

Why this exception occurs?
If you refer to persist() method in EntityManager.java you can see the javadocs clearly mention that persist throws javax.persistence.TransactionRequiredException if there is no transaction. Since we did not start a transaction in the service() method in servlet before calling em.persist(), the servlet got the exception.

What is the fix?
To fix this, update the servlet code as given below (new code is in bold face letter):

public class RegistrationServlet extends HttpServlet {
    // This injects the default persistence unit.
    @PersistenceContext private EntityManagerFactory emf;
    // This injects a user transaction object.
    @Resource private UserTransaction utx; 
    public void service (HttpServletRequest req , HttpServletResponse resp)
                    throws ServletException, IOException {
        try {
            resp.setContentType("text/html");
            PrintWriter out = resp.getWriter();
            ...
            String name = req.getParameter("name");
            String password = req.getParameter("password");
            // we must begin a tx.
            utx.begin();
            UserCredential credential = new UserCredential(name, password);
            EntityManager em = emf.getEntityManager(); // container managed em
            // em.persist makes the new object as persistent and managed.
            em.persist(credential);
            // commit the transaction,
            // b'cos web container rollbacks unfinished tx at the end of request
            utx.commit();
            out.println("Successfully created the new user. ");
        } catch (Exception nse) {
            throw new ServletException(nse);
        }
    }
    // other servlet methods like init etc. are omitted for bravity.
}

Comparison with equivalent code in a Session Bean
Let's compare the difference in behavior between a servlet and an ejb.
Given below is a stateless session bean that has a business interface called PasswordManager which has a method of type "void createNewUser(String, String)".

@Stateless
class PasswordManagerBean implements PasswordManager {
    @PersistenceContext private EntityManager em;
    public void createNewUser(String name, String password) {
            UserCredential credential = new UserCredential(name, password);
            // em.persist makes the new object as persistent and managed.
            em.persist(credential);
    }
}

Let's analyse the differences between ejb and servlet code:
Of course ejb code does not have the HttpResponse related code. The other significant difference is that ejb uses @PersistenceContext to get hold of an EntityManager where as the servlet uses @PersistenceUnit to get hold of an EntityManagerFactory and then calls getEntityManager() on the injected EntityManagerFactory to get hold of an EntityManager. The reason why the servlet does not use @persistenceContext is discussed here. That's not the point I am trying to make here. See that the entity creation code and EntityManager interaction code is exacty same as it was in the original servlet code. Yet the servlet gets the exception where as the ejb works. You are wondering why?
For the EJB, default transaction management type is CONTAINER and default transaction attribute for a business method is REQUIRED. So even though we did not specify transaction attributes in our bean class, defaults took over. So even if the business method does not start a transaction, the EJB container starts a transaction if the business method is called in null transaction context and ends it at the end of business method. Hence em.persist() works. For servlet, web container does not implicitly starts a transaction. The web container is only required to roll back a transaction if service() method leaves behind an unfinished transaction.
Hope this helped!
Technorati Tags:
More blogs about glassfish.

Related Topics >>