How do I properly do a background thread when using Spring Data and Hibernate?

后端 未结 3 724
小鲜肉
小鲜肉 2020-11-28 11:00

I\'m building a simple Tomcat webapp that\'s using Spring Data and Hibernate. There\'s one end point that does a lot of work, so I want to offload the work to a background t

相关标签:
3条回答
  • 2020-11-28 11:11

    With Spring you don't need your own executor. A simple annotation @Async will do the work for you. Just annotate your heavyMethod in your service with it and return void or a Future object and you will get a background thread. I would avoid using the async annotation on the controller level, as this will create an asynchronous thread in the request pool executor and you might run out of 'request acceptors'.

    The problem with your lazy exception comes as you suspected from the new thread which does not have a session. To avoid this issue your async method should handle the complete work. Don't provide previously loaded entities as parameters. The service can use an EntityManager and can also be transactional.

    I for myself dont merge @Async and @Transactional so i can run the service in either way. I just create async wrapper around the service and use this one instead if needed. (This simplifies testing for example)

    @Service
    public class AsyncService {
    
        @Autowired
        private Service service;
    
        @Async
        public void doAsync(int entityId) {
            service.doHeavy(entityId);
        }
    }
    
    @Service
    public class Service {
    
        @PersistenceContext
        private EntityManager em;
    
        @Transactional
        public void doHeavy(int entityId) {
            // some long running work
        }
    }
    
    0 讨论(0)
  • 2020-11-28 11:20

    What happens is, probably, you have transaction on your DAO piece of code and Spring is closing the session on transaction close.

    You should squeeze all your business logic into single transaction.

    You can inject SessionFactory into your code and use SessionFactory.openSession() method.
    The problem is, that you will have to manage your transactions.

    0 讨论(0)
  • 2020-11-28 11:23

    Method #1: JPA Entity Manager

    In background thread: Inject entity manager or get it from Spring context or pass it as reference:

    @PersistenceContext
    private EntityManager entityManager;    
    

    Then create a new entity manager, to avoid using a shared one:

    EntityManager em = entityManager.getEntityManagerFactory().createEntityManager();
    

    Now you can start transaction and use Spring DAO, Repository, JPA, etc

    private void save(EntityManager em) {
    
        try
        {         
            em.getTransaction().begin();                
    
            <your database changes>
    
            em.getTransaction().commit();                        
        }
        catch(Throwable th) {
            em.getTransaction().rollback();
            throw th;
        }        
    }
    

    Method #2: JdbcTemplate

    In case you need low-level changes or your task is simple enough, you can do it with JDBC and queries manually:

    @Autowired
    private JdbcTemplate jdbcTemplate;
    

    and then somewhere in your method:

    jdbcTemplate.update("update task set `status`=? where id = ?", task.getStatus(), task.getId());
    

    Side note: I would recommend to stay away from @Transactional unless you use JTA or rely on JpaTransactionManager.

    0 讨论(0)
提交回复
热议问题