일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- 소수 판정
- 자료 구조
- springboot
- 프로젝트
- 그래프 탐색
- JPA
- 백준
- 백트래킹
- 정수론
- dfs
- 브루트포스 알고리즘
- 재귀
- 배포
- 너비 우선 탐색
- 문자열
- MYSQL
- 구현
- 정보처리기사
- Vue
- 알고리즘
- 다이나믹 프로그래밍
- 그래프 이론
- 깊이 우선 탐색
- 스택
- 수학
- n과 m
- SWEA
- DB
- 프로그래머스
- Spring Security
- Today
- Total
영원히 남는 기록, 재밌게 쓰자
프록시 본문
프록시를 왜 사용해야할까?
예제를 통해 알아보자
멤버 하나를 조회할 때 팀의 정보도 데이터 베이스로 부터 한번에 조회를 해야할까? 그럴 경우가 있을 수 있다. 또는 반대로 멤버를 조회할 때에 멤버만 조회하고 싶은 경우가 있다.
프록시 초기화는 처음에는 아무것도 없는 빈 껍데기 프록시 객체만 가지고 있다. 타겟이라는 변수를 초기화 시킬 때 영속성 컨텍스트에 진짜 엔티티 객체를 요청하는 동작을 거친다.
꼭 필요한 데이터를 적절한 때에 가져오기 위해서 하이버네이트에서는 프록시 객체를 사용하는 기능을 제공한다.
em.find()와 em.getReference() (em: 엔티티 매니저)
em.find() → 실제 엔티티 조회
em.getReference() → 프록시 가자 객체를 조회 (DB를 거치지 않아 빈껍데기만 있다.)
이런 프록시들은 특징이 있다.
- 실제 클래스(엔티티)를 상속 받아서 만들어진다.
- 실제 클래스와 겉모양이 같다.
- 그래서 사용하는 입장에서 이론상으로는 실제 객체인지 프록시 객체인지 구분하지 않고 사용할 수 있다. (실제로는 구분해야 한다)
프록시 객체는 target이라는 참조 값이 실제 객체의 참조 값을 보관한다.
그래서 실제 객체를 호출하는 것 처럼 프록시 객체의 메서드를 호출하게 되면 그 때 실제 객체의 참조된 값을 통해 실제 객체의 메서드를 호출한다.
프록시 객체 초기화 동작 원리
- 가정: 회원 엔티티가 프록시 객체로 트랜잭션에 올라와있음. 이 회원 객체에서 회원 이름을 조회하는 getName()을 호출
- 회원 객체는 프록시 객체이기 때문에 빈 껍데기이다.
- 하지만 실제 엔티티 객체를 상속한 객체이기 때문에 메서드를 호출하면 그제서야 영속성 컨텍스트를 뒤지고 없으면 DB를 통해 찾아온다.
- 그리고 영속성 컨텍스트에 실제 엔티티 클래스를 불러오고 프록시 객체의 target은 실제 엔티티의 getName()의 반환 값을 가져와서 반환을 한다.
영속성 컨텍스트를 통해서 초기화를 요청한 뒤에는 초기화 된 프록시 객체는 실제 엔티티 값이 걸려 있다. 타겟에 값이 걸린 뒤부터는 getName()등을 요청해도 이미 초기화가 되어 있으므로 바로 프록시 객체에서 부른다.
그리고 프록시 객체의 매커니즘은 위 동작 원리가 JPA의 표준은 아니다.
JPA를 구현한 라이브러리가 구현하기 나름이다는 것을 알아두자.
프록시 객체의 특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화된다.
- 여러번 호출해도 한번 불러온 객체를 사용한다.
- 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다. 초기에 프록시 객체를 통해서 실제 엔티티로 접근한다.
- 한번 프록시 객체로 호출되면 그 프록시 객체는 실제 엔티티로 교체되지는 않는다.
- 이렇듯 프록시 객체는 원본 엔티티를 상속받음. 따라서 타입 체크 시 주의해야 한다. (==비교 대신 instance of 사용하기) 중요.
- 프록시를 쓸지 안쓸지 모르기 때문
- 웬만하면 == 비교 지양하자
- 영속성 컨텍스트에 찾는 엔티티가 이미 1차 캐시에 있으면 getReference()를 호출 하여도 실제 엔티티를 반환한다.
- 하나의 트랜잭션 안에서 1차캐시로 가져오는것을 생각해보자?
- 영속성 컨텍스트 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생한다. (실무에서 진짜 많이 만날 수 있다고 한다.)
- 하이버네이트는 LazyInitializationException 예외를 터트린다.
1차 캐시에 엔티티나 프록시 객체가 이미 있을 때 호출 및 동일 객체 비교
Member member = new Member();
member.setName("hello");
em.persist(member);
em.flush();
em.clear();
// find로 먼저 찾음 -> 실제 엔티티 반환
Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.getClass() = " + findMember.getClass());
Member refMember = em.getReference(Member.class, member.getId());
System.out.println("refMember.getClass() = " + refMember.getClass());
System.out.println("\\"findMember == refMember\\" = " + (findMember == refMember));
실제 엔티티를 먼저 조회했을 때 결과
- findMember : find()를 통해 조회한 객체 (실제 객체)
- refMember : getReference()로 조회
하지만 한 트랜잭션에서 영속성 컨텍스트의 1차 캐시의 라이프 사이클 안에서 동작하기 때문에 flush(), clear() 이후 처음 조회한 실제 엔티티 객체가 1차 캐시에 올라가 있어 “== 비교 시 실제 객체로 같다”
반대 상황 역시 getReference() 로 먼저 조회한 프록시 객체가 1차 캐시로 올라가기 때문에 find()로 조회하여도 둘 다 프록시 객체로 찍히는 것을 확인할 수 있었다.
프록시 객체를 먼저 호출 했을 때의 결과
프록시 객체라 처음엔 빈 값이라 getClass() 호출 시 System 출력이 먼저 찍히는 것을 확인할 수 있다.
프록시 확인
프록시 객체를 확인하고 조회를 도와주는 메서드들이 있다.
- 프록시 인스턴스 초기화 여부 확인
- 엔티티 매니저 팩토리로 부터 PersistenceUtil.isLoaded(엔티티)
- 그냥 getClass()등으로 강제로 찍어보면서 확인
프록시와 즉시 로딩 사용시 주의할 점
- 가급적 지연 로딩만 사용하자
- 모든 연관된 객체의 테이블이 엮여서 조인 쿼리가 나가게 된다. 만약 10개가 맞물려 있다면 조인이 10개가 된 조원 쿼리가 날아간다.
- 즉시 로딩은 JPQL 에서 N + 1 문제를 일으킨다.
- fetch 조인을 활용하자
- @ManyToOne, @OneToOne은 기본이 즉시로딩임
- 로딩 전략을 LAZY로 설정하자!
참고
자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 - 인프런
회사땜에 매일 바쁜 와중에 학원이라도 다닐까 생각했는데 마침 JPA 강의가 생겨서 꿀 타이밍이네요. 저는 이 전에 JPA 책을 보고 공부 했었는데요 궁금했던 점, 업무에 적용하며 고민하고 해결하
www.inflearn.com
'springboot > JPA' 카테고리의 다른 글
API 개발 공부 - 지연 로딩과 조회 성능 최적화에 대해서 (0) | 2024.05.26 |
---|---|
queryDSL을 사용해서 페이징 처리 해보기 (0) | 2024.04.20 |
다대일(N:1) 연관 관계에서 양방향 단방향 매핑과 연관 관계의 주인에 대해서 알아보자. (2) | 2024.02.24 |
영속성 컨텍스트의 플러시(flush)에 대해서 (0) | 2024.02.21 |
영속성 컨텍스트 (0) | 2024.02.20 |