[java] 동일한 클래스 내의 메서드에 의한 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  UserRepository repository;

    @Autowired
    private UserService userService;

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

    @Override
    public void save(Users user) {
        repository.save(user);
        userService.update(1);
    }
}


답변

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) 다른 트랜잭션 전파 내에서 동일한 메서드 를 호출 할 수 있으며 적절한 메서드 를 선택하는 것은 호출자 에게 달려 있습니다. 다음 두 줄을 비교하십시오.

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

3) 명시 적이므로 더 읽기 쉽습니다.


답변

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

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

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

    // ...
}


답변

동일한 클래스 내에서 BeanFactory를 자동 연결하고

getBean(YourClazz.class)

자동으로 클래스를 프록시 화하고 @Transactional 또는 기타 aop 주석을 고려합니다.


답변

이 문제는 스프링로드 클래스 및 프록시 방법과 관련이 있습니다. 다른 클래스에서 내부 메서드 / 트랜잭션을 작성하거나 다른 클래스로 이동 한 다음 다시 클래스로 와서 내부 중첩 트랜잭션 메서드를 작성하기 전까지는 작동하지 않습니다.

요약하면 스프링 프록시는 현재 직면하고있는 시나리오를 허용하지 않습니다. 다른 클래스에 두 번째 트랜잭션 메서드를 작성해야합니다.