스프링부트 강좌 40강(블로그 프로젝트) - ResponseDto 수정
스프링부트 강좌 41강(블로그 프로젝트) - DB격리수준 READ COMMIT
트랜잭션 : 일이 처리되기 위한 가장 작은 단위
유튜브 1강을 위한 것
1. 강의 준비
2. 영상 찍기
3. 영상 업로드
이 세가지가 묶여서 하나의 트랜젝션이 될 수 있다. 여러개의 트랜잭션을 묶어서 하나의 트랜젝션이 될 수 있고 이것을 서비스라고 부른다.
DB 격리 수준이 무엇인가?..
1. 오라클
read commit
A라는 얘가 트랜젝션을 시작한다.
2. MySQL
repeatable read
둘은 select시에 트랜잭션을 걸게 되면 차이가 들어난다. 무슨 차이가 있는지 살펴보자.
정합성문제 부정합 - 동일한 쿼리의 결과가 다르다.
PHANTOM READ - 동일한 쿼리 마다 결과가 보였다 안보였다 하는 것.
PHANTOM READ (데이터가 보였다 안보였다)
PHANTOM READ 이 문제를 해결을 하려면 repeatable read 방식을 사용해야 한다.
-> 정합성이 깨짐
Mysql은 InnoDB 스토리지 엔진 -> Repeatable read 이상 사용 -> 부정합이 발생하지 않는다.
Repeatable read : 자기 트랜잭션 번호보다 낮은 로그를 보고 select 한다.
정합성을 유지할 수 있어야 한다.
스프링할 때 정합성을 유지하기 위해 transational을 붙인다.
3. 스프링부트의 트랜잭션
세션의 시작은 서블릿이 시작되는 시점 부터~ (세션은 영속성 컨텍스트를 포함)
트랜잭션의 시작은 서비스 레이어부터, JDBC 커넥션도 이 시점부터.
트랜잭션의 종료는 서비스 계층에서 종료, JDBC 커넥션도 이 시점 부터 종료.
세션은 컨트롤러 영역까지 끌고 가기 때문에 영속성이 보장되어 select가 가능해지고 lazy-loading이 가능해진다.
스프링부트 강좌 44강(블로그 프로젝트) - 스프링의 전통적인 트랜잭션
JDBC커넥션과 트랜잭션의 시작이 Controller 진입직전!!
JDBC커넥션과 트랜잭션의 종료가 Controller 종료시점!!
스프링부트 강좌 45강(블로그 프로젝트) - 스프링 JPA의 OSIV 전략
user Interface 를 controller 라고 생각
yaml 설정에서 jpa를 open-in-view: true 로 설정해야 한다. 이때부터는 lazy 모드가 가능하다.
이게 무슨 뜻이냐면..?
user Interface
Persistence context의 부분이 컨트롤러라고 생각하면 된다. 여기서 영속성 컨텍스트가 열린다.
서비스가 실행될 때 트랜잭션과 jdbc 커넥션이 열린다. 그 다음에 다시 돌아와서 서비스가 종료될 때 트랜잭션이 종료되면서 jdbc 커넥션이 끊긴다. 그때 이제 커밋이 된다. 커밋이 되었는데 컨트롤러 단에서는 영속성 컨텍스트가 아직 열려있기 때문에 여기서 필요하면,
다시 해당 객체를 호출하면 해당 객체가 다시 jdbc 커넥션을 열어서 셀렉트를 다시 해서 돌려준다. 이때 이를 lazy 모드라고 한다.
영속성을 프리젠테이션 계층까지 가져간다. 트랜잭션은 Service계층에서 종료된다. Transaction이 종료된 후에도 Controller의 Session이 close되지 않았기 때문에, 영속 객체는 Persistence 상태를 유지할 수 있으며, 따라서 프록시 객체에 대한 Lazy Loading을 수행할 수 있게 된다.
버전 2.0부터 스프링 부트는 기본적으로 OSIV가 활성화되어있을 때 경고를 발행하므로 프로덕션 시스템에 영향을 주기 전에 이 문제를 발견 할 수 있다. -> 이건 무슨 뜻?
스프링부트는 기본적으로 open in view 가 기본적으로 true로 설정되어 있다. 만약에 open in view를 사용하고 싶지 않다면 false로 한다. false로 하게 되면 Persistence context 시작 지점이 Service 단으로 내려가게 된다. 그래서 컨트롤러에서 아무리 lazy로드를 하려고 해도 되지 않는다.
서블릿 필터에서 Session 을 오픈하고(영속성 컨텍스트를 오픈하고) 트랜잭션을 시작하던 전통적인 방식의 OPEN SESSION IN VIEW 패턴과 달리
SpringMVC 에서 제공하는 OpenSessionInViewFilter 는 필터 내에서 Session 은 오픈하지만 트랜잭션은 시작하지 않는다. 따라서 서블릿 필터 안에서는 커넥션 풀로부터 JDBC 커넥션을 얻을 필요가 없다.
hibernate.enable_lazy_load_no_trans: true
처음에는 시작을 전통적인 방식에서는 이 세개를 처음부터 시작한다. 컨트롤러가 시작되기 전에 이 세개가 같이 시작되고 컨트롤러가 종료될 때 이 세개가 같이 종료된다. 그렇게 하면 JDBC 커넥션의 시간이 늘어나기 때문에 그걸 줄이기 위해서 서비스 단에서 트랜잭션과 JDBC를 시작시키고 종료를 시킨다. 근데 Persistence context의 유지만 컨트롤러 단에서부터 시작하고, 컨트롤러 단이 끝날 때까지 유지를 해버리면 lazy로드가 가능하다는 것이다.
[정리]
세션의 시작은 서블릿이 시작되는 시점 부터~ (세션은 영속성 컨텍스트를 포함)
트랜잭션의 시작은 서비스 레이어부터, JDBC 커넥션도 이 시점부터.
트랜잭션의 종료는 서비스 계층에서 종료, JDBC 커넥션도 이 시점 부터 종료.
세션은 컨트롤러 영역까지 끌고 가기 때문에 영속성이 보장되어 select가 가능해지고 lazy-loading이 가능해진다.
이 방법을 가능하게 하기 위해서는 open in view를 true로 설정해야 한다.
스프링컨테이너 안에 컨트롤러, 서비스, 레파지토리가 있다. 그리고 영속성 컨텍스트가 있다. 영속성 컨텍스트 안에는 1차 캐시가 있고, 그 옆에는 DB가 있다.
사용자가 request 요청을 하면 보통 세션이 열린다고 한다. 세션이 시작되었다는 것은 영속성 컨텍스트가 시작되었다는 것이다.
request가 일어나면 컨트롤러 진입 직전에 서블릿 필터 내에서 세션이 시작된다. 세션이 시작되면 당연히 영속성 컨텍스트가 만들어진다.
그리고 컨트롤러로 요청을 하고 서비스가 요청할때, 이 시점에
c ---JDBC 커넥션, 트랜잭션 --> S
JDBC 커넥션이 시작되고, 트랜잭션에 시작된다. 그 이후 다시 요청을 해서 레파지토리에서 셀렉트를 하든 무언가를 할 것이다. 1차 캐시에 없으면 디비에서 가져와서 응답을 받아서 1차캐시 안에 객체가 만들어지는데,
이게 선수 객체라면 선수 객체가 들고있는 연결되어있는 팀객체가 있을 때 이걸 전략이면 팀객체를 바로 들고 오겠지만, 레이지 전략이면 팀객체를 들고오는 게 아니라 팀 객체에 대한 프록시를 들고 온다. 가짜 객체이다. 그리고 연결을 해놓고, 얘를 돌려준다. 서비스가 받아서 업데이틀릏 하든 인서트를 하든 지지고 볶고를 한다. 서비스가 종료되는 시점에 jdbc 커넥션과, 트랜잭션이 종료가 된다. 그래도 아직 영속성 컨텍스트는 살아있다.
그래서 컨트롤러에서 만약에 팀객체를 가져오고 싶다면, 팀 객체를 호출하면 프록시 객체가 실제 팀 객체로 변경이 된다. 다시 데이터베이스에 접근해서 jdbc 커넥션을 잠깐 열어서 셀렉트 해서 들고 온다는 것이다.
이때 트랜잭션이 다시 시작되는 게 아니기 때문에 업데이트 인서트 같은 커밋이 가능한 것들은 수행되지 않는다. 딱 셀렉트만 가능하다.
이제 컨트롤러가 들고 있는 객체는 선수와 팀 객체가 될 것이다.
전통적인 방식에서는 리퀘스트가 시작하는 시점에 jdbc, 트랜잭션, 영속성 컨텍스트가 다 시작되고, 컨트롤러가 끝나는 시점에 jdbc, 트랜잭션, 영속성 컨텍스트 함께 종료가 되었다.
이게 조금씩 업그레이드가 되어서
스프링 2.0 부터는 다음과 같은 로직이 실행되고 이게 디폴트 값으로 open-in-view:true 로 설정되어있다. 이것을 끄고 싶다면 false로 바꾸면 되는데, 영속성 컨텍스트의 종료가 c <-- ㅇㅇㅇㅇ --- s 에서 된다. 그렇기 때문에 컨트롤러에서 아무리 다시 레이지 로드를 해서 팀을 불러올려고 해도 영속성이 닫혀 있으니까 레이지 로드를 할 수 없다.
-이 글은 유투버 겟인데어의 스프링 부트 강좌를 바탕으로 정리한 내용입니다.-