Transactional 정리
@Transactional이란?
✔️ 개념
하나의 트랜잭션 단위로 모든 작업이 성공하면 커밋, 하나라도 실패하면 롤백.
주로 Service 계층에서 사용됨.
💚 테스트에서 @Transactional을 사용하면?
✔️효과
테스트 메서드에서 @Transactional을 붙이면, 테스트가 끝난 뒤 DB 변경사항이 자동으로 롤백됨.
즉, 테스트가 DB에 영향을 주지 않음 -> 항상 깨끗한 상태 유지 가능.
테스트 프레임워크 레벨에서 트랜잭션을 무조건 롤백하도록 되어 있다.
@Transaction은 프록시 기반 AOP로 동작한다.
Spring이 만든 Proxy 객체를 통해 호출될 때만 트랜잭션이 적용된다.
@Service
@Transactional
public class MyService {
public void save() {
// 트랜잭션 시작
}
}
Spring Context (ApplicationContext)
└─ MyService proxy (@Transactional 감싼 가짜 객체)
└─ 내부적으로 진짜 MyService 호출
Spring Container(ApplicationContext)에 등록된 프록시를 통해 호출될 때만 트랜잭션이 적용된다.
✅ 예시 1: 정상 작동하는 경우 (외부 Bean 간 호출)
@Component
public class Controller {
@Autowired
private MyService service;
public void handleRequest() {
service.save(); // ✅ 프록시를 통해 호출 → 트랜잭션 O
}
}
❌ 예시 2: 트랜잭션이 동작하지 않는 경우 (자기 자신 내부 호출)
@Service
public class MyService {
@Transactional
public void outer() {
inner(); // ❌ 직접 호출 → 프록시 통과 X → 트랜잭션 적용 안 됨
}
@Transactional
public void inner() {
// 여기에 트랜잭션 안 걸림
}
}
✅ 해결책
❗ 내부 호출에서도 트랜잭션이 작동하도록 하려면?
@Autowired
private MyService self;
public void outer() {
self.inner(); // ✅ ApplicationContext에서 주입된 프록시 → OK
}
@Transactional(readOnly = true) readOnley = true : 읽기 전용
CRUD에서 CUD 동작 X / only READ만 가능
JPA에서 CUD 스냅샷 저장, 변경감지를 하지않기때문에 (성능 향상)
CQRS를 하는 이유
COMMAND 와 READ를 분리하자
READ라는 행위가 보통 압도적으로 많다.
Master DB/ Slave DB( 복제본) 분리해서 Read는 Slave에서 , CUD는 Master DB에
@Transactional(readOnly = true)
public List<ProductResponse> getSellingProducts() {
List<Product> products = productRepository.findAllBySellingStatusIn(ProductSellingStatus.forDisplay());
return products.stream()
.map(product -> ProductResponse.of(product))
.collect(Collectors.toList());
}
// 동시성 이슈 때문에
// 실제 서비스에서는 UUID 활용하는게 좋을 수도
@Transactional
public ProductResponse createProduct(ProductCreateRequest request) {
String latestProductNumber = productRepository.findLatestProduct();
Product product = request.toEntity(request,createNextProductNumber(latestProductNumber));
Product savedProduct = productRepository.save(product);
ProductResponse productResponse = ProductResponse.of(savedProduct);
return productResponse;
}
private String createNextProductNumber(String latestProductNumber) {
if (latestProductNumber == null) {
return "001";
}
int nextNumber = Integer.parseInt(latestProductNumber) + 1;
return String.format("%03d", nextNumber);
}
Last updated