Programing

동일한 클래스 내의 메서드에 의한 Spring @Transaction 메서드 호출이 작동하지 않습니까?

lottogame 2020. 8. 28. 07:50
반응형

동일한 클래스 내의 메서드에 의한 Spring @Transaction 메서드 호출이 작동하지 않습니까?


저는 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);
        }
    } 
}

Spring AOP (동적 객체 및 cglib ) 의 한계입니다 .

AspectJ 를 사용하여 트랜잭션을 처리 하도록 Spring을 구성하면 코드가 작동합니다.

간단하고 아마도 가장 좋은 대안은 코드를 리팩토링하는 것입니다. 예를 들어 사용자를 처리하는 클래스와 각 사용자를 처리하는 클래스가 있습니다. 그러면 Spring AOP를 사용한 기본 트랜잭션 처리가 작동합니다.


AspectJ로 트랜잭션을 처리하기위한 구성 팁

Spring이 트랜잭션에 AspectJ를 사용하도록하려면 모드를 AspectJ로 설정해야합니다.

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

3.0 이전 버전에서 Spring을 사용하는 경우이를 Spring 구성에 추가해야합니다.

<bean class="org.springframework.transaction.aspectj
        .AnnotationTransactionAspect" factory-method="aspectOf">
    <property name="transactionManager" ref="transactionManager" />
</bean>

여기서 문제는 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);
        }
    } 
}

Spring 4를 사용하면 Self autowired가 가능합니다.

@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);
    }
}

이것은 자기 호출에 대한 내 솔루션입니다 .

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

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

    // ...
}

Starting from Java 8 there's another possibility, which I prefer for the reasons given below:

@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();
    }
}

This approach has the following advantages:

1) It may be applied to private methods. So you don't have to break encapsulation by making a method public just to satisfy Spring limitations.

2) Same method may be called within different transaction propagation and it is up to the caller to choose the suitable one. Compare these 2 lines:

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

3) It is explicit, thus more readable.


You can autowired BeanFactory inside the same class and do a

getBean(YourClazz.class)

It will automatically proxify your class and take into account your @Transactional or other aop annotation.


The issue is related to how spring load classes and proxies. It will not work , untill you write your inner method / transaction in another class or go to other class and then again come to your class and then write the inner nested transcation method.

To summarize, spring proxies does not allow the scenarios which you are facing. you have to write the 2nd transaction method in other class

참고URL : https://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo

반응형