Justas Justas - 4 months ago 33
Java Question

Guice and Hibernate - EntityManager thread safety

I have used this tutorial them same way in my application:
http://www.benmccann.com/hibernate-with-jpa-annotations-and-guice/

My app is JAX-RS web service which will receive many concurrent requests and make updates to database.

GenericDAOImpl.java implementation:

public class GenericDAOImpl<T> implements GenericDAO<T> {

@Inject
protected EntityManager entityManager;

private Class<T> type;

public GenericDAOImpl(){}

public GenericDAOImpl(Class<T> type) {
this.type = type;
}

@Override
public void save(T entity) {
entityManager.getTransaction().begin();
entityManager.persist(entity);
entityManager.getTransaction().commit();
}


}

If 2 concurrent threads try to save entity, I get

java.lang.IllegalStateException: Transaction already active


Saving works well if I comment transaction.

I have tried to use

@Inject
protected Provider<EntityManager> entityManagerProvider;


or

@Inject
protected EntityManagerFactory entityManagerProvider;


and for each request:

EntityManager entityManager = entityManagerProvider.get()


But then I get:

org.hibernate.PersistentObjectException: detached entity passed to persist


What is correct way to implement Guice + Hibernate EntityManager injection / thread-safe generic DAO class?

UPDATE

Andrew Rayner comment from
http://www.benmccann.com/hibernate-with-jpa-annotations-and-guice/

"The logic isn’t really production ready – at least if used in a web app.

Hibernates connection pool is very basic and is not production ready – the recommendation is to use a datasource pool such as c3p0.

EntityManager shouldn’t be reused – it is intended to be created per transaction/request. There is a good chance of polluting subsequent requests.

There is also no transaction rollback if something goes wrong.

An interesting approach – but it would be much safer for webapps to use Guices own Persist extension module for managing the lifecycle of EntityMananger instances and transactions."

Answer

The problem was that my endpoint was annotated with @Singleton so it reused the same EntityManager during concurrent calls. After removing @Singleton, during concurrent calls, different EntityManager objects are used. If endpoint calls are subsequent, it may be that previous/old EntityManager will be used.

Highly simplified example:

@Path("/v1/items")
public class ItemsService {

    @Inject
    private EntityManager entityManager;

    @POST
    @Path("/{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public void saveItem(){
         entityManager.getTransaction().begin();
         entityManager.persist(new Item());
         entityManager.getTransaction().commit();
    }
}
Comments