Spring @Transactional 사용시 주의할점

janjanee·2022년 3월 10일
0

Spring

목록 보기
2/2
post-thumbnail

트랜잭션 분리 작업을 하면서 발생한 문제들에 대해 정리한 포스트 입니다.

💡 @Transactional 선언전 트랜잭션
→ 트랜잭션 기능이 포함된 프록시 객체가 생성 자동으로 commit or rollback

💡 @Transactional propagation(전파속성)
→ @Transactional(propagation=Propagation.REQUIRES_NEW)
항상 새로운 트랜잭션을 생성한다. 이미 진행중인 트랜잭션이 있다면 잠깐 보류하고
해당 트랜잭션을 먼저 진행

트랜잭션 분리 작업을 하면서 크게 아래의 두 문제가 발생했다.

  1. 트랜잭션이 적용되지 않는 문제
  2. 새로운 트랜잭션(@Transactional(propagation=Propagation.REQUIRES_NEW))이 걸린 곳에서
    무한로딩이 발생하는 문제

원인은 아래의 문제들이 복합적으로 엮여서 발생했다.


트랜잭션 사용시 주의사항


  1. public 외의 모든 접근제어자는 @Transactional을 선언해도 적용되지 않는다.
// O
@Transactional
public void search(SearchDto searchDto) {
	...
}

// X
@Transactional
protected void search(SearchDto searchDto) {
	...
}

// X
@Transactional
private void search(SearchDto searchDto) {
	...
}

  1. 동일한 클래스 내에서 @Transanctional이 선언되지 않은 메소드에서
    @Transactional이 선언된 메소드를 호출해도 트랜잭션이 적용되지 않음.
@Controller
class PackingController {
		
	packingService.packing();
		
}

@Service
class PackingService {
		
	// 트랜잭션 선언되어있지 않음.
	public void packing() {
		...
		callExternalApi();
		processCompleted();
	}

	@Transactional
	public void callExternalApi() {
		...
	}

	@Transactional(propagation=Propagation.REQUIRES_NEW)
	public void processCompleted() {
		...
	}

}
  • PackingController에서 PackingService의 packing() 메소드를 호출했지만

    packing() 메소드에 @Transactional이 선언되어있지 않기 때문에,

    callExternalApi(), processComplete() 두 메소드의 트랜잭션 역시 적용되지 않은 상태이다.

  • 이 상황의 문제점은 동작해보면 잘 되는것 같지만 트랜잭션이 모두 적용되어있지 않아서 예외가 발생하더라도 데이터가 롤백되지 않는 문제가 발생한다


  1. @Transactional(propagation=Propagation.REQUIRES_NEW) 새로운 트랜잭션은

    동일한 클래스 메소드끼리 호출하면 새로 생성되지 않음. 반드시 다른 클래스 메소드를 호출해야함.

// 문제상황

class PackingService {
		
	@Transactional
	public void packing() {
		...
		callExternalApi();	
	}	

	@Transactional(propagation=Propagation.REQUIRES_NEW)
	public void callExternalApi() {
		...
	}

}
  • packing() 메소드에서 callExternalApi()를 호출해도 동일한 클래스이기 때문에

    새로운 트랜잭션이 시작되지않고 기존 packing() 트랜잭션이 이어서 적용된다.

// 문제해결

class PackingService {
		
	@Transactional
	public void packing() {
		...
		packingApiService.callExternalApi();	
	}

}

class PackingApiService() {
		
    @Transactional(propagation=Propagation.REQUIRES_NEW)
	public void callExternalApi() {
		...
	}

}

  1. 정상적으로 트랜잭션이 적용됐음에도 불구하고

    @Transactional(propagation=Propagation.REQUIRES_NEW) 메소드에서 무한로딩이 발생하는 상황

class PackingApiService {
		
		@Transactional(propagation=Propagation.REQUIRES_NEW)
		public void callExternalApi(ApiDto apiDto) {
				Long packingId = apiDto.getPackingId();
				String name = apiDto.getName();
				saveInvoiceEntity(id, name);
				...
		}

   private void saveInvoiceEntity(Long packingId, String name) {
        PackingEntity packingEntity = packingRepository.getOne(packingId);

        InvoiceEntity invoiceEntity = InvoiceEntity.builder()
                .packing(packingEntity)
                .name(name)
				.build();
        invoiceRepository.save(invoiceEntity);
    }
}

callExternalApi() 메소드 파라미터로 넘어온 apiDto는 이전 트랜잭션의 Querydsl 결과값에서 가져온 dto다.

saveInvoiceEntity() 메소드에서 해당 dto 내부 값으로 Packing엔티티를 조회하고 새로운 엔티티를

저장하는데 이 때, API 응답 무한로딩이 발생한다.

class PackingApiService() {
		
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void callExternalApi(Long packingId) {
		ApiDto apiDto = apiRepository.findApiDtoById(packingId).orElseThrow();
		Long packingId = apiDto.getPackingId();
		String name = apiDto.getName();
		saveInvoiceEntity(packingId, name);
		...
	}

   private void saveInvoiceEntity(Long packingId, String name) {
        PackingEntity packingEntity = packingRepository.getOne(packingId);

        InvoiceEntity invoiceEntity = InvoiceEntity.builder()
                .packing(packingEntity)
                .name(name)
				.build();
        invoiceRepository.save(invoiceEntity);
   }
}

해결방법은 새로운 트랜잭션 내에서 파라미터로 받은 id로 apiDto를 조회 후 다음 로직을 수행하면

정상적으로 동작한다.


References

Spring 동일한 Bean(Class)에서 @Transactional 동작 방식 - Yun Blog | 기술 블로그

[spring] @Transactional 작동 안할때 확인해봐야 할 것

스프링에서 @Transactional 사용시 주의점

[Java]@Transactional Annotation 알고 쓰자

profile
얍얍 개발 펀치

0개의 댓글