Запуск новой транзакции в Spring bean
Имеем:
@Transactional(propagation = Propagation.REQUIRED)
public class MyClass implementes MyInterface { ...
MyInterface имеет единственный метод: go()
.
Когда go() выполняется, мы начинаем новую транзакцию, которая совершает/откаты после завершения метода - это нормально.
Теперь скажем, в go() мы вызываем частный метод в MyClass, который имеет @Transactional(propagation = Propagation.REQUIRES_NEW
. Кажется, что Spring "игнорирует" аннотацию REQUIRES_NEW и не запускает новую транзакцию. Я считаю, что это потому, что Spring AOP работает на уровне интерфейса (MyInterface) и не перехватывает никаких вызовов методам MyClass. Правильно ли это?
Есть ли способ начать новую транзакцию в транзакции go()? Единственный способ вызвать другой Spring управляемый bean, который имеет транзакции, настроенные как REQUIRES_NEW?
Обновить: добавив, что, когда клиенты выполняют go()
, они делают это через ссылку на интерфейс, а не класс:
@Autowired
MyInterface impl;
impl.go();
Ответы
Ответ 1
Из справки Spring 2.5:
При использовании прокси, аннотация @Transactional
должна применяться только к методы с публичной видимостью. Если вы делаете аннотацию защищенных, закрытых или методы, видимые в пакете, с аннотацией @Transactional
, никакая ошибка не будет быть поднятым, но в аннотированном методе не будет отображаться сконфигурированный транзакционный настройки.
Итак, Spring игнорирует аннотацию @Transactional
для непубличных методов.
Кроме того,
В режиме прокси (который по умолчанию), только входящие вызовы 'внешнего' через прокси-сервер будет перехвачен. Это означает, что "самоисключение", то есть метод внутри целевого объекта, вызывающий какой-либо другой метод целевой объект, не приведет к фактической транзакции во время выполнения, даже если вызываемый метод помечен @Transactional
!
Итак, даже если вы создадите свой метод public
, вызов его из метода одного класса не начнет новую транзакцию.
Вы можете использовать режим aspectj
в настройках транзакции, чтобы связанный с транзакцией код был соткан в классе, и прокси не создается во время выполнения.
Подробнее см. справочный документ.
Другой возможный способ сделать это - получить прокси-сервер Spring класса в самом классе и вызвать на нем методы, а не this
:
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class SomeService {
@Autowired
private ApplicationContext applicationContext;
private SomeService getSpringProxy() {
return applicationContext.getBean(this.getClass());
}
private void doSomeAndThenMore() {
// instead of
// this.doSometingPublicly();
// do the following to run in transaction
getSpringProxy().doSometingPublicly();
}
public void doSometingPublicly() {
//do some transactional stuff here
}
}
Ответ 2
@Transactional
будет замечен только в том случае, если он по методу public
, из-за способа работы Spring АОП.
Однако вы можете программно запустить новую транзакцию, если хотите, используя TransactionTemplate
, например
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus status) {
// do stuff
}
});
Ответ 3
Короче говоря, вы должны вызвать метод, хотя и прокси, для достижения транзакционного поведения.
Можно вызвать "REQUIRES_NEW" в том же bean, как задано в вопросе. Для этого вам нужно сделать ссылку "self".
В spring это не так просто. Вы должны ввести его
с аннотацией @Resource.
@Service("someService")
public class ServieImpl implements Service {
@Resource(name = "someService")
Service selfReference;
@Transactional
public void firstMethod() {
selfReference.secondMethod();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void secondMethod() {
//do in new transaction
}
}
Вызов в firstMethod вызывает прокси, а не "this", который должен сделать транзакцию REQUIRES_NEW.