엔티티를 조회할 때 연관관계를 맺고있는 다른 엔티티도 같이 조회된다. 하지만 항상 연관된 엔티티 정보가 필요한 것은 아니므로 불필요한 데이터베이스 조회가 생기는 것이다. Employee 정보를 얻기위해 아래와 같이 조회한다면 내부적으로 Department 정보까지 동시에 조회를 한다.
publicclassRealSubject{ publicrequest(){...} } publicclassProxySubjectextendsRealSubject{ private RealSubject realSubject = null; public String request(){ // 실제 데이터가 필요한 시점에 객체 초기화 및 데이터를 불러온다. if(realSubject == null) { // 초기화 및 참조 보관 // JPA의 경우 DB 조회 realSubject = new RealSubject(); } return realSubject.request(); } } publicclassClient{ publicvoidselectName(){ RealSubject proxy = new ProxySubject(); proxy.request(); ..... } }
프록시 객체를 얻고 초기화하는 작업은 영속성 컨텍스트를 통해서 한다. 즉, 프록시 객체는 영속성 컨텍스트에서 관리되고 있는 객체이다. 준영속(detached) 상태의 프록시 객체를 사용하려고 하면 예외가 발생된다.
엔티티매니져의 getReference() 메소드를 통해서 프록시 객체를 얻을 수 있다.
getReference()로 프록시 객체를 얻으려고 할때 이미 영속성 컨텍스트에 실제 객체가 있는경우 프록시 객체가 아닌 실제 객체를 리턴한다. 반면, 프록시 객체를 조회 했다면 find()로 조회 하더라도 실제 객체가 아닌 프록시 객체가 리턴된다. 이는 영속성 컨텍스트가 항상 엔티티의 동일성(identity)을 보장하기 위함이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 실제 객체가 먼저 조회되어 영속성 컨텍스트에 존재하는 경우 Employee realEmployee = em.find(Employee.class, "id1"); Employee proxyEmployee = em.getReference(Employee.class, "id1"); // realEmployee, proxyEmployee 모두 실제 엔티티 이다. Assert.assertTrue(realEmployee == proxyEmployee); // 성공
// 프록시 객체를 조회한 뒤 실제 객체를 조회하려고 하는 경우 Employee proxyEmployee = em.getReference(Employee.class, "id1"); Employee realEmployee = em.find(Employee.class, "id1"); // realEmployee, proxyEmployee 모두 프록시 객체이다. Assert.assertTrue(realEmployee == proxyEmployee); // 성공
// 타입 비교 Assert.assertFalse(proxyEmployee.getClass() == Employee.class); Assert.assertTrue(proxyEmployee instanceof Employee);
프록시 객체를 사용시 동등성(equal) 비교시 주의 해야한다. IDE를 통해 자동으로 equals() 오버라이딩 메소드를 생성한 경우 제대로 동작하지 않는다. 내부적으로 같은 타입인지 비교하기 위해 getClass()를 사용하고,(프록시 객체는 실제 객체를 상속받음) 필드 값들을 비교하기 위해서 getter 메소드가 아닌 필드에 직접 접근하여 값을 얻는다.(프록시 객체가 초기화 되지 않았 을 수 있음) 타입비교는 instanceof를 사용하고, 필드 값은 getter 메소드를 통해 얻어야 한다.
NOTE JPA에서 즉시 로딩을 할때 내부조인(INNER JOIN)이 아니라 외부조인(LEFT OUTTER JOIN)을 사용한다. 이는 null값이 허용되는 경우 데이터가 누락되는 것은 막을 수 있지만, 성능상 안좋을 수 있다. INNER JOIN을 사용해야 되는 경우에는 아래와 같이 설정해 주면된다. (둘중 한가지 방법 선택) @JoinColumn(name=”DEPARTMENT_ID”, nullable=false) @ManyToOne(fetch= FetchType.EAGER, optional=false)
Department dept = em.find(Department.class, "id01"); dept.getEmployees().remove(0); // 연관관계 삭제 // DELETE FROM EMPLOYEE WHERE EMPLOYEE_ID = ? 실행 됨 dept.getEmployees().clear(); // 연관된 모든 Employee 삭제됨