3

我阅读了 Spring 文档,它说:

@PersistenceContext 注解有一个可选的属性类型,默认为 PersistenceContextType.TRANSACTION。这个默认值是您接收共享 EntityManager 代理所需要的。

  1. 这是否意味着我必须让 EntityManager 在事务中工作?
  2. 它如何用于非事务性方法(读取查询),例如下面代码中的 loadProductsByCategory?
  3. “共享”是什么意思?它如何使用 EntityManager 与他人共享?
  4. 我是否需要将 @Transactional 添加到方法 loadProductsByCategory 才能将 EntityManager 绑定到线程?因为 ProductDaoImpl 类是单例的并且工作在多线程中,但是 entityManager 不是线程安全的。

    @Service
    public class ProductDaoImpl implements ProductDao {
        @PersistenceContext
        private EntityManager em;
        public Collection loadProductsByCategory(String category) {
            Query query = em.createQuery("from Product as p where p.category = :category");
            query.setParameter("category", category);
            return query.getResultList();
        }
        @Transactional
        public void loadProductsByCategory(Product product) {
            em.persist(product);
        }
    }
    
4

1 回答 1

8

以下博客链接对此行为进行了详细分析。http://doanduyhai.wordpress.com/2011/11/21/spring-persistencecontext-explained/这是我的总结。

EntityManager是一个java接口,它允许spring提供它自己的接口实现。spring 注入的实现使用动态代理来处理对实体管理器的调用。动态代理的行为方式如下。

如果没有@Transactional注解,因为loadProductsByCategoryspring 在被调用时会创建一个EntityManagerem的实例em.createQuery,spring 不会返回 JPA 创建的 Query 对象,但它会返回EntityManager这个 spring 代理的 Spring Proxy 将所有调用转发给真正的实现Query并等待直到getResultor getSingleResultorexecuteUpdate被调用并立即关闭实体管理器。

因此,当没有@TransactionalSpring 时,将确保尽快关闭实体管理器,即在实体管理器上的每个方法调用之后或提取结果集之后。在上面的示例中,如果您注释掉 query.getResultList(),您最终将泄漏一个未关闭的实体管理器实例

 public Collection loadProductsByCategory(String category) {
        Query query = em.createQuery("from Product as p where p.category = :category");
        query.setParameter("category", category);
        return null;
        // a leak of an entity manager will happen because getResultList() was never called, so 
        // spring had no chance to close the entity manager it created when em.creaueQuery was 
        // invoked.
        // return query.getResultList(); 
 }

当有@Transactional 属性时,spring 事务管理器将在调用事务方法之前创建一个事务上下文。当事务方法调用实体管理器上的任何方法时,Spring 将创建一个全新的 EntityManager 实例并将其与当前跨国关联,如果事务方法调用另一个方法,该方法调用另一个方法并且所有这些方法都使用实体管理器,那么实体管理器是在所有这些电话中共享。这是一个例子。

main(..)
{
    Foo foo = call spring to get foo instance 
    foo.doFoo();
} 

public class Foo {
     @PersistenceContext 
     EntityManager em; 

     @Autowired
     Bar bar;

     @Transactional 
     public doFoo(){
         // before this method is called spring starts a spring transaction
         em.createQuery(....) // here spring will create an instance of the Entity manager 
          // and assoicated with the current tx 
         bar.doBar(); // call bar transactional method 
     }
}

public calss Bar {

      @PersistenceContext 
     EntityManager em; 


     @Transactional 
     public doBar(){
         // no tx is started here because one was already started in doFoo
         em.createQuery(....) // spring looks under the current tx and finds that it has 
            // an entity manager, was created in the doFoo() method so this entity manager
            // is used, This is what is meant by sharing of the entity manager.
     }
}

回答你最后一个问题。

我是否需要将 @Transactional 添加到方法 loadProductsByCategory 才能将 EntityManager 绑定到线程?因为 ProductDaoImpl 类是单例的并且工作在多线程中,但是 entityManager 不是线程安全的。

@Transactional 导致 spring 将一个 spring tx 绑定到当前线程,然后实体管理器绑定到 spring tx,该 spring tx 通过线程本地绑定到当前线程。

Pro JPA 2 书在第 6 章中对这些内容进行了很好的解释,它有点密集,并且在 Java EE 的上下文中进行了解释,但是对于 spring 的步骤是相同的​​。

于 2013-05-15T05:01:08.697 回答