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

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

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