问题
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
来源:https://stackoverflow.com/questions/37857481/jersey-guice-and-hibernate-entitymanager-thread-safety