Spring Вызов метода @Transaction методом внутри одного класса не работает?
Я новичок в транзакции Spring. Что-то, что я нашел очень странным, возможно, я это правильно понял. Я хотел иметь транзакционный уровень метода, и у меня есть метод вызова в одном классе, и кажется, что ему это не нравится, его нужно вызывать из отдельного класса. Я не понимаю, как это возможно. Если у кого-то есть идея, как решить эту проблему, я был бы очень признателен. Я хотел бы использовать тот же класс для вызова аннотированного транзакционного метода.
Вот код:
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);
}
}
}
Ответы
Ответ 1
Это ограничение 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>
Ответ 2
Проблема заключается в том, что прокси-серверы Spring AOP не распространяются, а скорее завершают ваш экземпляр службы для перехвата вызовов. Это приводит к тому, что любой вызов "this" из вашего экземпляра службы непосредственно вызывается в этом экземпляре и не может быть перехвачен прокси-сервером (прокси-сервер даже не знает о таком вызове). Уже упоминалось одно решение. Другим важным является просто Spring вставить экземпляр службы в саму службу и вызвать метод на вложенном экземпляре, который будет прокси-сервером, который обрабатывает ваши транзакции. Но имейте в виду, что это может иметь и плохие побочные эффекты, если ваша служба bean не является одиночной:
<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);
}
}
}
Ответ 3
С 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);
}
}
Ответ 4
Это мое решение для самостоятельного вызова:
public class SBMWSBL {
private SBMWSBL self;
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void postContruct(){
self = applicationContext.getBean(SBMWSBL.class);
}
// ...
}
Ответ 5
Начиная с 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) Это явно, поэтому более читабельно.
Ответ 6
Вы можете авторизовать BeanFactory внутри того же класса и сделать
getBean(YourClazz.class)
Он автоматически проксимитирует ваш класс и учитывает вашу @Transactional или другую аннотацию aop.
Ответ 7
Проблема связана с тем, как классы весенней нагрузки и прокси. Он не будет работать, пока вы не напишете свой внутренний метод/транзакцию в другом классе или не перейдете в другой класс, а затем снова придете к вашему классу, а затем напишите внутренний метод вложенной транскокации.
Подводя итог, весенние прокси не позволяют сценариев, с которыми вы сталкиваетесь. вам нужно написать второй метод транзакции в другом классе