Jersey, Guice and Hibernate - EntityManager thread

2020-03-31 06:52发布

问题:

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."

回答1:

First of all, what kind of EntityManager are you using? Looking at your code I suposse this kind is Application-Managed EntityManager. It would be important for you to understand the different types of EntityManager.

Please see: http://docs.oracle.com/javaee/6/tutorial/doc/bnbqw.html

Basing on this, you need to create an EntityManagerFactory object and then create an EntityManager object.

Basic Example:

private static EntityManagerFactory emf; 
EntityManager em = null;

public static EntityManagerFactory getEmf(){
    if(emf == null){
        emf = Persistence.createEntityManagerFactory("nameOfYourPersistenceUnit");
    }
    return emf;
}


em = getEmf().createEntityManager();
em.getTransaction().begin();
em.persist(entity);
em.getTransaction().commit();
em.close();


回答2:

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();
    }
}


回答3:

If it says that the transaction is already open, that means that it was open by another process and not closed ...

I suggest to use @Transactionl instead of Writing :

em.getTransaction().begin();

and

em.getTransaction().commit();
em.close();

That will manage the things for you ...

so for you it will be this way :

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

Hope that's help