Transactional 어노테이션

YJS·2024년 1월 9일
0

Spring Boot 탐구

목록 보기
8/11

😖문제 상황

@Transactional 어노테이션을 추가해서 save 메서드를 직접 호출하지 않고 변경감지로 update해주는 api를 만들었는데 update쿼리가 날아가지 않는 이슈가 발생했다.

문제 코드 )

// controller
@PostMapping("/{id}/object")
public void addOrUpdateObject(@PathVariable long id, @ModelAttribute ObjectRequest request){
	objectService.checkObject(request);
}

//service
public void addOrUpdateObject(ObjectRequest request){
	if(request.getId() == null)
    	addObject(request);
    else
    	modifyObject(request.getId(),request);
}

@Transactional
public void modifyObject(long objectId, ObjectRequest request){
	Object obj = objectRepositsory.findById(objectId).orElseThrow(() -> new NoSuchElementException("존재하지 않는 오브젝트입니다.");
	obj.updateObject(request);
}

🤓문제 해결 과정

step1. 디버깅을 통해 어느 함수에서 문제가 발생하는지 확인했다. (컨트롤러로 잘 들어와서 addOrUpdateObject함수를 호출하는것을 확인함)

step2. 프론트에서 request를 잘못 보낸게 아닌지 확인했다.(addOrUpdateObject함수의 request를 확인하니 변경이 필요한 값들이 잘 들어와 있었음)

step3. modifyObject함수에서 호출한 updateObject함수의 문제가 아닌지 확인했다.(Object타입의 엔티티에서 update함수가 정상적으로 작성되어 있고 변경될 값도 잘 들어오는걸 확인함)

🧐문제의 원인 파악

문제의 원인은 service클래스의 어노테이션에 있었다. DB에 직접 접근하는 함수들을 제외한 나머지 함수들에서는 읽기만 허용하기 위해 어노테이션을 걸어두었는데 해당 어노테이션 때문에 addOrUpdateObject 함수도 읽기 권한만 적용이 된 것이었다.

@Transactional(readOnly = true)

그렇다면 왜 modifyObject함수에는 @Transactional 어노테이션을 붙였는데도 위와 같은 오류가 난 것일까?

@Transactional의 전파 속성
@Transactional 어노테이션에는 여러 옵션들이 있습니다. 그 중에서도 propagation 옵션은 트랜잭션 전파 속성을 설정하는데 사용됩니다. 이 속성은 메서드가 이미 실행 중인 트랜잭션과 어떻게 상호작용하는지를 결정합니다.
@Transactional 어노테이션의 propagation 속성 기본값은 Propagation.REQUIRED입니다. 이것은 "현재 실행 중인 트랜잭션이 있으면 해당 트랜잭션을 사용하고, 없다면 새로운 트랜잭션을 시작한다"는 것을 의미합니다. 따라서 @Transactional 어노테이션을 사용하고 아무런 설정을 하지 않으면, 메서드가 이미 다른 트랜잭션 내에서 호출되는 경우에는 해당 트랜잭션을 사용하게 됩니다.

즉, @Transactional(readOnly = true)를 서비스 클래스나 메서드에 설정하면 해당 트랜잭션은 읽기 전용으로 설정되어 데이터베이스의 변경 작업을 수행하지 않는 것을 나타낸다. 따라서 읽기 작업만 허용되고, 쓰기 작업(데이터 변경)은 허용되지 않는다.

나는 클래스레벨에서 선언을 했기 때문에 서비스 클래스의 모든 메서드에 해당 속성이 전파된 것이다. 따라서 데이터를 수정하려는 시도가 있더라도 해당 트랜잭션은 읽기 전용으로 설정되었기 때문에 데이터 변경 작업이 허용되지 않았던 것이다. 이로 인해 modifyObject 메서드에서의 데이터 변경 작업도 허용되지 않게 되었던 것이다.

🤗해결 방법

@Transactional(readOnly = true)를 사용하고 있는 상황에서 modifyObject 메서드가 수정 작업이 필요하다면, @Transactional(readOnly = true) 옵션을 제거하거나, 해당 메서드 내에서 새로운 트랜잭션을 시작하도록 설정하여 쓰기 작업을 수행하도록 변경해야 한다. 나는 클래스 레벨의 어노테이션은 유지한채로 modifyObject의 경우에만 쓰기 작업을 허용하고 싶었기 때문에 새로운 어노테이션을 추가하기로 했다.

서비스 클래스에 있는 @Transactional(readOnly = true)는 클래스 내의 모든 메서드에 해당 트랜잭션 속성을 적용하는 것이지만, 메서드에 별도의 @Transactional 어노테이션을 추가하면 그 메서드에 대해서는 해당 @Transactional 어노테이션의 설정이 우선시 된다.
따라서 checkObject 메서드에 별도의 @Transactional 어노테이션을 추가하면 해당 메서드에 대한 트랜잭션 속성이 @Transactional(readOnly = true)를 덮어씌우게 된다. 그렇기 때문에 checkObject 메서드 내에서는 새로운 트랜잭션이 생성되고, 그 트랜잭션의 전파 속성은 @Transactional 어노테이션에 설정한대로 동작하게 된다.

해결된 코드)

//service
@Transactional
public void addOrUpdateObject(ObjectRequest request){
	if(request.getId() == null)
    	addObject(request);
    else
    	modifyObject(request.getId(),request);
}

@Transactional
public void modifyObject(long objectId, ObjectRequest request){
	Object obj = objectRepositsory.findById(objectId).orElseThrow(() -> new NoSuchElementException("존재하지 않는 오브젝트입니다.");
	obj.updateObject(request);
}
profile
우당탕탕 개발 일기

0개의 댓글