How to use transaction-scoped persistence context

2019-04-12 19:08发布

问题:

I read the Spring doc and it says:

The @PersistenceContext annotation has an optional attribute type, which defaults to PersistenceContextType.TRANSACTION. This default is what you need to receive a shared EntityManager proxy.

  1. Does this mean I have to make EntityManager work in a transaction?
  2. How does it work for non-transactional method (reading query), such as the loadProductsByCategory in the below code?
  3. What does the "shared" mean? How can it use EntityManager sharing with others?
  4. Do I need to add @Transactional to the method loadProductsByCategory in order to bind the EntityManager to the thread? Because the class ProductDaoImpl is singleton and works in multi-thread, but entityManager is not thread-safe.

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

回答1:

There is a detailed analysis of this behavior at the following blog link. http://doanduyhai.wordpress.com/2011/11/21/spring-persistencecontext-explained/ Here is my summary of it.

EntityManager is a java interface which allows spring to provide it's own implementation of the interface. The implementation injected by spring use a dynamic proxy to handle the calls to the entity manager. The dynamic proxy behaves the following way.

if there is no @Transactional annotation as in loadProductsByCategory spring will create an instance of the EntityManager em when em.createQuery is called, spring will not return the Query object created by JPA but it will return Spring Proxy of EntityManager this spring proxy forwards all calls to the real implementation of Query and it waits until the getResult or getSingleResult or executeUpdate are called and it immediately closes the Entity Manager.

So when there is no @Transactional Spring will make sure that the entity manager is closed as soon as possible, i.e. after each method call on the entity manager or after a result set is extracted. In your above example if you comment out the query.getResultList() you will end up leaking an entity manager instance that does not get closed

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

When there is @Transactional attribute the spring transaction manager will create a transactional context before the transactional method is called. When transactional method calls any method on the entity manager Spring will create a brand new EntityManager instance and associate it with current transnational, if the transactional method, calls another method, which calls another and all those methods use an entity manager then the entity manager is shared across all those calls. here is an example.

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

To answer your last last question.

Do I need to add @Transactional to the method loadProductsByCategory in order to bind the EntityManager to the thread? Because the class ProductDaoImpl is singleton and works in multi-thread, but entityManager is not thread-safe.

The @Transactional causes spring to bind a spring tx to the current thread, and then the entity manager is bound to the spring tx which is bound via thread local to the current thread.

Pro JPA 2 book has a decent explanation of this stuff in chapter 6, it is bit dense and it is explained in the context of Java EE but the steps are the same for spring.