Spring Вызов метода @Transaction методом внутри того же класса, не работает? - PullRequest
89 голосов
/ 06 августа 2010

Я новичок в Spring Transaction. Что-то, что мне показалось странным, наверное, я правильно понял. Я хотел иметь транзакционный уровень метода, и у меня есть метод вызывающей стороны в том же классе, и кажется, что это не так, его нужно вызывать из отдельного класса. Я не понимаю, как это возможно. Если у кого-то есть идеи, как решить эту проблему, я буду очень признателен. Я хотел бы использовать тот же класс для вызова аннотированного транзакционного метода.

Вот код:

public class UserService {

    @Transactional
    public boolean addUser(String userName, String password) {
        try {
            // call DAO layer and adds to database.
        } catch (Throwable e) {
            TransactionAspectSupport.currentTransactionStatus()
                    .setRollbackOnly();

        }
    }

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            addUser(user.getUserName, user.getPassword);
        }
    } 
}

Ответы [ 7 ]

85 голосов
/ 07 августа 2010

Это ограничение Spring AOP (динамические объекты и cglib ).

Если вы сконфигурировали Spring для использования AspectJ для обработки транзакций ваш код будет работать.

Простая и, вероятно, лучшая альтернатива - это рефакторинг вашего кода.Например, один класс, который обрабатывает пользователей, и один, который обрабатывает каждого пользователя.Тогда по умолчанию обработка транзакций с Spring AOP будет работать.


Советы по настройке для обработки транзакций с AspectJ

Чтобы разрешить Spring использовать AspectJ для транзакций, вы должны установитьрежим AspectJ:

<tx:annotation-driven mode="aspectj"/>

Если вы используете Spring с более старой версией, чем 3.0, вы также должны добавить это в свою конфигурацию Spring:

<bean class="org.springframework.transaction.aspectj
        .AnnotationTransactionAspect" factory-method="aspectOf">
    <property name="transactionManager" ref="transactionManager" />
</bean>
54 голосов
/ 06 октября 2010

Проблема в том, что прокси-серверы Spring AOP не расширяются, а заключают в себе экземпляр службы для перехвата вызовов.Это приводит к тому, что любой вызов «this» из вашего экземпляра службы напрямую вызывается в этом экземпляре и не может быть перехвачен прокси-сервером переноса (прокси даже не знает ни о каком таком вызове).Одно из решений уже упоминалось.Другим изящным было бы просто сделать так, чтобы Spring вставлял экземпляр службы в саму службу и вызывал ваш метод для внедренного экземпляра, который будет прокси, который обрабатывает ваши транзакции.Но имейте в виду, что это может иметь и плохие побочные эффекты, если ваш служебный компонент не является единичным:

<bean id="userService" class="your.package.UserService">
  <property name="self" ref="userService" />
    ...
</bean>

public class UserService {
    private UserService self;

    public void setSelf(UserService self) {
        this.self = self;
    }

    @Transactional
    public boolean addUser(String userName, String password) {
        try {
        // call DAO layer and adds to database.
        } catch (Throwable e) {
            TransactionAspectSupport.currentTransactionStatus()
                .setRollbackOnly();

        }
    }

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            self.addUser(user.getUserName, user.getPassword);
        }
    } 
}
12 голосов
/ 19 марта 2018

С Spring 4 возможно самостоятельное подключение

@Service
@Transactional
public class UserServiceImpl implements UserService{
    @Autowired
    private  UserRepositroy repositroy;

    @Autowired
    private UserService userService;

    @Override
    public void update(int id){
       repository.findOne(id).setName("ddd");
    }

    @Override
    public void save(Users user) {
        repositroy.save(user);
        userService.update(1);
    }
}
5 голосов
/ 04 декабря 2016

Это мое решение для самостоятельного вызова :

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void postContruct(){
        self = applicationContext.getBean(SBMWSBL.class);
    }

    // ...
}
2 голосов
/ 27 мая 2019

Начиная с Java 8, есть еще одна возможность, которую я предпочитаю по причинам, указанным ниже:

@Service
public class UserService {

    @Autowired
    private TransactionHandler transactionHandler;

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            transactionHandler.runInTransaction(() -> addUser(user.getUsername, user.getPassword));
        }
    }

    private boolean addUser(String username, String password) {
        // TODO
    }
}

@Service
public class TransactionHandler {

    @Transactional(propagation = Propagation.REQUIRED)
    public <T> T runInTransaction(Supplier<T> supplier) {
        return supplier.get();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public <T> T runInNewTransaction(Supplier<T> supplier) {
        return supplier.get();
    }
}

Этот подход имеет следующие преимущества:

1) Может применяться к частных методов.Таким образом, вам не нужно нарушать инкапсуляцию, делая метод общедоступным только для удовлетворения ограничений Spring.

2) Один и тот же метод может быть вызван при различном распространении транзакции, и это зависит от вызывающей стороны выбрать подходящий.Сравните эти 2 строки:

transactionHandler.runInTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
transactionHandler.runInNewTransaction(() -> userService.addUser(user.getUserName, user.getPassword));

3) Это явно, поэтому более читабельно.

0 голосов
/ 06 июля 2018

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

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

0 голосов
/ 04 ноября 2014

Вы можете автоматически подключить BeanFactory внутри того же класса и сделать

getBean(YourClazz.class)

. Он автоматически проксирует ваш класс и учитывает вашу аннотацию @Transactional или другую aop.

...