@Async is killing Hibernate transaction

13,805

Hibernate transactions work on a ThreadLocal basis.

As you use another thread with @Async there will be no transaction active.

You can achieve this functionality by having the async method calling another bean which is annotated by @Transactional.

Here I explain this approach a little bit more: How do I properly do a background thread when using Spring Data and Hibernate?

Share:
13,805
Dejell
Author by

Dejell

I like traveling around the world. So far I have been to: USA England Italy Slovania Croatia Jordan South Africa Zimbabwe Botswana France Canada Israel Thailand Switzerland Holland Bulgaria I am going to Vietnam soon

Updated on June 04, 2022

Comments

  • Dejell
    Dejell almost 2 years

    I am using an Open-Session-In-View transaction model for my REST api like this:

    @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          sessionFactory.getCurrentSession().beginTransaction();
          chain.doFilter(request, response);
          sessionFactory.getCurrentSession().getTransaction().commit();
    }
    

    This work just fine. I wanted to add @Async abilities. So I created:

    @Configuration
    @EnableAsync
    public class AsyncConfig implements AsyncConfigurer {
    
        @Override
        @Bean(destroyMethod="shutdown")
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(18);
            executor.setMaxPoolSize(18);
            executor.initialize();
            executor.setDaemon(true);
            executor.setWaitForTasksToCompleteOnShutdown(false);
            return executor;
        }
    
        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return new SimpleAsyncUncaughtExceptionHandler();
        }
    }
    

    and:

    @Component
    public class AsyncMarketCaller {
    
        @Inject
        MarketManagerFactory marketManagerFactory;
    
        @Async
        public Future<List<Product>> getProducts(){
    
            MarketManager productManager = marketManagerFactory.obtainMarketManager(market);
            result = productManager.getProducts();
            ....
        }
    }
    

    The productManager makes a call to another @Service

    @Service
    public class DefaultIdentifierManager implements IdentifierManager{
         @Inject
        UpcEanDAO upcEanDAO;
    
          @Override
        public String getTitleForIdentifier(String identifier){
            UpcEan upcEan = upcEanDAO.find(identifier);
        }
    }
    

    however, for upcEanDAO.find(identifier) I get an exception:

    Caused by: org.hibernate.HibernateException: get is not valid without active transaction
    

    Before I added the @Async ability to make async calls to getProducts() it worked just fine so I assume that @Async kills the transaction that I opened with Hibernate.

    I tried adding based on another answer here, @Transactional to the method annotated with @Async but it doesn't help.

    Any idea?

    EDITED

    I edited the code so

    @Component
    public class AsyncMarketCaller {
    
        @Inject
        AsyncMarketService asyncMarketService;
    
        @Async
        public Future<List<Product>> getProducts(){
            asyncMarketService.getProducts();
        }
    }
    

    and

    @Service
    public class AsyncMarketService {
    
        @Inject
        MarketManagerFactory marketManagerFactory;
    
        @Transactional
        public Future<List<Product>> getProducts()
         ....
    }
    
    }
    

    I see in the log

    50689 DEBUG [localhost-startStop-1] org.springframework.transaction.annotation.AnnotationTransactionAttributeSource - Adding transactional method 'AsyncMarketService.getProducts' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
    

    but it doesn't help. Please note that my method AsyncMarketService.getProducts doesn't call directly the DB, it calls to other methods and only one of them will make the call.

    I also added above the one that actually make the call to DB:@Transactional

    49992 DEBUG [localhost-startStop-1] org.springframework.transaction.annotation.AnnotationTransactionAttributeSource - Adding transactional method 'DefaultIdentifierManager.getTitleForIdentifier' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''