Подтвердить что ты не робот

Запуск новой транзакции в 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();
4b9b3361

Ответ 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.