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


109

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

TransactionTemplate접근 방식을 살펴보십시오 : stackoverflow.com/a/52989925/355438
Lu55

자체 호출이 작동하지 않는 이유에 대해서는 8.6 프록시 메커니즘을 참조하십시오 .
Jason Law '

답변:


99

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>

정보 주셔서 감사합니다. 지금은 코드를 리팩토링했지만 AspectJ를 사용하여 예제를 보내거나 유용한 링크를 제공해 주시겠습니까? 미리 감사드립니다. 마이크.
Mike

내 대답에 트랜잭션 특정 AspectJ 구성을 추가했습니다. 도움이되기를 바랍니다.
Espen

10
잘 됐네요! Btw : 내 질문을 베스트 답변으로 표시하여 몇 가지 포인트를 주시면 좋겠습니다. (녹색 확인 표시)
Espen

2
봄 부팅 구성 : @EnableTransactionManagement (mode = AdviceMode.ASPECTJ)
VinyJones

64

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

3
이 경로를 선택하고 (좋은 디자인이든 아니든 다른 문제) 생성자 주입을 사용하지 않는 경우이 질문
Jeshurun

UserService싱글 톤 범위가 있다면 어떨까요? 같은 물건이라면 어떨까요?
Yan Khonski

26

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

2
최고의 답변! Thx
mjassani 2019

2
내가 틀렸다면 저를 정정하십시오. 그러나 그러한 패턴은 작동하지만 실제로 오류가 발생하기 쉽습니다. Spring 기능의 쇼케이스와 비슷합니다. "이 빈 호출"동작에 익숙하지 않은 사람이 실수로 자체 자동 연결 빈을 제거 할 수 있으며 (결국 "this."를 통해 메서드를 사용할 수 있음) 첫눈에 감지하기 어려운 문제가 발생할 수 있습니다. 그것이 발견되기 전에 찌르는 환경으로 만들 수도 있습니다).
pidabrow

2
@pidabrow 당신이 맞습니다, 그것은 거대한 안티 패턴이며 처음에는 분명하지 않습니다. 따라서 가능하다면 피해야합니다. 같은 클래스의 메서드를 사용해야하는 경우 AspectJ
Almas Abdrazak

21

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) 명시 적이므로 더 읽기 쉽습니다.


대단합니다! Spring이 주석으로 도입하는 모든 함정을 피할 수 있습니다. 그것을 사랑하십시오!
Frank Hopkins

TransactionHandler하위 클래스로 확장 하고 하위 클래스가 TransactionHandler수퍼 클래스 에서이 두 메서드를 호출하는 경우 @Transactional의도 한대로 여전히 이점을 얻을 수 있습니까?
tom_mai78101

6

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

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

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

    // ...
}

0

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

getBean(YourClazz.class)

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


2
그것은 나쁜 습관으로 간주됩니다. 빈을 재귀 적으로 주입하는 것조차 더 좋습니다. getBean (clazz) 사용은 코드 내부의 Spring ApplicationContext 클래스에 대한 긴밀한 결합 및 강력한 종속성입니다. 또한 빈을 스프링 랩핑하는 경우 클래스별로 빈을 가져 오지 못할 수 있습니다 (클래스가 변경 될 수 있습니다).
Vadim Kirilchuk 2015 년

0

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

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


0

다음은 동일한 클래스 내에서 메서드 호출을 약간만 사용하는 소규모 프로젝트에 대해 수행하는 작업입니다. 동료들에게는 이상하게 보일 수 있으므로 코드 내 문서를 적극 권장합니다. 그러나 그것은 싱글 톤과 함께 작동하고 , 테스트하기 쉽고, 간단하고, 빠르게 달성 할 수 있으며, 완전한 AspectJ 계측을 아끼지 않습니다. 그러나 더 많이 사용하려면 Espens 답변에 설명 된 AspectJ 솔루션을 조언합니다.

@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class PersonDao {

    private final PersonDao _personDao;

    @Autowired
    public PersonDao(PersonDao personDao) {
        _personDao = personDao;
    }

    @Transactional
    public void addUser(String username, String password) {
        // call database layer
    }

    public void addUsers(List<User> users) {
        for (User user : users) {
            _personDao.addUser(user.getUserName, user.getPassword);
        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.