Должен ли я передать управляемую сущность методу, требующему новой транзакции?

мое приложение загружает список лиц, которые должны быть обработаны. Это происходит в классе, который использует планировщик

@Component
class TaskScheduler {

    @Autowired
    private TaskRepository taskRepository;

    @Autowired
    private HandlingService handlingService;

    @Scheduled(fixedRate = 15000)
    @Transactional
    public void triggerTransactionStatusChangeHandling() {
        taskRepository.findByStatus(Status.OPEN).stream()
                               .forEach(handlingService::handle);
    }
}

в своем HandlingService обрабатывает каждую задачу в issolation с помощью REQUIRES_NEW на уровне пропаганды.

@Component
class HandlingService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void handle(Task task) {
        try {
            processTask(task); // here the actual processing would take place
            task.setStatus(Status.PROCCESED);
        } catch (RuntimeException e) {
            task.setStatus(Status.ERROR);
        }
    }
}

код работает только потому, что я начал родительскую транзакцию на TaskScheduler класса. Если я удалю @Transactional аннотация сущности больше не управляются, и обновление сущности задачи не распространяется на БД.Я не нахожу его. естественно сделать запланированный метод транзакционным.

из того, что я вижу есть два варианта:

1. Сохраняйте код таким, какой он есть сегодня.

  • может быть, это только я, и это правильный подход.
  • это varianthas наименьшие поездки в базу данных.

2. Удалить @Transactional аннотация из планировщика, передайте идентификатор задачи и перезагрузите объект задачи в HandlingService.

@Component
class HandlingService {

    @Autowired
    private TaskRepository taskRepository;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void handle(Long taskId) {
        Task task = taskRepository.findOne(taskId);
        try {
            processTask(task); // here the actual processing would take place
            task.setStatus(Status.PROCCESED);
        } catch (RuntimeException e) {
            task.setStatus(Status.ERROR);
        }
    }
}
  • имеет больше поездок в базу данных (один дополнительный запрос/элемент)
  • может быть выполнен с помощью @Async

не могли бы вы высказать свое мнение о том, какой правильный способ решения такого рода проблем, возможно, с помощью другого метода, о котором я не знал?

3 ответов


если ваше намерение состоит в обработке каждой задачи в отдельной транзакции, то ваш первый подход фактически не работает, потому что все фиксируется в конце транзакции планировщика.

причина в том, что во вложенной транзакции Task экземпляры в основном являются обособленными сущностями (Sessions, запущенные во вложенных транзакциях, не знают об этих экземплярах). В конце транзакции планировщика Hibernate выполняет грязную проверку управляемого экземпляры и синхронизирует изменения с базой данных.

этот подход также очень рискован, потому что могут возникнуть проблемы, если вы попытаетесь получить доступ к неинициализированному прокси-серверу на Task экземпляр во вложенной транзакции. И могут возникнуть проблемы, если вы измените Task граф объектов во вложенной транзакции, добавив к нему другой экземпляр сущности, загруженный во вложенную транзакцию (потому что этот экземпляр теперь будет отсоединен, когда элемент управления вернется в планировщик сделка.)

С другой стороны, второй подход является правильным и просто и помогает избежать всех вышеперечисленных ошибок. Только я бы прочитал идентификаторы и зафиксировал транзакцию (нет необходимости приостанавливать ее во время обработки задач). Самый простой способ достичь этого-удалить Transactional аннотация из планировщика и сделать метод репозитория транзакционным (если он еще не транзакционный).

если (и только если) производительность второй подход является проблемой, как вы уже упоминали, вы можете пойти с асинхронной обработкой или даже распараллелить обработку до некоторой степени. Кроме того, вы можете взглянуть на расширенные заседания (разговоры), Может быть, вы могли бы найти его подходящим для вашего случая.


текущий код обрабатывает задачу во вложенной транзакции, но обновляет состояние задачи во внешней транзакции (поскольку объект задачи управляется внешней транзакцией). Поскольку это разные транзакции, возможно, что одна завершается успешно, а другая завершается неудачно, оставляя базу данных в несогласованном состоянии. В частности, в этом коде завершенные задачи остаются открытыми, если обработка другой задачи вызывает исключение или сервер перезапускается перед всеми задачи обработаны.

Как показывает ваш пример, передача управляемых сущностей в другую транзакцию делает неоднозначным, какая транзакция должна обновлять эти сущности, и поэтому лучше избегать. Вместо этого вы должны передавать идентификаторы (или отдельные объекты) и избегать ненужного вложения транзакций.


предполагая, что processTask(task); является методом в HandlingService класс (такой же, как handle(task) способ), затем удалить @Transactional на HandlingService не будет работать из-за естественного поведения динамического прокси-сервера Spring.

цитирую весна.форум Ио:

когда Spring загружает ваш TestService, он обертывает его прокси-сервером. Если вы вызываете метод TestService вне TestService, вместо этого будет вызываться прокси-сервер, и ваша транзакция будет управляться правильно. Однако если вы вызываете свой транзакционный метод в методе в том же объекте, вы не будете вызывать прокси-сервер, а непосредственно цель прокси-сервера, и вы не будете выполнять код, обернутый вокруг вашей службы для управления транзакцией.

это один из SO thread об этой теме, И вот некоторые статьи о это:

  1. http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html
  2. http://tutorials.jenkov.com/java-persistence/advanced-connection-and-transaction-demarcation-and-propagation.html
  3. http://blog.jhades.org/how-does-spring-transactional-really-work/

Если вам действительно не нравится добавлять @Transaction аннотация в @Scheduled метод, вы можете получить транзакция из EntityManager и управлять транзакцией программно, например:

UserTransaction tx = entityManager.getTransaction();
try {
  processTask(task);
  task.setStatus(Status.PROCCESED);
  tx.commit(); 
} catch (Exception e) {
  tx.rollback();
}

но я сомневаюсь, что вы пойдете этим путем (ну, я не буду). В конце концов,

можете ли вы, пожалуйста, предложить свое мнение о том, какой правильный способ решения такого рода проблем

нет правильно в этом случае. Мое личное мнение, аннотация (например,@Transactional) - всего маркер, а вы нужен процессор аннотаций (spring, в данном случае), чтобы сделать @Transactional работа. Аннотация не будет иметь никакого влияния вообще без своего процессора.

Я буду больше беспокоиться о том, например, почему у меня processTask(task) и task.setStatus(Status.PROCESSED); живу за пределами processTask(task) если похоже, что делает то же самое и т. д.

HTH.