2016-04-06 2 views
8

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

@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, сущности больше не управляются, а обновление объекта задачи не распространяется на db.I не считаю естественным сделать транзакционный метод по расписанию.

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

1. Код Keep, как сегодня.

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

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

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

ответ

9

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

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

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

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

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

+0

В этом примере синхронизируется ли сущность сущности сущности транзакции с сеансом внешней транзакции? Например, если объект «Задача» изменен внутри вложенной транзакции, это изменение применимо и к сеансу внешней транзакции? – froi

+0

Следуйте моему вопросу, так как сеанс внешней транзакции покраснел, означает ли это, что изменения задачи будут считаться «устаревшими» изменениями? – froi

1

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

Цитируя spring.io forum:

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

This is one of 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 met корыто, вы могли бы получить от сделки EntityManager и управлять сделки программно, например:

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

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

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

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

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

НТН.

+0

Даже если метод 'processTask' является частным методом в' HandlingService', все обновления должны быть переданы в db, потому что новая транзакция была открыта, когда был вызван метод 'handle' (хотя прокси-сервер Spring). Во втором варианте я предложил удалить «@ Transcendal» из планировщика и передать идентификатор в службу обработки (который откроет транзакции для каждой обрабатываемой задачи) – mvlupan

2

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

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

Смежные вопросы