티스토리 뷰

인프런 - 김영한 JPA 강의 중

 

            Member member = em.find(Member.class, 1L);
            printMemberAndTeam(member); //경우에 따라 각각 출력하고 싶은 것이 다를 수 있다.
            printMember(member); // 어떤 때에는 멤버만, 어떤 때에는 팀만 출력하고 싶을 수 있다.

    private static void printMember(Member member) {
        System.out.println("member = " + member.getUsername());
    }

    private static void printMemberAndTeam(Member member) {
        String username = member.getUsername();
        System.out.println("username = " + username);

        Team team = member.getTeam();
        System.out.println("teat = " + team.getName());

    }

 

상황에 따라 가각 출력하고 싶은게 다를 수 있다. 매번 멤버를 부를 때에 꼭 팀을 부를 필요는 없다는 것이다.

 

            Member member = new Member();
            member.setUsername("hello");

            em.persist(member);

            em.flush();
            em.clear();

            Member findMember = em.getReference(Member.class, member.getId());
            System.out.println("findMember.id" + findMember.getId());
            System.out.println("findMember.Username" + findMember.getUsername());

로그

Hibernate: 
    /* insert jpabook.jpashop.domain.Member
        */ insert 
        into
            Member
            (createBy, createDate, lastModifiedBy, lastModifiedDate, LOCKER_ID, USERNAME, id) 
        values
            (?, ?, ?, ?, ?, ?, ?)
findMember.id1
Hibernate: 
    select
        member0_.id as id1_3_0_,
        member0_.createBy as createBy2_3_0_,
        member0_.createDate as createDa3_3_0_,
        member0_.lastModifiedBy as lastModi4_3_0_,
        member0_.lastModifiedDate as lastModi5_3_0_,
        member0_.LOCKER_ID as LOCKER_I7_3_0_,
        member0_.TEAM_ID as TEAM_ID8_3_0_,
        member0_.USERNAME as USERNAME6_3_0_,
        locker1_.LOCKER_ID as LOCKER_I1_2_1_,
        locker1_.name as name2_2_1_,
        team2_.TEAM_ID as TEAM_ID1_8_2_,
        team2_.name as name2_8_2_ 
    from
        Member member0_ 
    left outer join
        Locker locker1_ 
            on member0_.LOCKER_ID=locker1_.LOCKER_ID 
    left outer join
        Team team2_ 
            on member0_.TEAM_ID=team2_.TEAM_ID 
    where
        member0_.id=?
findMember.Usernamehello

 

해당 로그를 보면 순서대로 쿼리가 실행 되지 않는다.먼저 getId부분이 출력되는데, getId는 기존 member에 남아있는 id를 가져오게 된다. 그후 username을 출력하려하니 값이 없으니까 쿼리를 먼저 실행한다. 왜 이런 일이 일어나는것일까?

 

em.getReference();

            Member findMember = em.getReference(Member.class, member.getId());
            System.out.println("findMember.getClass = " + findMember.getClass());
            
            로그
                /* insert jpabook.jpashop.domain.Member
        */ insert 
        into
            Member
            (createBy, createDate, lastModifiedBy, lastModifiedDate, LOCKER_ID, USERNAME, id) 
        values
            (?, ?, ?, ?, ?, ?, ?)
            findMember.getClass = class jpabook.jpashop.domain.Member$HibernateProxy$nLZHJfje

 

class를 확인했는 떄 HibernateProxy라는 가짜 클래스가 눈에 보인다.

 

프록시 기초

- em.find() vs em.getReference()

- em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회

- em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

ㄴ 하이버네이트가 내부 라이브러리를 써서 프록시라하는 가짜 엔티티를 준다. 껍데기는 똑같은데 안에가 비어있다. 그 프록시 내부에는 Entity target =null 이라는게 있는데 이게 진짜 레퍼런스(엔티티)를 가르킨다.

 

인프런 - 김영한 JPA 강의

프록시 특징

- 실제 클래스를 상속 받아서 만들어짐

- 실제 클래스와 겉 모양이 같다.

- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨 (이론상)

인프런 - 김영한 JPA 강의 중

 

프록시 특징

- 프록시 객체는 실제 객체의 참조(target)를 보관

- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

인프런 - 김영한 JPA 강의 중

 

인프런 - 김영한 JPA 강의 중

 

먼저

1. 요청을 하면 프록시 객체에 값이 없을 경우

2. 영속성 컨텍스트에 초기화 요청을하고

3. db를 조회한다.

4. 실제로 entity가 생성되고

5. 프록시 객체가 target을 참조하고 있기에 해당값을 상속받아서 사용하게 된다. 

 

프록시의 특징

- 프록시 객체는 처음 사용할 때 한 번만 초기화

- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능

- 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)

- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환

- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시 초기화 문제 발생

(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트린다.)

 

            Member member1 = new Member();
            member1.setUsername("hello1");
            Member member2 = new Member();
            member2.setUsername("hello1");

            em.flush();
            em.clear();


            Member m1 = em.find(Member.class, member1.getId());
            Member m2 = em.find(Member.class, member2.getId());

            System.out.println("m1 == m2 : " + (m1.getClass() == m2.getClass()));

 

당연히 true 가 나온다.

 

하지만 타입체크 할 때는 조심해야한다. 

 

            Member m1 = em.find(Member.class, member1.getId());
            Member m2 = em.getReference(Member.class, member2.getId());

            System.out.println("m1 == m2 : " + (m1.getClass() == m2.getClass()));

false가 나온다. 만약 타입체크를 하고 싶다면 ==이 아닌 instanceof를 사용해야 한다.

 

상황을 반대로 보고

 

            Member member1 = new Member();
            member1.setUsername("hello1");

            em.flush();
            em.clear();


            Member refMember= em.getReference(Member.class, member1.getId());
            System.out.println("refMember = " + refMember);
            Member findMember  = em.find(Member.class, member1.getId());
            System.out.println("findMember = " + findMember);
            System.out.println("m1 == m2 : " + (refMember.getClass() == findMember.getClass()));

 

이렇게 부른다면 같은 멤버일 경우 getReference로 먼저 프록시를 호출하게 되면 다음 find를 사용해도 해당 객체는 프록시가 되어 true값이 나온다

 

만약 중간에 영속성 컨텐츠를 꺼버린다면?

            Member refMember= em.getReference(Member.class, member1.getId());
            System.out.println("refMember = " + refMember);

            em.detach(refMember);
            System.out.println("refMember = " + refMember.getUsername());

getUserName을 사용할 때에 프록시를 사용할 수 없다는 오류가 나게 된다.

 

 

프록시 인스턴트 초기화 여부 확인

- PersistenceUnitUtil.isLoaded(Object entity)

프록시 컨텐츠 확인법

- entity.getClass().getName() 출력(..javasist..or HibernateProxy..)

프록시 강제 초기화

- org.hibernate.Hibernate.initialize(entity);

참고 : JPA 표준은 강제 초기화 없음

강제 호출 : member.getName() //쿼리를 다시 부른다.

 

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
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 31
글 보관함