ㅅㅇ
[Spring boot] @Transactional 어노테이션 본문
1. Transactional 이란 ?
데이터베이스 트랜잭션은 데이터베이스 관리 시스템 또는 유사한 시스템에서 상호작용의 단위. 데이터에 대한 하나의 논리적 실행 단계라 할 수 있다.
여기서 단위라는 말을 사용했는데, 쉽게 말하면 더 이상 쪼개질 수 없는 최소의 연산
트랜잭션의 목적은 트랜잭션을 조작하는 기능은 사용자가 데이터 베이스 완전성을 유지하는데 확신을 심어주게 하는 것이다.
어떤 연산에 트랜잭션이 보장된다면, DB에서 의도치 않은 값이 저장되거나 조회되는 것을 막을 수 있다.
2. @transactional
@transactional은 클래스나 메서드에 붙여줄 경우, 해당 범위 내 메서드가 트랜잭션이 되도록 보장해준다.
직접 객체를 만들 필요 없이 선언만으로도 관리를 용이하게 해주기 때문에 선언적 트랜잭션이라고 한다.
SpringBoot 의 경우 선언적 트랜잭션에 필요한 여러 설정이 이미 되어있는 탓에 더 쉽게 사용할 수 있다.
3. SpringBoot 에서의 선언적 트랜잭션
- 스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용한다.
서비스 클래스에서 @transactional 을 사용할 경우, 해당 코드 내의 메서드를 호출할 때 영속성 컨텍스트가 생기게 된다. - 영속성 컨텍스트는 트랜잭션 AOP가 트랜잭션을 시작할 때 생겨나고, 메서드가 종료되어 트랜잭션 AOP가 트랜잭션을 커밋할 경우 영속성 컨텍스트가 flush되면서 해당 내용이 반영되기에 이후 영속성 컨텍스트 역시 종료가 되게 된다. 이러한 방식으로 영속성 컨텍스트를 관리해 주기 때문에, @transactional을 쓸 경우 트랜잭션의 원칙을 정확히 지킬 수 있다.
4. 예제
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public AccountDTO login(LoginDTO login) throws Exception {
AccountEntity account = accountRepository.findByAccountEmail(login.getAccountEmail())
.orElseThrow(() -> new ServerErrorRequestException("아이디를 잘 못 입력하셨습니다. 입력하신 내용을 다시 확인해주세요."));
boolean matches = passwordEncoder.matches(login.getAccountPassword(), account.getAccountPassword());
if (!matches) {
throw new ServerErrorRequestException("비밀번호를 잘못 입력했습니다. 입력하신 내용을 다시 확인해주세요.");
}
return AccountDTO.toDTO(account);
}
}
- @transactional 메소드 를 사용하게 되면, login() 메서드가 실행될 경우 해당 메서드는
- 연산이 고립되어, 다른 연산과의 혼선으로 인해 잘못된 값을 가져오는 경우가 방지된다.
- 연산의 원자성이 보장되어, 연산이 도중에 실패할 경우 변경사항이 Commit되지 않는다.
=> 위 속성이 보장되기 때문에, 해당 메소드를 실행하는 동안 오류가 발생해도 rollback 해서 DB 에 해당 오류로 인한 결과가 반영되지 않도록 할 수 있다.
5. 유의점
- 같은 트랜잭션 내에서 여러 EntityManager를 쓰더라도, 이는 같은 영속성 컨텍스트를 사용한다.
- 같은 EntityManager 를 쓰더라도, 트랜잭션이 다르면 다른 영속성 컨텍스트를 사용한다.
- 트랜잭션은 보통 서비스 계층에서 시작하므로, 서비스 계층이 끝나는 시점에 트랜잭션이 종료되면서 영속성 컨텍스트도 함께 종료된다.
- 따라서, 조회한 entity가 Service/Repository 계층에서는 영속성 컨텍스트에서 관리되며 영속 상태를 유지하지만, 프리젠테이션 계층 controller나 view 에서는 준영속 상태가 된다!!
=> 준영속 상태는 엔티티가 영속성 컨텍스트에서 분리된 것을 뜻하는데, 이 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다. 그리고 이때의 문제는 바로 지연 로딩(Lazy Loading)이다.
@Entity
public class Post {
@Id @GeneratedValue
private int postId;
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩 전략
private Account accountId;
}
- 이 엔티티가 Service 에서 조회된다고 해도 서비스의 해당 find 메소드가 종료된다면 영속성 컨텍스트가 닫히고 반환된 entity 는 준영속 상태가 된 것이다.
lazy loading 전략을 사용했으므로 비어있는 프록시 객체로 존재했는데, 해당 객체에서 실제로 값을 뽑아 쓰려고 하니 예외가 발생한다. 만약 준영속이 아니었다면 영속성 컨텍스트를 통해 Account 를 조회하고 값을 반환했을 것이다.
=> 이러한 준영속 상태의 lazy loading 이슈를 해결할 방법에는 여러 방법이 있으나, 가장 간단하게 서비스와 컨트롤러 간에 DTO 를 사용하면 된다. Entity 는 오로지 서비스, 레퍼지토리 에서만 쓰고 그 외엔 DTO 로 해결하면 되는 것.
'SW_STUDY > SpringBoot' 카테고리의 다른 글
[SpringSecurity JWT] 인증인가 구현 - 회원가입, 로그인, 토큰을 통한 접근 (0) | 2023.03.29 |
---|---|
[Spring boot] @PostConstruct 어노테이션 (0) | 2023.03.08 |
[Spring Boot, Java] Object ↔ JSON 문자열 : Gson (0) | 2023.03.08 |
[Spring Boot, Java] Entity ↔ DTO 변환 : 자바 코드 매핑 & ModelMapper (0) | 2023.03.08 |
[Spring Boot] 스프링 컨테이너, Bean 등록 및 사용하기 (0) | 2023.03.08 |