@Transactional
스프링 프레임워크에서 특정 메서드 또는 클래스에서 수행되는 ‘트랜잭션’과 관련하여 관리를 위해 사용되는 어노테이션이다.
•
메서드 또는 클래스에 해당 어노테이션을 선언하면 코드 실행 중 트랜잭션 오류 발생 시 트랜잭션이 ‘롤백’ 되고 변경사항이 모두 취소된다.
•
여러 개의 데이터베이스 작업을 하나의 트랜잭션으로 처리해야 할 때 유용하다.
◦
한 메소드에서 여러 개의 업데이트나 삽입 작업을 수행하고, 이 작업들이 모두 성공해야 데이터베이스에 반영하고 싶은 경우 @Transactional을 사용할 수 있다.
트랜잭션
•
데이터베이스 작업을 ‘원자적으로 처리’ 하기 위한 메커니즘
•
여러 개의 데이터베이스 작업을 하나의 논리적 단위로 묶어서 실행하고 모든 작업이 성공적으로 완료되면
‘커밋’하고 실패할 경우 ‘롤백’하는 기능을 제공한다.
•
원자적으로 처리한다?
◦
트랜잭션 내의 모든 데이터베이스 작업이 모두 성공하거나 모두 실패할 때까지 어떤 작업도 부분적으로 적용되지 않는 것을 의미한다.
◦
다시 말해, 트랜잭션은 모든 작업이 완전히 수행되기 전까지 어떤 변화도 발생하지 않도록 보장한다.
@Transaction(readOnly = true)를 왜 붙이는가?
•
우리는 스프링의 AOP를 통해 @Transactional 어노테이션만으로 손쉽게 Service Layer에서 트랜잭션을 걸 수 있다.
•
일반적으로, 조회용 메서드에 대해서는 @Transactional(readOnly = true) 를 설정함으로써 성능상 이점을 얻을 수 있다고 한다.
◦
JPA 관련
▪
이는 JPA의 영속성 컨텍스트(Persistence Context)가 수행하는 변경 감지(Dirty Checking)와 관련이 있다. 영속성 컨텍스트는 Entity 조회 시 초기 상태에 대한 Snapshot을 저장한다. 트랜잭션이 Commit 될 때, 초기 상태의 정보를 가지는 Snapshot과 Entity의 상태를 비교하여 변경된 내용에 대해 update query를 생성해 쓰기 지연 저장소에 저장한다.
▪
그 후, 일괄적으로 쓰기 지연 저장소에 저장되어 있는 SQL query를 flush 하고 데이터베이스의 트랜잭션을 Commit 함으로써 우리가 update와 같은 메서드를 사용하지 않고도 Entity의 수정이 이루어진다. 이를 변경 감지(Dirty Checking) 라고 한다.
▪
이 때, readOnly = true를 설정하게 되면 스프링 프레임워크는 JPA의 세션 플러시 모드를 MANUAL로 설정한다.
▪
MANUAL 모드는 트랜잭션 내에서 사용자가 수동으로 flush를 호출하지 않으면 flush가 자동으로 수행되지 않는 모드이다.
▪
즉, 트랜잭션 내에서 강제로 flush()를 호출하지 않는 한, 수정 내역에 대해 DB에 적용되지 않는다.
▪
이로 인해 트랜잭션 Commit 시 영속성 컨텍스트가 자동으로 flush 되지 않으므로 조회용으로 가져온 Entity의 예상치 못한 수정을 방지할 수 있다.
▪
또한, readOnly = true를 설정하게 되면 JPA는 해당 트랜잭션 내에서 조회하는 Entity는 조회용임을 인식하고 변경 감지를 위한 Snapshot을 따로 보관하지 않으므로 메모리가 절약되는 성능상 이점 역시 존재한다.
◦
데이터 무결성 보장
▪
해당 속성을 true로 설정함으로써 트랜잭션이 데이터베이스에 대한 변경을 수행하지 않도록 함으로써 데이터 무결성을 보장하는데 도움이 된다. 복잡한 비즈니스 로직을 가진 애플리케이션에서 유용하며, 의도치 않은 데이터 변경을 방지할 수 있다.
◦
코드의 가독성
▪
위에서 아래로 코드를 읽게 되었을 때 가장 먼저 눈에 띄는 것은 어노테이션이다.이 때, readOnly = true라는 옵션이 붙어있다면 직관적으로 '이 메서드는 조회용 메서드구나' 라고 알 수 있다. 즉, 성능상의 이점 뿐 아니라 readOnly = true를 붙임으로써 직관적으로 해당 메서드가 조회용 메서드임을 알 수 있어 가독성 측면에서도 이점을 가진다.
// 예시를 위해 간단하게 작성했다. 실제 구현 시에는 DTO로 Entity를 변환하는 것이 조타
// 1
@Transactional
public Member getMember(Long memberId) {
Optional<Member> member = memberRepository.findById(memberId);
...
..
..
return member;
}
// 2
@Transactional(readOnly = true)
public Member getMember(Long memberId) {
Optional<Member> member = memberRepository.findById(memberId);
...
..
..
return member;
}
Java
복사
@Transactional 사용 시 save가 필요 없는 이유 (Dirty Checking)
•
dirty checking의 단점
◦
JPA에서 업데이트 방식은 dirty checking 으로, 모든 행에 대해서 하나씩 update 쿼리를 날려서 수정한다. 그러므로 성능에 악영향을 줄 수 있다.
◦
Entity에 @DynamicUpdate 어노테이션을 사용하면 수정된 데이터만 동적으로 update query를 날릴 수 있다.