Proxy?

반응형
반응형

프록시 프록시란 과연 무엇일까?
JPA에서 프록시를 이용한다고 하는데 도대체 어디에서 이용을 하는 걸까?
들어가기전에 프록시에 대해 간략하게 소개하면
프록시의 역할은 총 2가지로 구분이 되어진다.
1. 대리 기능
2. 추가 기능

대리 기능은 우리가 흔히 아는 캐싱 기능이고 추가 기능은 부가적인 기능을 추가할 수 있는 기능이다.
과연 JPA는 언제 프록시 기능을 사용하는 것일까?
내가 생각할때는 대리 기능을 사용하는 것으로 생각이 들어진다.
그 이유는 추후에 얘기를 해보자. 그전에 GOF의 디자인 패턴을 살펴보는 것이 좋을 것 같다.
왜냐하면 프록시를 설명하고 있는 패턴이 존재하기 때문이다.
디자인 패턴 중 프록시 패턴과 데코레이터 패턴이 프록시 역할을 한다고 한다.
근데 이상하다 프록시 패턴은 그려려니 하는데 데코레이터라니

결론 부터 말하면
프록시  패턴은 대리 기능을 담당하고
데코레이터 패턴은 추가 기능을 담당한다고 한다.
100% 기정사실화된 내용은 아니지만 GOF패턴을 만든 사람들이 대리 기능을 사용하는 것을 프록시 패턴으로 지정한 이유는
어쩌면 프록시의 대표적인 기능은 대리 기능이 아닐까 추측한다.

프록시

아무튼 본격적으로 프록시 패턴과 데코레이터 패턴에 대해 코드를 작성해보자.
  간단하게 이런식으로 작성했다.

public class RealSever implements Connect{

  @Override
  public String operation() {
    System.out.println("리얼 서버 이용");
    return "data";
  }
}

일단 코드에 대해 설명하자면 Connect라는 키워드를 통해 데이터를 받아온다.

이 코드를 조금 수정하면 프록시 패턴을 작성할 수 있다.

public class ProxyServer implements Connect{
  private Connect connect;
  private String target;

  public ProxyServer(Connect connect) {
    this.connect = connect;
  }

  @Override
  public String operation() {
    if (target == null) {
      target = connect.operation();
    }
    System.out.println("프록시 서버 이용");
    return "data";
  }
}

이는 target이 존재하지 않는다면, 리얼 서버에서 데이터를 가져오고 그렇지 않는다면 그 데이터를 사용한다.
근데 솔직히 1초를 해놨다고는 하지만 이것을 알 방법이 없다.ㅜㅜ
System.out.println으로 해놔서 그런가 ㅜㅜ
그러면 log를 찍어서 확인해보자.

17:11:19.055 [main] INFO com.example.design_pettern.object_instance._proxy.RealSever - 리얼 서버 이용
17:11:20.067 [main] INFO com.example.design_pettern.object_instance._proxy.RealSever - 리얼 서버 이용
17:11:21.069 [main] INFO com.example.design_pettern.object_instance._proxy.RealSever - 리얼 서버 이용
17:08:59.301 [main] INFO com.example.design_pettern.object_instance._proxy.RealSever - 리얼 서버 이용
17:09:00.313 [main] INFO com.example.design_pettern.object_instance._proxy.ProxyServer - 프록시 서버 이용
17:09:00.313 [main] INFO com.example.design_pettern.object_instance._proxy.ProxyServer - 프록시 서버 이용
17:09:00.313 [main] INFO com.example.design_pettern.object_instance._proxy.ProxyServer - 프록시 서버 이용

놀랍게도 리얼 서버에서 이용할때는 1초라는 거치지 않고 데이터를 출력한다는 사실을 알 수 있다.
또 프록시는 또다른 프록시를 가질 수 있는데 다음처럼 만들 수 있다.

17:13:21.951 [main] INFO com.example.design_pettern.object_instance._proxy.RealSever - 리얼 서버 이용
17:13:22.963 [main] INFO com.example.design_pettern.object_instance._proxy.ProxyServer - 프록시 서버 이용
17:13:22.963 [main] INFO com.example.design_pettern.object_instance._proxy.ProxyServer2 - 프록시 서버 이용
17:13:22.963 [main] INFO com.example.design_pettern.object_instance._proxy.ProxyServer2 - 프록시 서버 이용
17:13:22.963 [main] INFO com.example.design_pettern.object_instance._proxy.ProxyServer2 - 프록시 서버 이용

요런식도 가능하다.
어떻게 보면 이건 내림갈굼과 비슷한 느낌이다.

요게 프록시 다. 과정이야 어떻든 결과만 나오면 되기 때문일까?

데코레이터

요건 프록시의 추가 기능을 담당하고 있다.
코드 상으로 보면 프록시와 다를거는 없지만 말이다.ㅜㅜ

@Slf4j
public class ProxyServer implements Connect{
  private Connect connect;

  public ProxyServer(Connect connect) {
    this.connect = connect;
  }

  @Override
  public String operation() {
    log.info("프록시 서버 이용");
    return "*******"+connect.operation() + "********";
  }
}

추가기능을 추가해서 실제 서버에서 도출 결과에 plus된 결과를 얻을 수 있다.
개인적으로 추가 기능은 마이너스 기능도 추가 기능이라 생각이 든다.
따라서 횡령같은 것도 데코레이터 패턴으로 작성할 수 있지 않을까 생각이 든다.

다시 돌아와서 JPA

다시 본론으로 돌아와서 JPA에서는 프록시 기능을 1차 캐시에 사용한다고 생각이 든다.
이름 부터 캐싱 기능을 사용한다고 적혀 있다. 고로 추가적인 기능이 있는게 아니라 데이터를 빠르게 호출 할 수 있는 기능이라 생각이 든다.
근데 이것을 어떻게 설명을 해야 하지 ㅜㅜ

대충 그림을 그려보면

이런 그림을 그릴 수 있는데 만약 1차 캐시에 데이터가 존재하지 않는다면 실제 DB에 요청을 해 데이터를 초기화를 한다.
그리고 나서 다시 조회를 하게 되면 그 다음 부터는 1차캐시로 부터 데이터를 출력을 받는다.

물론 그림으로 보면 클래스가 달라질거라 생각한다.
즉, 첫번째 요청한 클래스와 두번째 요청한 클래스의 정보가 서로 상이할거라 생각한다.
하지만 JPA의 특성상 무조건 같은 클래스를 보여주게 설계되었다. (같은 트랜잭션 안에서는)
즉 어떻게 해서든 클래스는 같다는 이야기다.

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

이것을 해야 되는 결정적인 이유는 강제로 DB로 초기화를 시켜야 하기 때문이다. 그래야 원하는 정보를 얻을 수 있기 때문이다.

사실 find말고 getReference를 사용할 수 있는데 이는 프록시 객체에 직접 접근할 수 있다.
즉, 코드를 작성해보면

HighSchool highSchool = em.find(HighSchool.class, school.getId());
System.out.println(highSchool.getClass());

HighSchool findHigh = em.getReference(HighSchool.class, school.getId());
System.out.println(findHigh.getClass());

이 두가지의 조합이라 할 수 있다. 
테스트도 올리고 싶지만 귀찮은 관계로 넘어가구

사실 이건 지연로딩과 즉시로딩을 설명하기 위한 빌드업이었다.
이들을 제대로?이해하기 위해서는 위 지식이 필수라 생각이 든다.

그러면 지연로딩과 즉시 로딩은 뭐지?
지연 로딩이라면 뭔가 오래 걸리는 느낌이 들고 
즉시 로딩은 바로 로딩이 되는 뭐 그런 느낌이 든다. 하지만 즉시 로딩보다는 지연 로딩을 사용해야 된다고 알고 있다.
왜냐하면 즉시 로딩은 1+N문제가 존재하기 때문이다.

그러면 지연로딩과 즉시 로딩에 대해 알아 보자.
이들을 설명하기전에 코드를 통해 작성해보자.
대충 코드를 작성하고
main문

School school = new School();
school.setName("학교1");
em.persist(school);
Student studentA = new Student();
studentA.setName("학생1");
studentA.setSchool(school);
em.persist(studentA);

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

Student student = em.find(Student.class, studentA.getId());

이렇게 하면 학교 값을 가져올 수있다.
쿼리를 출력하면

select
        students0_.school_id as school_i3_1_0_,
        students0_.id as id1_1_0_,
        students0_.id as id1_1_1_,
        students0_.name as name2_1_1_,
        students0_.school_id as school_i3_1_1_ 
    from
        Student students0_ 
    where
        students0_.school_id=?

School, Student를 한번에 가져오는 한방 쿼리로 만들어서 가져오는 것을 알 수 있다.
그러면 이것은 어떤 클래스를 참조할까?

class helloJava.School

확인 결과 실제 객체를 가져오는 것을 알 수 있다. 즉, 즉시 로딩은 프록시를 이용하지 않는다는 것을 알 수 있다.
근데 이거 가지고는 1+N 문제을 확인 할 수 없다.
사실 여기에서는 확인이 어려워 강의를 확인 해보니

List<Student> studentList = em.createQuery("select s from Student s",
Student.class).getResultList();

이런식으로 확인해야 된다고 한다.
확인 결과

Hibernate: 
    /* select
        s 
    from
        Student s */ select
            student0_.id as id1_1_,
            student0_.name as name2_1_,
            student0_.school_id as school_i3_1_ 
        from
            Student student0_
Hibernate: 
    select
        school0_.id as id1_0_0_,
        school0_.name as name2_0_0_,
        students1_.school_id as school_i3_1_1_,
        students1_.id as id1_1_1_,
        students1_.id as id1_1_2_,
        students1_.name as name2_1_2_,
        students1_.school_id as school_i3_1_2_ 
    from
        School school0_ 
    left outer join
        Student students1_ 
            on school0_.id=students1_.school_id 
    where
        school0_.id=?

확인 결과 Student 검색 쿼리가 나오고 위에서 확인한 쿼리가 나온다.

그러면 어떻게 해야 할까?
지연 로딩으로 바꿔야 한다. 즉, 실제 객체를 직접 접근하지 말고 프록시를 통해 우회해서 접근하라는 뜻으로 이해된다.
아무튼 지연로딩으로 바꿔보자.

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn
private School school;

이렇게 바꾸면 된다고 한다.
참고로 ToOne 시리즈는 기본값이 즉시로딩 eager이다.
이유는 잘 모르겠다.

FetchType fetch() default EAGER;


아무튼 eager도 사용 용도가 따로 정해져 있지만 실무에서는 모든 것들을 지연 로딩으로 바꾸라고 강조하셨다.
이 쿼리를 출력해보면,

Hibernate: 
    /* select
        s 
    from
        Student s */ select
            student0_.id as id1_1_,
            student0_.name as name2_1_,
            student0_.school_id as school_i3_1_ 
        from
            Student student0_

학생만 가져오게된다. 그런데 학교도 구하고 싶으면 어떻게 할까?
그건 나중에 fetch join을 학습하면 된다고 하니 조금만 기다려 보자.

솔직히 이 부분은 100%이해했다고는 어려운것 같다.

추가+)

영속성 전의와 고아 객체

이 부분은 짧게 하고 넘어가자. 영속성 전의는 자식을 생명주기를 부모가 관리하도록 하는 거고,
고아 객체는 부모가 없어지는 순간 삭제가 되는 기능이라 한다.
이에 대해서는 조금더 공부하고 따로 작성하는게 좋을 것 같다,

 

반응형

'JPA' 카테고리의 다른 글

JPQL 기본 문법  (0) 2021.12.18
값 타입 작성 방법  (0) 2021.12.12
상속하는 방법?  (0) 2021.11.27
연관관계의 주인을 바꾸면 무슨일이 발생할까?  (0) 2021.10.30
Entity와 Table  (0) 2021.10.16

댓글

Designed by JB FACTORY