Springboot

Spring boot/JPA 실습 양뱡향 매핑, 지연 로딩(lazy loading), 영속성 컨텍스트, toString오류와 noSession오류

25G 2021. 7. 2. 17:50

양방향 매핑

 

일단 더미 데이터가 필요해서 생성해준다

자주 쓸 것 같으니까 redme에 추가시켜준다.

 

jpa에서 orm을 해줄 때 위 주소를 /test/post/1을 호출해주면 이 1은 user오브잭트가 아닌데 어떻게 데이터를 호출해줄까?

바로 ManyToOne에서 보고 프라이머리 키인 것을 인식한 후에 해당 오브젝트로 때려주는 것이다. 

근데 만약에 user에서 post를 orm을 하려고 한다면

주석을 꼭 공부할것.

왼쪽@JsonIgnoreProperties({"user"})를 붙여주지 않고 호출하면 아래와 같이 무한 반복되는 것을 볼 수 있다.

오른쪽@JsonIgnoreProperties({"user"})를 붙여주고 호출했을 때다

post를 컬랙션으로 설정한 이유는 유저 한 명은 여러 개의 게시글을 작성할 수 있기 때문이다.

 

onetomany는 user에서 post에 있는 오브젝트를 전부 join 해서 매핑해주는 것이다.

이렇게 해주면 된다.

위와 같이 양방향에서 매핑이 되는 구조가 양방향 매핑이다.

 

 

 

 

지연 로딩(lazy loding)과 영속성 컨텍스트

 

위와 같은 요청이 일어났을 때 일어나는 흐름은 아래와 같다

 

디스패처-> ioc컨트롤러-> 레파지토리-> 영속성 컨텍스트->DB ->영속성 컨텍스트 -> 레파지토리-> ioc컨트롤러

물론 이 사이사이에 더 디테일한 과정이 있다.

 

컨트롤러

1. 주소가 들어오면 분기시키는 것이 역할이다.

2. 요청 body데이터 파싱 (java오브젝트로 변환)

3. 함수 내부 실행

4.  1) ViewResolver 실행(@Controller)

    2) MessgeConverter실행(@RestController)

      2-1) String이면 문자열로 변환

      2-2) 오브젝트면 json으로 변환

 

레파지토리

1. 영속성 컨텍스트에 DB 데이터를 요청해 받아와서 자바 오브젝트로 바꿔줌(jpaAPI)

 

레파지토리는 컨트롤러에 의존할 수 없다. 그래서 의존성을 주입시켜야 한다.(@Repository)

 

영속성 컨텍스트

컨텍스트의 뜻은 문맥 교환이다

컨택스트가 없다는 말은 상황 파악을 할 수 없다는 말이다.

ORM을 사용하는 어떠한 틀이 있는 서버는 대부분 연속성 컨텍스트가 있다. 

EX 1)

1./user/1 요청이 들어왔다

2. 디스패처가 해당 자원의 컨트롤러를 때린다.

3. 레파지토리에서 DB 접근을 위한 함수를 때린다

4. 영속성 컨택스트에서  user오브젝트가 있는지 먼저 찾는다. 최초 요청 시 user오브젝트가 없으면 자바 오브젝트로 변환해서 그대로 들고 있는다. -> 자바 오브젝트를 래파지토리에 넘긴다. -> 데이터를 컨트롤러에 넘긴다. 컨트롤러, 래파지토리 영속성 컨택스트 db이 네 가지 곳에서 데이터가 동기화된다.(이때 이 영역들끼리 데이터를 주고받는 게 아니라 레퍼런스(주소)가 동기화된 상태이다) MessgeConverter를 타고 json이 응답된다.

5. 다시 똑같은 요청이 들어왔다고 가정했을 때 이미 동기화된 데이터가 있기 때문에 캐싱돼서 IO 없이 곧바로 응답된다.

위 Post클래스를 보면 user오브젝트가 선언돼있다 이와 같이 하나의 오브젝트가 별로 부하가 없기 때문에 디폴드 값으로 DB에 있는 데이터를 알아서 user오브젝트가 join 돼서 들고 와준다.

 

 

EX 2)

1./post/1 요청이 들어왔다. 

2. 디스패처가 해당 자원의 컨트롤러를 때린다.

3. 레파지토리에서 DB 접근을 위한 함수를 때린다

4. 영속성 컨택스트에서  post오브젝트가 있는지 먼저 찾는다. 최초 요청 시 없으면 자바 오브젝트로 변환해서 그대로 들고 있는다.. post에 user오브젝트가 있기 때문에 영속성 컨택스트에 저장될 때 user오브젝트를 join한상태로 저장된다. 자바 오브젝트를 래파지토리에 넘긴다. -> 데이터를 컨트롤러에 넘긴다. 컨트롤러, 래파지토리 영속성 컨택스트 db이 네 가지 곳에서 데이터가 동기화된다.(이때 이 영역들끼리 데이터를 주고받는 게 아니라 레퍼런스(주소)가 동기화된 상태이다) MessgeConverter를 타고 json이 응답된다.

5.post에 user오브젝트가 있기 때문에 영속성 컨택스트에 저장될 때 user오브젝트를 join한상태로 저장된다.

 

 

지연 로딩(스프링 기본전략)

 

스프링에선 컬렉션 프레임워크는 호출하기 전까지 데이터를 들고 오지 않는 규칙이 있다. 컬렉션은 한 번에 들고 올 데이터가 많기 때문에 불필요한 자원 소모를 최소화하기 위함(효율을 좋게 하기 위해서)

그렇게 private타입의 컬렉션 프래임워크는 데이터를 들고 있지 않다가 해당 컬랙션에 getter를 호출하는 순간 데이터를 들고 오는 것이다 이것을 지연 로딩이라고 한다. 필요한 순간에만 사용할 수 있도록 프로그래밍을 하기 위해서

상황에 따라서 컬랙션을 호출할지 말지 MessgeConverter에게 알려주는 것이 @JsonIgnoreProperties이다.

 

 

 

Hibernate: select user0_.id as id1_1_0_, user0_.address as address2_1_0_, user0_.email as email3_1_0_, user0_.password as password4_1_0_, user0_.username as username5_1_0_ from user user0_ where user0_.id=?
1
Hibernate: select posts0_.user_id as user_id4_0_0_, posts0_.id as id1_0_0_, posts0_.id as id1_0_1_, posts0_.content as content2_0_1_, posts0_.title as title3_0_1_, posts0_.user_id as user_id4_0_1_ from post posts0_ where posts0_.user_id=?
2

처음부터 user클래스에 있는 posts(컬랙션)을 호출하지 않고 posts를 호출하는 순간에 다시 select드가 되는 것을 볼 수 있다. 이것이 바로 지연 로딩!

만약에 컬렉션을 지연 로딩(lazy loding) 하지 않고 무조건 호출하고 싶을 때 쓰는 방법이 있다.

FetchType.EAGER로 설정해준다.

그럼 하나의 오브젝트를 호출할 때의 쿼리는 어떻게 될까?

Hibernate: select post0_.id as id1_0_0_, post0_.content as content2_0_0_, post0_.title as title3_0_0_, post0_.user_id as user_id4_0_0_, user1_.id as id1_1_1_, user1_.address as address2_1_1_, user1_.email as email3_1_1_, user1_.password as password4_1_1_, user1_.username as username5_1_1_ from post post0_ left outer join user user1_ on post0_.user_id=user1_.id where post0_.id=?

위와 같이 하나의 오브젝트가 호출될 때는 자동으로 join 된다.

하나의 오브젝트는 그렇게 큰 부하가 없기 때문에 바로 데이터를 들고 와 주는 것이다.

 

Console에 나오는 경고문의 이유

 

view(컨트롤러)는 다른 말로 프레젠터라고 한다. open-in-view라는 건 session을 view까지 가지고 오겠다는 것이다. enabled by default라고 서술된 이유는 원래 옛날 스프링에서는 컨트롤러로 가기 전에 session이 사라지는 방식으로 설계된 시스템이었다. 왜냐하면 컨트롤러가 db에 접근하는 건 컨트롤러의 책임에서 벗어나는 것이기 때문에 그렇다. 하지만 view까지 session 들고 오는 이유는 lazyLoding을 하기 위해서이다. 그래서 스프링에서 자체적으로 경고를 주는 것이다.(너 지금 컨트롤러까지 session이 살아있다는 거 인지해~라고 말을 해주는 것이다.)

위와 같이 설정해주면 WARN이 출력되지 않는다.

 

 

중요!!

JPA에서 발생할 수 있는 오류

ex 1)

open-in-view:false 상태에서 toString을 쓰면 어떻게 될까?

상황에 따라서 만약에 user에 있는 컬렉션을 toString 한다면 두 가지 오류가 난다.

1.no session오류 

왜냐하면 지금 현제 컨트롤러에서는 session이 없어서 db에 접근이 불가능하기 때문이다.

2. 스택 오버플로 오류

지금은 현제 양방향 매핑 중인데 toString을 하면 서로 무한 호출하기 때문이다.

 

ex 2)

open-in-view:false 상태에서 lazyLodig을 하려고 하면 어떻게 될까?

1. no session오류

이 또한 open-in-view:false 상태에서는 컨트롤러에 session이 없어서 DB에 접근이 불가능하기 때문이다.