본문 바로가기

코딩/Java, SpringBoot

JPA 책 603p

 

603p의 내용

"스프링 OSIV"는 "기존의 OSIV"를 수정했다. 컨트롤러에서는 엔티티를 변경해도 변경감지가 동작하지 않음. 하지만 예외가 있다. 컨트롤러에서 엔티티의 set함수를 호출한 뒤에 트랜잭션을 시작하면 DB의 내용이 변경된다. 

 

위의 내용이 잘 이해되지 않았다. 알아보자.!

먼저 영속성 컨텍스트와 트랜잭션에 대해 알고 있어야 한다.

 

영속성 컨텍스트 : SQL지연쓰기, snapshot, 엔티티 1차 캐시를 가지고 있다. JPA의 대부분의 기능은 영속성컨텍스트 때문에 가능한 것이다. 트랜잭션은 거들 뿐임.

트랜잭션 : 트랜잭션이 있어야 데이터 쓰기(변경포함)할 수 있다. 트랜잭션 없이 데이터 변경, 쓰기는 불가능하다. 예외발생함.

 

알아야할 것이 스프링 기본전략은 트랜잭션과 영속성 컨텍스트의 생명주기가 같지만(생성될 때 함께 생성되고 소멸할 때 함께 소멸한다.) OSIV에서는 이게 달라진다는 점을 기억하자. 무의식적으로 두개를 같게 생각하면 안된다. 역할과 기능을 확실히 분리해서 알고 있도록 하자.

 

603p는 스프링 OSIV에 대한 설명이다. 

문제는 컨트롤러에서 set을 호출 후 트랜잭션을 시작하는 메소드를 호출하면 컨트롤러에서 호출한 set메소드를 변경감지해서 DB의 내용이 바뀌는 것이다. 아래와 같은 상황임.

 

처음에는 트랜잭션을 begin하기 전에 setName()을 했기 때문에 나중에 시작된 트랜잭션이 commit, flush() 한들 반영이 안된다고 생각했다. 다시말해서 트랜잭션의 begin(), commit() 안에서 변경, 사용된 엔티티의 내용만 커밋되는줄 알았다. 결론적으로는 이게 틀렸다.  

//컨트롤러의 코드
member.setName("이름");//트랜잭션이 시작하기 전에 setName을 호출. 영속성은 살아있고 트랜잭션은 없는 상태

memberService.getMember(id);//트랜잭션 begin, commit이 일어남

왜냐하면 스프링 OSIV는 영속성 컨텍스트가 컨트롤러까지 살아있기 때문에 그렇다. 

 

영속성 컨텍스트가 무엇인가? 1차 캐시, 쓰기지연 SQL저장소, snapshot을 가지고 있다. JPA 대부분의 기능이 영속성 컨텍스트 덕분에 가능하다. 즉 컨트롤러에서 set을 호출하더라도 영속성 컨텍스트는 살아있기 때문에 영속성 컨텍스트의 1차캐시에 바뀐 엔티티가 반영된다. 이후에 트랜잭션이 커밋될 때 flush()를 호출한다. flush()를 할 때 영속성 컨텍스트는 snapshot과 다른 엔티티를 찾아서 update문을 날린다. setName()을 트랜잭션 begin() 이전에 수행했던 이후에 수행했던 상관없다. 단순 snapshot과 다른 엔티티에 대해서 update문을 생성하고 날릴 뿐이다. 트랜잭션의 begin()은 rollback()을 하기 위한 범위일 뿐이라고 이해했다(?). 따라서 flush()는 영속성 컨텍스트를 DB에 반영하고 컨트롤러에서 영속성 컨텍스트는 살아 있었기 때문에 DB에 반영되는 것이 당연하다. 

 

해결방법은 단순히 모든 비즈니스 로직을 호출한 뒤에 엔티티의 set메소드를 사용하면 반영되지 않는다. 지연로딩은 사용가능함.(트랜잭션없이 읽기 기능을 지원한다. nontransactional reads)

 

참고로 위의 상황은 스프링 OSIV를 켰을 때의 상황임을 기억하자.