Почему транзакция не откатывается в Spring JPA для требуемого уровня распространения? - PullRequest
0 голосов
/ 14 января 2020

У меня есть два метода в репозитории JPA. Оба метода имеют уровень распространения как REQUIRED Методы используются для сохранения объектов сущности с использованием Hibernate до Postgresql

@Transactional(propagation = Propagation.REQUIRED)
public void persistEmployee() {
    Employee employee = new Employee("Peter", "Washington DC");
    entityManager.persist(employee);
    try {
        persistLineManager();
    }
    catch( Exception e ) {
         // some task
    }
}

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
public void persistLineManager() {
    Employee lineManager = new Employee("John", "NYC");
    entityManager.persist(lineManager);
    if(lineManager != null) // intentionally! To trigger rollback
        throw new RuntimeException("Rollback!");
}

Согласно документам Spring, когда уровень распространения равен REQUIRED, оба метода будут работать в той же транзакции. В моем коде я намеренно выбрасываю исключение для запуска отката, но тем не менее обе сущности сохраняются. Но я считаю, что обе операции должны быть отменены. Пожалуйста, исправьте, если мое понимание неверно, и дайте мне знать правильный способ откатить обе операции.

PROPAGATION_REQUIRES_NEW : [из весенних документов]

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

1 Ответ

5 голосов
/ 14 января 2020

ПРОКСИФИКАЦИЯ

В вашем сервисе вы создали 2 метода, оба @Transactional. Когда вы создаете ваш bean-компонент, Spring создаст прокси-сервер, который добавит вам поведение во время выполнения для транзакционного метода. Давайте погрузимся глубже: Proxy by spring Этот проксификатор иллюстрируется изображением. Любой звонящий из внешнего мира будет напрямую разговаривать не с вами , а с вашим доверенным лицом. И тогда прокси-сервер позвонит вам, чтобы выполнить код вашего сервиса.

Теперь, это " Любой абонент из внешнего мира не будет напрямую разговаривать с вами " очень важно. Если вы делаете внутренний вызов, как вы делаете в persistEmployee, который вызывает persistLineManager, то вы не проходите через прокси. Вы вызываете напрямую свой метод, НЕ ПРОКСИ. Поэтому аннотации в верхней части вашего persistLineManager метода не читаются.

Итак, когда persistLineManager выдает RuntimeException, исключение перехватывается непосредственно вашим вызывающим persistEmployee, вы go прямо в вашем улове. Поскольку прокси-сервер отсутствует, отката нет, поскольку транзакционный прокси-сервер не перехватил исключение.

Если вы сделаете только это, у вас произойдет откат:

@Transactional(propagation = Propagation.REQUIRED)
public void persistEmployee() {
    Employee employee = new Employee("Peter", "Washington DC");
    entityManager.persist(employee);
    persistLineManager();
    // Don't catch and the exception will be catched by the transaction proxy, which will rollback
}

public void persistLineManager() {
    Employee lineManager = new Employee("John", "NYC");
    entityManager.persist(lineManager);
    if(lineManager != null) // intentionally! To trigger rollback
        throw new RuntimeException("Rollback!");
}

По умолчанию @Transactional откат для RuntimeException

ШАГ СДЕЛКИ

Предположим, что вы все еще хотите, чтобы оба метода были транзакционными независимо, и вы можете использовать TransactionTemplate. Вот пример:

class MyService {
    // Have a field of type TransactionTemplate
    private TransactionTemplate template;

    // In the constructor, Spring will inject the correct bean
    public MyService(PlatformTransactionManager transactionManager) {
        template = new TransactionTemplate(transactionManager);
        // Set this here if you always want this behaviour for your programmatic transaction
        template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    }

    // Here you start your first transaction when arriving from the outside
    @Transactional(propagation = Propagation.REQUIRED)
    public void persistEmployee() {
        Employee employee = new Employee("Peter", "Washington DC");
        entityManager.persist(employee);
        // Inner call
        try {
            persistLineManager();
        } catch (RuntimeException e) {
            // Do what you want
        }
    }

    public void persistLineManager() {
        // Here, ask to your transactionTemplate to execute your code.
        template.execute(status -> {
            Employee lineManager = new Employee("John", "NYC");
            entityManager.persist(lineManager);
            if(lineManager != null) // intentionally! To trigger rollback
                throw new RuntimeException("Rollback!");
            return null;
        });
    }
}

Я не все протестировал, вы можете столкнуться с некоторыми ошибками, но я надеюсь, что вы поняли идею.

PROPAGATION

Позвольте мне добавить последнюю часть о разнице между PROPAGATION_REQUIRED и PROPAGATION_REQUIRES_NEW:

PROPAGATION_REQUIRED: PROPAGATION_REQUIRED

  • Либо у меня нет транзакции, затем я создаю одну
  • Или выполняется транзакция, и я присоединяюсь к ней.

PROPAGATION_REQUIRES: PROPAGATION_REQUIRES_NEW

  • В любых ситуациях, независимо от того, выполняется транзакция или нет, я создаю новую.

Пример:

  • Клиент входя в мой транзакционный метод, с PROPAGATION_REQUIRED. Он создает имя транзакции "TA".
  • Этот транзакционный метод вызывает метод, который также является транзакционным, но PROPAGATION_REQUIRES_NEW. Он создает вторую транзакцию с именем "TB"
  • . Имеет now: "client" -> "TA" -> "TB"
  • Но второй метод вызывает откат. В этом случае будет выполнен откат только «TB», так как «TA» и «TB» - это две разные транзакции.
  • Итак, в БД я буду сохранять все операции, которые были выполнены в «TA», но не в "ТБ".

Надеюсь, это поможет

...