영속성 컨텍스트는 트랜잭션 당 하나가 생성된다. 따라서 @Transactional을 붙여주지 않으면, db 관련 로직이 여러개 있을 때 각각 영속성 컨텍스트가 생성된다.
그래서 실제로 아래와 같은 코드가 있을 때, @Transcational
이 붙어있지 않아 save
메서드 실행 후, changeName
을 해도 foundUser
의 이름은 max
가 아닌 air
가 되어야한다고 생각했다.
public void update() {
User user = userRepository.save(new User("air"));
user.changeName("max");
User foundUser = userRepository.findById(user.getId())
.orElseThrow(IllegalArgumentException::new);
System.out.println("foundUser = " + foundUser.getName());
}
그러나 출력 결과를 보면 foundUser
의 이름은 max
였다. save가 이뤄질 때 영속성 컨텍스트가 끝나기 때문에 changeName
을 해도 반영이 되지 않아야했는데 왜 이름이 바뀌었을까?
아무리 고민해봐도 모르겠어서 제이슨에게 물어봤는데 OSIV라는 키워드를 주셨다.
결론부터 말하면 jpa 사용시 기본적으로 OSIV 옵션이 true이기 때문에 일어나는 현상이었다. application.properties에서 spring.jpa.open-in-view=false
설정을 해주면 위의 update
메서드 실행시 foundUser
의 이름이 max
가 아닌 air
로 출력된다.
위의 예시에서 osiv를 켰었을때 가져온 값은 db에서 가져온게 아니고 영속성 컨텍스트에서 가져온 값이다. 실제로 DB에 접속해보면 값이 air
로 나온다.
그럼 OSIV가 무엇일까? OSIV는 영속성 컨텍스트를 뷰까지 열어두는 기능이다. 따라서 엔티티도 계속 영속 상태로 관리된다.
따라서 위의 경우에 @Transactional이 없었지만 user는 계속 영속 상태로 관리되기 때문에 changeName 메서드가 효력이 있었던 것이다.(실제 db에 반영된건 아니다. 뒤에 @Transactional 이 붙은 메서드 호출시는 쓰기지연 저장소에 쓰여있던 쿼리가 날라가서 실제 db에도 반영된다.)
osiv 켜져있고 @transactional 없을때 -> 영속성 컨텍스트가 열려있어서 엔티티를 변경시켰을때 영속성컨텍스트에는 반영됨. 실제로 바뀌는것은아님(db에는 반영안됨. 쓰기 지연 저장소에 써놓음)
osiv 꺼져있고 @transactional 없을때 -> 영속성 컨텍스트 자체에도 반영안됨.
1) 클라이언트의 요청이 들어오면 영속성 컨텍스트가 생성된다.
2) 그 후, @Transactional이 붙어있는 메서드가 실행될 때 만들어 놓은 영속성 컨텍스트를 가지고 와 트랜잭션 처리를 시작한다.
3) 이 트랜잭션이 끝나면 플러시와 커밋이 일어난다.
4) 여기서 OSIV가 false라면 영속성 컨텍스트가 종료되지만, OSIV가 true라면 영속성 컨텍스트가 계속 살아있다.
OSIV가 true인 경우 데이터베이스 커넥션이 계속 유지되므로 커넥션 비용이 낭비 될 수 있다는 단점이 있다.