Spring вложенные транзакции

В моем проекте загрузки Spring я реализовал следующий метод службы:

@Transactional
public boolean validateBoard(Board board) {
    boolean result = false;
    if (inProgress(board)) {
        if (!canPlayWithCurrentBoard(board)) {
            update(board, new Date(), Board.AFK);
            throw new InvalidStateException(ErrorMessage.BOARD_TIMEOUT_REACHED);
        }
        if (!canSelectCards(board)) {
            update(board, new Date(), Board.COMPLETED);
            throw new InvalidStateException(ErrorMessage.ALL_BOARD_CARDS_ALREADY_SELECTED);
        }
        result = true;
    }
    return result;
}

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

@Transactional(propagation = Propagation.REQUIRES_NEW)
public Board update(Board board, Date finishedDate, Integer status) {
    board.setStatus(status);
    board.setFinishedDate(finishedDate);

    return boardRepository.save(board);
}

Мне нужно внести изменения в базу данных в методе update независимо от транзакции владельца, которая запущена в методе validateBoard. В настоящее время любые изменения отскакивают назад в случае каких-либо исключений.

Даже с @Transactional(propagation = Propagation.REQUIRES_NEW) он не работает.

Как правильно сделать это с помощью Spring и разрешить вложенные транзакции?

Ответ 1

Эта документация охватывает вашу проблему - https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#transaction-declarative-annotations

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

Тем не менее, есть возможность переключиться в режим AspectJ

Ответ 2

Использование "self" jnject parttner может решить эту проблему.

Пример кода, как показано ниже:

@Service @Transactional
public class YourService {
   ... your member

   @Autowired
   private YourService self;   //inject proxy as instance member variable ;

   @Transactional(propagation= Propagation.REQUIRES_NEW)
   public void methodFoo() {
      //...
   }

   public void methodBar() {
      //call self.methodFoo() rather than this.methodFoo()
      self.methodFoo();
   }
}

Дело в том, чтобы использовать "я", а не "это".

Ответ 3

Аннотации транзакций в методе update не рассматриваются инфраструктурой транзакций Spring, если вызваны из некоторого метода того же класса. Для более полного понимания того, как работает инфраструктура транзакций Spring, обратитесь к this.

Ответ 4

Основное правило "вложенных транзакций" заключается в том, что они полностью зависят от базовой базы данных, т.е. поддержка вложенных транзакций и их обработка зависят от базы данных и зависят от нее. В некоторых базах данных изменения, внесенные вложенной транзакцией, не видны транзакцией "хоста", пока вложенная транзакция не будет зафиксирована. Это может быть достигнуто с помощью изоляции транзакций в @Transactional (изоляция = "")

Вам нужно определить место в вашем коде, откуда генерируется исключение, т.е. из родительского метода: "validateBoard" или из дочернего метода: "update".

Ваш фрагмент кода показывает, что вы явно выбрасываете исключения.

ТЫ ДОЛЖЕН ЗНАТЬ::

В конфигурации по умолчанию код инфраструктуры транзакций Spring Frameworks помечает транзакцию только для отката в случае выполнения, непроверенных исключений; это когда выброшенное исключение является экземпляром или подклассом RuntimeException.

Но @Transactional никогда не откатывает транзакцию для любого проверенного исключения.

Таким образом, Spring позволяет определить

  • Исключение, для которого транзакция должна быть откатана
  • Исключение, для которого транзакция не должна откатываться

Попробуйте аннотировать ваш дочерний метод: обновите с помощью @Transactional (no-rollback-for = "ExceptionName") или вашего родительского метода.

Ответ 5

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

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

@Service
class SomeService ..... {
    -->> @Autorired
    -->> private ApplicationContext context;
    -->> //or with implementing ApplicationContextAware

    @Transactional(any propagation , it not important in this case)
    public boolean methodOne(SomeObject object) {
      .......
       -->> here you get a proxy from context and call a method from this proxy
       -->>context.getBean(SomeService.class).
            methodTwo(object);
      ......
   }

    @Transactional(any propagation , it not important in this case)public boolean 
    methodTwo(SomeObject object) {
    .......
   }
}

когда вы вызываете контейнер context.getBean(SomeService.class).methodTwo(object);, возвращает объект прокси, и в этом прокси-сервере вы можете вызвать methodTwo(...) с транзакцией.

Ответ 6

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