article thumbnail image
Published 2022. 6. 6. 03:03

로그인을 구현하지 않았기 때문에 게시글을 작성하는 작성자에 대한 writerId 를 전달 받아와야만 한다.

writerId 를 받아와 게시글을 작성하는 서비스 메소드 뿐만 아니라,

후에 로그인 구현을 할 것을 생각하고 ( 이게 잘못되었던 걸까..) Writer 를 인자로 받는 메소드 또한 정의해 놓았다. 컨트롤러에서부터 Authentication 이라던가 Authmember 라던가 LoginMember 라던가 User 객체를 받아올 것으로 생각 했다.

 

그래서 이 메소드를 내부적으로 호출하도록 하였는데 여기서 @Transactional 과 this…() 에 대한 지적을 받았다

	@Transactional
	public PostDto.PostInfo store(String title, User writer, String content) {
		Post post = new Post(title, writer, content);

		return postConverter.entity2Info(
			postRepository.save(post)
		);
	}

	@Transactional
	public PostDto.PostInfo store(String title, Long writerId, String content) {
		try {
			User writer = userService.getById(writerId);
			// FIXME : same class 내에서 @Transactional method 호출시 , 트랜잭션이 적용되지 않는 문제존재. 현재 로그인 기능이 구현되어있지 않기 때문에 writerId 를 받아오도록 하고 있어 생기는 문제
			return this.store(title, writer, content);
		} catch (NotExistException e) {
			log.info("존재하지 않는 사용자의 게시글 작성 요청 : writerId {}", writerId);

			throw new AuthorizationFailException();
		}
	}

동일한 클래스내에서 @Transactional annotated method 를 호출할 경우 동작하지 않을 것이라는 것이었다.

그래서 이에 대해 찾아보았다

self-invocation

출처 : https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-understanding-aop-proxies

However, once the call has finally reached the target object (the SimplePojo  reference in this case), any method calls that it may make on itself, such as this.bar()  or this.foo(), are going to be invoked against the this reference, and not the proxy

this 참조를 통해 self invocation 을 하는 경우에는, 프록시 객체가 아닌 this 참조 객체에게 호출을 하는 것이 된다. → 따라서 B 메소드에 어노테이트한 @Transactional 은 적용되지 않는다.

 

self-invocation 으로 인해, 프록시 객체에 대한 요청이 아닌 , this 참조 객체에 직접 호출을 하게 된다 → 호출되는 메소드에 대한 @Transactional 이 적용되지 않는다. @Transactional 은 스프링 AOP 를 사용하는 기술로 AOP는 프록시를 통해 구현하고 있기 때문이다.

 

따라서 다음과 같은 상황들이 존재할 것이다

  • @Transactional A(), @Transactional B() 
    • 이 경우에는 A 의 트랜잭션이 B 로 그대로 전파된다. 따라서 B 의 @Transactionl 에 서 별개의 트랜잭션 격리 레벨, 전파레벨 등을 설정했던 경우 이와 같은 것이 적용되지 않는다는 것이다

  • A() → @Transactional B()
    • B 내부에서는 @Transactional 어노테이션이 달린 DAO method 를 여러개 호출하고 있는 상황. B의 트랜잭션을 통해 이 연산들을 하나의 작업 단위로 묶고자 @Transactional 을 한 것이었다면, DAO 각각의 연산들은, 결국 각각의 트랜잭션에서 수행된다.

(추가) 참고 : JPA test 에서 @Transactional 을 사용할 경우 주의하라는 글에서의 상황

https://tecoble.techcourse.co.kr/post/2020-08-31-jpa-transaction-test/

 

JPA 사용시 테스트 코드에서 @Transactional 주의하기

서비스 레이어()에 대해 테스트를 한다면 보통 DB…

tecoble.techcourse.co.kr

이런 글을 본 적이 있다. 

 

Test method 에는 @Transactional 을 사용한 경우 -> 실제 서비스 메소드 B 에서는 @Transactional 을 깜빡한 경우더라도 정상적으로 테스트가 나와버리기 때문에, 사용에 주의하라는 것임을 복기 하자. 

즉 아래와 같은 그림이 되는 것이다. 

 

현재 상황에 대한 결론

문서에서는

The best approach (the term "best" is used loosely here) is to refactor your code such that the self-invocation does not happen

self - invocation 이 일어나지 않도록 하는 것이 최선이라고 하고 있다. ( 물론 항상 이렇게 할 수 없기에 다른 방법도 제사하고 있다 )

  • 해당 메소드들을 서로 다른 서비스로 분리하여, Proxy 에 대한 호출이 일어날 수 있도록 만들어주도록 하면 될 것 같다.

하지만 나의 경우는 로그인을 구현한다면 호출하지 않을 메소드, 삭제할 메소드고, 현재로서는 똑같은 트랜잭션에서 실행되어도 문제가 없는 부분이기 때문에, self-invocation 의 문제만을 인지하고 넘어가기로 했다.

 

 

추가 -> programmatic transaction 을 사용할 수도 있다

https://cocoomoo.tistory.com/124

복사했습니다!