private static void printMemberAndTeam(String memberId) {
Member member = em.find(Member.class, memberId)
Team team = member.getTeam();
System.out.println("username = " + member.getUsername);
System.out.println("team = " + team.getName());
}
회원만 출력
private static void printMemberAndTeam(String memberId) {
Member member = em.find(Member.class, memberId)
Team team = member.getTeam();
System.out.println("username = " + member.getUsername);
}
Team과 member는 연관관계가 걸려있지만 member를 em.find로 entity를 찾아올때 team에 대한 정보까지 불러오는건 성능적으로 아쉽다. 그럴경우 사
프록시 기초
em.find() vs em.getReference()
em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
// 실제 DB에 Query가 날아가지 않고 가짜 객체가 존재
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember = " + findMember.getClass());
// 해당 객체를 사용할때 query를 날려 가짜 객체를 진짜 객체로 바꿈
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMEmber.name = " + findMember.getUsername());
프록시 특징
실제 클래스를 상속 받아서 만들어짐
실제 클래스와 겉 모양이 같다.
사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨(이론상)
프록시 객체는 실제 객체의 참조(target)를 보관
프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
프록시 객체의 초기화
Member member = em.getReference(Member.class, "id1");
member.getName();
프록시의 특징
프록시 객체는 처음 사용할 때 한 번만 초기화
프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)
private static void logic(Member m1, Member m2) {
System.out.println("m1 == m2" + (m1.getClass() == m2.getClass()));
// m1이 em.find로 받아온 객체인지 getReference로 가져온 proxy객체인지 알 수 없으니
// jpa에서 객체비교는 instanceOf 메서드로 비교를 하자.
System.out.println("m1 == m2" + (m1 instanceOf Member)));
}
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
Member m1 = em.find(Member.class, member1.getId());
Member reference = em.getReference(Member.class, member1.getId());
-> m1 == reference
// 1. 이미 영속성 컨텍스트 1차캐시에 있음
// 2. (진짜이유) 한 영속성 컨텍스트 안에서(tx) 같은 pk로 가져온 객체는 항상 같아야 한다.
// 2번 이유 JPA가 기본적으로 제공하는 메커니즘상 규약 규칙 약속 그리 되어야만 함
Member refMember = em.getReference(Member.class, member1.getId());
Member findMember = em.find(Member.class, member1.getId());
=> proxy proxy -> 객체가 같아야 한다
// em.find로 나중에 찾은 객체가 proxy가 아닌 entity라면 다른 객체가 되므로
// proxy객체를 가져오게 된다.
영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생 (하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)
Member refMember = em.getReference(Member.class, member1.getId());
em.detach(refMember)
//em.clear();
//em.close();
refMember.getUserName();
// persistContext의 흐름이 끊겼기때문에 proxy target entity를 제어할 수 없음
프록시 확인
프록시 인스턴스의 초기화 여부 확인
PersistenceUnitUtil.isLoaded(Object entity)
프록시 클래스 확인 방법
entity.getClass().getName() 출력(..javasist.. or HibernateProxy...)
프록시 강제 초기화
org.hibernate.Hibernate.initialize(entity);
참고 : JPA 표준은 강제 초기화 없음
강제 호출 : member.getName()
emf.getPersistenceUnitUtil().isLoaded(refMember); -> 인스턴스 초기화 여부 확인
refMember.getClass() -> 클래스 확인 방법
Hibernate.initialize(refMember); -> 강제 초기화
@Entity
public class Member extends BaseEntity{
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn
private Team team;
지연 로딩
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("user1");
member.setCreatedBy("kim");
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
Member findMember = em.find(Member.class, member.getId());
Member와 Team을 자주 함께 사용한다면?
즉시 로딩 EAGER를 사용해서 함께 조회
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn
private Team team;
즉시 로딩(EAGER), Member조회시 항상 Team도 조회
JPA 구현체는 가능하면 조인을 사용해서 SQL 한번에 함께 조회
프록시와 즉시로딩 주의
가급적 지연 로딩만 사용(특히 실무에서)
즉시 로딩 적용하면 예상하지 못한 SQL이 발생
즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
@ManyToOne, @OneToOne은 기본이 즉시 로딩 -> LAZY로 설정
@OneToMany, @ManyToMany는 기본이 지연 로딩
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
n+1
JQPL 사용시 N+1 문제 해결법
FetchType.LAZY 기본
Fetch Join 활용
(em.createQuery("select m from Member m join fetch m.team", Member.class)