Как правильно выполнять фоновый stream при использовании Spring Data и Hibernate?

Я создаю простой Tomcat webapp, который использует Spring Data и Hibernate. Есть одна конечная точка, которая выполняет большую работу, поэтому я хочу разгрузить работу в фоновый stream, чтобы веб-запрос не зависал в течение 10+ минут, пока работа выполняется. Поэтому я написал новую службу в пакете component-scan’d:

@Service public class BackgroundJobService { @Autowired private ThreadPoolTaskExecutor threadPoolTaskExecutor; public void startJob(Runnable runnable) { threadPoolTaskExecutor.execute(runnable); } } 

Затем настройте ThreadPoolTaskExecutor весной:

      

Все это отлично работает. Однако проблема исходит из Hibernate. Внутри моей runnable, запросы только наполовину работают. Я могу сделать:

 MyObject myObject = myObjectRepository.findOne() myObject.setSomething("something"); myObjectRepository.save(myObject); - MyObject myObject = myObjectRepository.findOne() myObject.setSomething("something"); myObjectRepository.save(myObject); 

Но если у меня есть ленивые загруженные поля, это терпит неудачу:

 MyObject myObject = myObjectRepository.findOne() List lazies = myObject.getLazies(); for(Lazy lazy : lazies) { // Exception ... } 

Я получаю следующую ошибку:

 org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.stackoverflow.MyObject.lazies, could not initialize proxy - no Session 

Поэтому мне кажется (Hibernate newbie), что новый stream не имеет сеанса в этих домашних streamах, но Spring Data автоматически создает новые сеансы для streamов HTTP-запросов.

  • Есть ли способ начать новый сеанс вручную из сеанса?
  • Или способ рассказать пул streamов сделать это для меня?
  • Какая стандартная практика для такого рода работ?

Мне удалось немного обойти это, сделав все из-за метода @Transactional , но я быстро @Transactional , что это не очень хорошее решение, поскольку это не позволяет мне использовать методы, которые отлично подходят для веб-запросов ,

Благодарю.

    С весной вам не нужен собственный исполнитель. Простая аннотация @Async сделает для вас работу. Просто комментируйте свой heavyMethod в своей службе с ним и верните объект void или Future и вы получите фоновый stream. Я бы не использовал аннотацию async на уровне controllerа, так как это создаст asynchronous stream в исполнителе пула запросов, и у вас могут закончиться «акцепторы запросов».

    Проблема с вашим ленивым исключением возникает, как вы подозревали в новом streamе, который не имеет сеанса. Чтобы избежать этой проблемы, ваш метод async должен обрабатывать всю работу. Не предоставляйте ранее загруженные объекты в качестве параметров. Служба может использовать EntityManager и также может быть транзакционной.

    Я для себя не объединяю @Async и @Transactional поэтому я могу запустить службу в любом случае. Я просто создаю асинхронную оболочку вокруг службы и при необходимости использую ее. (Это упрощает тестирование, например)

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

    Что происходит, вероятно, у вас есть транзакция на вашей части кода DAO, а Spring закрывает сеанс закрытия транзакции.

    Вы должны сжать всю свою бизнес-логику в одну транзакцию.

    Вы можете ввести SessionFactory в свой код и использовать метод SessionFactory.openSession() .
    Проблема в том, что вам придется управлять своими транзакциями.

    Метод №1: Управление сущностью JPA

    В фоновом streamе: Инъекционный менеджер объектов или получить его из контекста Spring или передать его в качестве ссылки:

     @PersistenceContext private EntityManager entityManager; 

    Затем создайте новый менеджер сущностей, чтобы избежать использования общего:

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

    Теперь вы можете начать транзакцию и использовать Spring DAO, repository, JPA и т. Д.

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

    Метод №2: JdbcTemplate

    Если вам нужны низкоуровневые изменения или ваша задача достаточно проста, вы можете сделать это с помощью JDBC и запросов вручную:

     @Autowired private JdbcTemplate jdbcTemplate; 

    а затем где-то в вашем методе:

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

    Замечание: я бы рекомендовал держаться подальше от @Transactional, если вы не используете JTA или не полагаетесь на JpaTransactionManager.

    Давайте будем гением компьютера.