Jersey、Guice 和 Hibernate - EntityManager 线程安全

Jersey, Guice and Hibernate - EntityManager thread safety

我在我的应用程序中以相同的方式使用了本教程: http://www.benmccann.com/hibernate-with-jpa-annotations-and-guice/

我的应用程序是 JAX-RS 网络服务,它将接收许多并发请求并对数据库进行更新。

GenericDAOImpl.java 实施:

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

}

如果 2 个并发线程尝试保存实体,我得到

java.lang.IllegalStateException: Transaction already active

如果我评论交易,保存效果很好。

我试过用

@Inject
protected Provider<EntityManager> entityManagerProvider;

@Inject
protected EntityManagerFactory entityManagerProvider;

并且对于每个请求:

EntityManager entityManager = entityManagerProvider.get()

但后来我得到:

org.hibernate.PersistentObjectException: detached entity passed to persist

什么是实现 Guice + Hibernate EntityManager 注入/线程安全通用 DAO 的正确方法class?

更新

Andrew Rayner 评论来自 http://www.benmccann.com/hibernate-with-jpa-annotations-and-guice/

“逻辑并没有真正准备好生产——至少如果在网络应用程序中使用的话。

Hibernates 连接池非常基础,还没有准备好生产——建议使用数据源池,例如 c3p0。

不应重复使用 EntityManager – 它旨在根据 transaction/request 创建。很有可能会污染后续的请求。

如果出现问题,也没有事务回滚。

一个有趣的方法——但是对于 webapps 使用 Guices 自己的 Persist 扩展模块来管理 EntityMananger 实例和事务的生命周期会更安全。

首先,您使用的是哪种EntityManager?查看您的代码,我认为这种是 Application-Managed EntityManager。了解不同类型的 EntityManager 对您来说很重要。

请看:http://docs.oracle.com/javaee/6/tutorial/doc/bnbqw.html

基于此,您需要创建一个EntityManagerFactory对象,然后再创建一个EntityManager对象。

基本示例:

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

问题是我的端点用@Singleton 注释,因此它在并发调用期间重复使用了相同的 EntityManager。删除@Singleton 后,在并发调用期间,将使用不同的 EntityManager 对象。如果端点调用是后续的,可能会使用 previous/old EntityManager。

高度简化的示例:

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

如果它说事务已经打开,这意味着它被另一个进程打开并且没有关闭...

我建议使用@Transactionl 而不是写作:

em.getTransaction().begin();

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

那将为您管理这些事情...

所以对你来说就是这样:

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

希望对您有所帮助