spring

같은 클래스에서 @Transcation 메서드 호출할때 문제점

키위먹고싶다 2022. 7. 8. 21:43

문제점

같은 클래스에서 A메서드가 @Transcation 메서드B를 호출하는 경우 B메서드의 commit이 작동하지 않아 update 가 계속해서 문제가 발생했다. 

public ProfileResponse updateMember(String memberId, ProfileUpdateRequest profileUpdateRequest) {

    return userLevelLock.LockProcess(new Object(){}.getClass().getEnclosingMethod().getName() +
            profileUpdateRequest.getMemberId(), () -> ProfileResponse.from(updateMemberInner(memberId, profileUpdateRequest)));
}

@Transactional
public Member updateMemberInner(String memberId, ProfileUpdateRequest profileUpdateRequest) {

    Member member = findMember(memberId);

    duplicateIdChecking(profileUpdateRequest.getMemberId());

    member.updateProfile(
            profileUpdateRequest.getMemberId(),
            profileUpdateRequest.getMemberName(),
            profileUpdateRequest.getPhoneNumber(),
            profileUpdateRequest.getEmail(),
            profileUpdateRequest.getMemberBirth());

    return member;
}

 

특히 update 하는 부분은 영속성 컨텍스트 안에서 변경감지가 일어나면 트랜잭션 종료시 플러시 호출과 동시에 스냅샷을 비교하여 update 쿼리를 날리는데  이상하게 계속 update쿼리가 생기지 않았다. 

 

원인

원인은 @Transaction의 AOP때문인데 스프링은 AOP적용시 대상 객체 대신 프록시 객체를 빈으로 등록하고 주입한다. 그렇기 때문에 대상 객체를 직접 호출할때는 프록시를 거쳐 문제가 발생하지 않지만 대상 객체 내부에서 메소드를 호출 하면 프록시를 거치지 않고 대상 객체가 직접 메서드를 호출하여 프록시를 거치지 않기 때문에 당연히 AOP 적용도 안되고 트랜잭션 commit또한 안된다. 

 

해결

private final ObjectProvider<MemberService> memberServiceObjectProvider;
public ProfileResponse updateMember(String memberId, ProfileUpdateRequest profileUpdateRequest) {

    return userLevelLock.LockProcess(new Object(){}.getClass().getEnclosingMethod().getName() +
            profileUpdateRequest.getMemberId(), () -> ProfileResponse.from(memberServiceObjectProvider.getObject().updateMemberInner(memberId, profileUpdateRequest)));
}

@Transactional
public Member updateMemberInner(String memberId, ProfileUpdateRequest profileUpdateRequest) {

    Member member = findMember(memberId);

    duplicateIdChecking(profileUpdateRequest.getMemberId());

    member.updateProfile(
            profileUpdateRequest.getMemberId(),
            profileUpdateRequest.getMemberName(),
            profileUpdateRequest.getPhoneNumber(),
            profileUpdateRequest.getEmail(),
            profileUpdateRequest.getMemberBirth());

    return member;
}

 

내부 호출시 발생하는 문제점을 해결하기 위한 간단한 방법은 자기 자신을 주입받는 것인데 AOP가 적용된 대상을 주입받으면 주입받은 대상은 프록시 객체이므로 주입받은 자기 자신의 객체는 프록시 객체가 된다. 그래서 프록시를 통해 AOP를 적용할 수 있지만 생성자 주입시 본인이 생성하면서 주입 해야하기 때문에 순환 사이클이 발생하므로 오류가 발생한다. 

 

그래서 생성자 주입을 성공시키기 위해 스프링 빈을 지연해서 조회하는 방법을 선택하였고, ObjectProvider를 통해 스프링 빈을 조회하는 것이 빈 생성 시점이 아니라 실제 객체를 사용하는 시점으로 지연시켜 순환사이클 문제를 해결하였다. 

 

결론

이거때문에 이틀을 날렸다. 검색할때 키워드를 잘 쳐야한다.