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 трюк реально сработал. Вы можете решить эту проблему, создав новую услугу, но я не думаю, что это ваша идея, поэтому самовнушение сделало это для меня