Where to close Entitymanager

Asked

Viewed 1,199 times

3

I am developing a Java application, and I am doubtful where to close the Entity Manager.

I have an abstract class AbstractService<T> which is extended by all entities of the system, and this class has the persistence methods.

In any Servlet, I instate one or more service classes (each entity class has a service class, for example the service class Category_DTO and its class of service CategoryServiceImpl), which contains queries of search, and at the same time extends AbstractService<T>

My doubt is the best place to close the entityManager, em.close().

I thought of closing in the very Servlet, at the end of it, using the instance of DocumentServiceImpl with dsi.closeTX(), but I found it a bit tricky (remembering that a Servlet can contain several service classes, and I would have to close the entitymanager of each one that extended AbstractService and created the entitymanager).

I will send a JSP, with an example code that is used in Servlets, where I will return all categories. At the end of JSP I am closing the entitymanager, but I believe that the correct thing would be to close in the persistence class and not in Servlet.

Follows the code:

Entity:

    package freedom.technology.domain.document;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

import freedom.technology.enus.CategoryType;

@Entity
@Table(name = "FRE_CL_GED_CATEGORY")
@NamedQueries({    
    @NamedQuery(name="Category_DTO.findAllCategories", query="SELECT a FROM Category_DTO a WHERE :search is null OR (a.id LIKE :search OR a.description LIKE :search) ORDER BY a.description ASC"),
    @NamedQuery(name="Category_DTO.findAllCategoriesDESC", query="SELECT a FROM Category_DTO a WHERE :search is null OR (a.id LIKE :search OR a.description LIKE :search) ORDER BY a.description DESC"),
    @NamedQuery(name="Category_DTO.findAllCategoriesByType", query="SELECT a FROM Category_DTO a WHERE (:search is null OR (a.id LIKE :search OR a.description LIKE :search)) AND a.type = :type ORDER BY a.description ASC"),
    @NamedQuery(name="Category_DTO.findAllCategoriesByTypeDESC", query="SELECT a FROM Category_DTO a WHERE (:search is null OR (a.id LIKE :search OR a.description LIKE :search)) AND a.type = :type ORDER BY a.description DESC"),
    @NamedQuery(name="Category_DTO.getNumberOfCategories", query="SELECT COUNT(*) FROM Category_DTO"),
    @NamedQuery(name="Category_DTO.getNumberOfCategoriesBySearchCondition", query="SELECT COUNT(*) FROM Category_DTO a WHERE :search is null OR (a.id LIKE :search OR a.description LIKE :search) ORDER BY a.id DESC"),
}) 


public class Category_DTO {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)   
    @Column(name="id")
    private Integer id;

    @Column(name="description")
    private String description;

    @Column(name="type")
    private CategoryType type;

    public Integer getId() {
        return id;
    }

    public String getDescription() {
        return description;
    }

    public CategoryType getType() {
        return type;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public void setType(CategoryType type) {
        this.type = type;
    }
}

Class that returns records based on a specific search:

package freedom.technology.service.document.impl;

import java.util.List;

import javax.persistence.TypedQuery;

import freedom.technology.domain.document.Category_DTO;
import freedom.technology.domain.document.Subject_DTO;
import freedom.technology.enus.CategoryType;
import freedom.technology.exception.ExceptionType;
import freedom.technology.exception.FREBDCoreException;
import freedom.technology.service.AbstractService;
import freedom.technology.service.document.api.CategoryService;

public class CategoryServiceImpl extends AbstractService<Category_DTO> implements CategoryService {

    @Override
    protected Class<Category_DTO> getEntityClass() {
        return Category_DTO.class;
    }

    @Override
    public List<Category_DTO> findAllCategories(Integer pageSize, Integer pageNumber, String searchCondition, String orderBy)
            throws FREBDCoreException {

        List<Category_DTO> result = null;

        if (pageSize == null || pageNumber == null){
            throw new FREBDCoreException(ExceptionType.NULLPOINTER, "Parametro null: pageSize/pageNumber"); 
        }       
        try {
            TypedQuery<Category_DTO> query = em.createNamedQuery("Category_DTO.findAllCategories", Category_DTO.class);
            if(orderBy.equals("DESC")){
                query = em.createNamedQuery("Category_DTO.findAllCategoriesDESC", Category_DTO.class);
            }

            if(searchCondition != null) {           
                query.setParameter("search", "%"+searchCondition+"%");
            } else {
                query.setParameter("search", null);             
            }

            query.setFirstResult(pageSize*pageNumber);
            query.setMaxResults(pageSize);
            result =  query.getResultList();
        } catch(Exception e) {
            e.printStackTrace();
            throw new FREBDCoreException(ExceptionType.NORESULT, "findAllCategories");
        }
        return result;  
    }

    @Override
    public List<Category_DTO> findAllCategoriesByType(Integer pageSize, Integer pageNumber, CategoryType type, String searchCondition, String orderBy)
            throws FREBDCoreException {

        List<Category_DTO> result = null;

        if (pageSize == null || pageNumber == null){
            throw new FREBDCoreException(ExceptionType.NULLPOINTER, "Parametro null: pageSize/pageNumber"); 
        }       
        try {
            TypedQuery<Category_DTO> query = em.createNamedQuery("Category_DTO.findAllCategoriesByType", Category_DTO.class);
            if(orderBy.equals("DESC")){
                query = em.createNamedQuery("Category_DTO.findAllCategoriesByTypeDESC", Category_DTO.class);
            }

            if(searchCondition != null) {           
                query.setParameter("search", "%"+searchCondition+"%");
            } else {
                query.setParameter("search", null);             
            }

            if(type != null) {          
                query.setParameter("type", type);
            } else {
                query.setParameter("type", null);               
            }

            query.setFirstResult(pageSize*pageNumber);
            query.setMaxResults(pageSize);
            result =  query.getResultList();
        } catch(Exception e) {
            e.printStackTrace();
            throw new FREBDCoreException(ExceptionType.NORESULT, "findAllCategoriesByType");
        }
        return result;  
    }

    @Override
    public int getNumberOfCategories() throws FREBDCoreException {
        int result;

        try {           
            TypedQuery<Long> query = em.createNamedQuery("Category_DTO.getNumberOfCategories", Long.class);
            result = query.getSingleResult().intValue();
        } catch(Exception e) {
            result = 0;
        }

        return result;
    }

    @Override
    public int getNumberOfCategories(String searchCondition)
            throws FREBDCoreException {

        int result;

        try {           
            TypedQuery<Long> query = em.createNamedQuery("Category_DTO.getNumberOfCategoriesBySearchCondition", Long.class);

            if(searchCondition != null) {           
                query.setParameter("search", "%"+searchCondition+"%");
            } else {
                query.setParameter("search", null);             
            }

            result = query.getSingleResult().intValue();
        } catch(Exception e) {
            result = 0;
        }

        return result;
    }

}

Persistence class:

package freedom.technology.service;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import freedom.technology.exception.ExceptionType;
import freedom.technology.exception.FREBDCoreException;
import freedom.technology.utils.PersisteUtils;

public abstract class AbstractService<T> implements GenericService<T> {

    private static EntityManagerFactory factory = PersisteUtils.getEntityManagerFactory();
    protected EntityManager em;
    protected abstract Class<T> getEntityClass();

    public AbstractService() {
        em = factory.createEntityManager();
    }

    public void beginTX(){
        em.getTransaction().begin();
    }

    public void commitTX(){
        em.getTransaction().commit();
    }

    public void closeTX(){
        em.close();     
    }

    public void rollback(){
        em.getTransaction().rollback();     
    }

    public T findById(Integer id){
        T result = null;

        try {
            em.getTransaction().begin(); 
            result = em.find(this.getEntityClass(), id);
            em.getTransaction().commit(); 
        } catch(Exception e) {
            e.printStackTrace();
        }

        return result;
    }

    public void persist(T entity) {
        try { 
            em.getTransaction().begin(); 
            em.persist(entity); 
            em.getTransaction().commit(); 
        } catch (Exception ex) {
            ex.printStackTrace(); 
            em.getTransaction().rollback(); 
        } 
    } 

    public void merge(T entity) {
        try {
            em.getTransaction().begin(); 
            em.merge(entity); 
            em.getTransaction().commit(); 
        } catch (Exception ex) {
            ex.printStackTrace(); 
            em.getTransaction().rollback(); 
        } 
    }  

    public void removeObject(T entity) {
        try { 
            em.getTransaction().begin(); 
            em.remove(entity); 
            em.getTransaction().commit(); 
        } catch (Exception ex) {
            ex.printStackTrace(); 
            em.getTransaction().rollback(); 
        }
    }
}

Jsp example, closing entityManager at the end:

<%@page import="freedom.technology.domain.document.Category_DTO"%>
<%@page import="freedom.technology.service.document.api.CategoryService"%>
<%@page import="freedom.technology.service.document.impl.CategoryServiceImpl"%>

<%@page import="java.util.List"%>

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>

<%

    CategoryService ds = new CategoryServiceImpl();
    List<Category_DTO> listCategory = ds.findAllCategories(100, 0, null, null);

    for(Category_DTO category : listCategory){

        out.print("Categorias:");
        out.print("<br><br>");
        out.print("Categoria: " + category);
        out.print("<br>");
    }

    ds.closeTX();
%>
</body>
</html>
  • Related tip, I suggest rethinking this class AbstractService, the way this hierarchy will grow and expand throughout your system, in the near future it will be a real nightmare.

2 answers

2


Separation of responsibilities

Most systems can be divided into some conceptual layers:

  • Interface or presentation: screens/forms/pages or service facade for external consumption.

  • Applying: receives requests from the user or from external systems and invokes business components to process requests.

  • Business: processing of requests.

  • Persistence: the database where everything is persisted.

If you can keep each code in place, it’s easier to figure out where to open and close connection to the database (or "session", or "context"... depending on the culture/platform/framework) and where to open and close the transaction.

Session management and database transaction

A database transaction serves to ensure the atomicity of an operation. That is, if the operation will change two records: either both will be changed successfully or none will be changed - if there is error in the change of the second, the change of the first.

Thinking about operation atomicity, it makes no sense for the transaction management to be within the class responsible for the persistence of a single entidde!

So an operation (or "business process") knows whether it requires a transaction or not: if it will change more than one record, it will want atomicity, then it will require transaction.

public void processo_de_negocio() {
    
    // Não ligo sobre transação. Uma transação com commit implícito já serve pra mim.
    // Neste caso aqui, se a transação estiver 
    // na classe de persistência deste único registro, 
    // parece ok.
    salvarRegistro(); 
}

public void processo_de_negocio2() {
    
    // Quero garantir que o salvamento dos dois registros seja atômico!
    // Então a transação não pode ser controlada pela classe 
    // de persistência de cada um dos registros.
    beginTransaction(); 
    
    salvarRegistro(); 
    salvarOutroRegistro();
    
    commitTransaction();
}

Very well. But business process 2 does not know if it will be part of another larger process and will need to participate in the same transaction. Then he shouldn’t be handling the transaction either.

So who should handle the transaction? The application layer should take care of the transaction:

public void post() {

    beginTransaction();
    
    processo_de_negocio();
    processo_de_negocio2();
    
    commitTransaction();
}

And the connection/session/entitymanager? Connection management is also the responsibility of the application layer:

public void post() {

    em = factory.createEntityManager();
    em.getTransaction().begin();
    
    processo_de_negocio(em);
    processo_de_negocio2(em);
    
    em.getTransaction().commit();
    em.close();
}

Of course frameworks make life easier and eventually we don’t need to keep passing connection/session/entitymanager per parameter, and using these frameworks we also don’t need to manage the transaction at hand.

But conceptually the idea is this and this is how frameworks work.

Your code

That put, let’s take a look at your code.

The class Abstractservice

Now, if the transaction management is function of the application layer, almost all the work done today by your class Abstractservice is in the wrong place.

If you pass transaction management to the right place, most of the methods that are on today Abstractservice are no longer necessary and the remaining methods you can pass to the Categoryserviceimpl, as they will have a single line each (examples: em.persist(entity);, return em.find(this.getEntityClass(), id);).

And since you’ll never have to represent Categoryserviceimpl in the form of Abstractservice, and not much code to reuse, this heritage is completely unnecessary.

The JSP page

The code there is also not respecting the definition of layers, as it mixes presentation and application logic.

If you want more ease of maintenance and reuse, you can create a class Servlet whose method doGet deliver this list of entities to the JSP page. And, in case business processing is required in the page’s Ubmit, the method doPost would try to get the session and manage the transaction, as in the example I showed above.

The treatment of exceptions

You are making mistakes. When they happen, your app will behave in an almost unpredictable and difficult to diagnose.

No use printing the exception in the log, the user needs to know that its operation did not occur successfully.

It’s no use, either, at catch, return zero when trying to read "how many records". Ali will only give error if there is something very wrong, as for example the table name has changed; cover up this error returning a confusing Zero will not help in diagnosing the problem.

Sometimes it makes sense an exception treatment in the lower layers but, in general, exception handling also belongs to the application layer.

Then you should remove most of the exception treatment you have today, and move to the application layer.

The Servlet (belonging to the application layer), would look like this after all:

public void post() {
    try {
        em = factory.createEntityManager();
        em.getTransaction().begin();
        
        processo_de_negocio(em);
        processo_de_negocio2(em);
        
        em.getTransaction().commit();
        em.close();
        
    } catch (Exception e) {
        // aqui, avisa o usuário de maneira amigável, 
        // e mostra ou registra a exceção no log
    }
}

A Finally would also be useful there to ensure the rollback and closure of the connection (or its return to the pool) immediately.

Final note: Once you understand these concepts, consider using frameworks to reduce the amount of code you need to write and concerns you need to have.

0

Good afternoon,

Thank you for the reply.

Based on studies I have undertaken and aiming to meet the needs of the software, I have found the following solution:

I managed Entitymanager instances and transactions through a javax.Servlet filter class, which will filter requests, instantiate entityManager and open transaction if necessary: (Securityfilter Implements Filter). -Before starting Servlet I create the in instance and open the transaction; -After finishing Servlet, the filter commits the transaction, and finalizes the entityManager; -In case of error, rollback at all, ensuring atomicity.

package freedom.technology.session;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import freedom.technology.service.PersistEngine;


@WebFilter("/*")
public class SecurityFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpSession session = httpRequest.getSession();
        UserSession userSession = (UserSession) (session.getAttribute("UserSession"));

        String url = httpRequest.getRequestURI();
        StringBuilder urlToRedirect = new StringBuilder("?");

        if ((userSession == null) && (url.startsWith("/FRE_BPM/page-wrapper"))) {

            //redirecionamento de url
            //documentos e tarefas enviadas por email, etc
            HashMap<String, String[]> params = (HashMap<String, String[]>) request.getParameterMap();
            Set<Entry<String, String[]>> setParams = params.entrySet();

            for (Iterator<Entry<String, String[]>> it = setParams.iterator(); it.hasNext(); ) {
                 Entry<String, String[]> entry = it.next();

                 urlToRedirect.append(entry.getKey()).append("=").append(request.getParameter(entry.getKey())).append("&");
            }

            httpResponse.sendRedirect("/FRE_BPM/login.jsp".concat(urlToRedirect.toString()));
        } else {            

            boolean transactional = true;

            if(url.contains(".") && !url.endsWith(".jsp")){
                transactional = false;
            }

            if(transactional){

                System.out.println("Iniciando transação para a url: " + url);
                try {
                    PersistEngine.beginTransaction();
                    chain.doFilter(request, response);
                    PersistEngine.commit();
                } catch (Exception e) {
                    e.printStackTrace();

                    if ( PersistEngine.getEntityManager() != null && PersistEngine.getEntityManager().isOpen()) {
                        PersistEngine.rollback();
                    }

                } finally {
                    PersistEngine.closeEntityManager();
                }
            } else {
                chain.doFilter(request, response);
            }
        }
    }

    @Override
    public void destroy() {
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }

}

I migrated all persistence logic to a class I created (Persitengine). This class will create instances of entityManager, control transactions, and perform persist, merge, and remove actions.

package freedom.technology.service;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import freedom.technology.utils.PersisteUtils;

public class PersistEngine {

    private static EntityManagerFactory factory = PersisteUtils.getEntityManagerFactory();
    private static final ThreadLocal<EntityManager> threadLocal = new ThreadLocal<EntityManager>();

    public static EntityManager getEntityManager() {
        EntityManager em = threadLocal.get();

        if (em == null) {
            em = factory.createEntityManager();
            threadLocal.set(em);
        }
        return em;
    }

    public static void closeEntityManager() {
        EntityManager em = threadLocal.get();
        if (em != null) {
            em.close();
            threadLocal.set(null);
        }
    }

    public static void closeEntityManagerFactory() {
        factory.close();
    }

    public static void beginTransaction() {
        getEntityManager().getTransaction().begin();
    }

    public static void rollback() {
        getEntityManager().getTransaction().rollback();
    }

    public static void commit() {
        getEntityManager().getTransaction().commit();
    }

    public static <T> void persist(T entity){

        getEntityManager().persist(entity);
    }

    public static <T> void remove(T entity){        

        getEntityManager().remove(entity);      
    }

    public static <T> T merge(T entity){

        return getEntityManager().merge(entity);        
    }

    public static <T> Object findById(Class<T> clazz, Integer id){

        T result = getEntityManager().find(clazz, id);

        return result;
    }
}

Abstractservice and Genericservice classes will be excluded as I will no longer need them.

  • 1

    Cool, you’re heading for the idea of my answer: manage session/connection and database transaction in the application layer. I never did it in a Filter; I was a little afraid but I couldn’t think now what could go wrong...

  • 1

    Tip: It doesn’t do much to start your answer by saying "thank you for the answer", once others appear, you won’t know what you’re talking about. And general acknowledgments are kind of unnecessary here :-) A better option is to vote on the answers that are useful to you.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.