Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
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
Tags
more
Archives
Today
Total
관리 메뉴

쿵쿵일지

프록시와 연관관계 관리 본문

java/JPA

프록시와 연관관계 관리

노마지 2016. 5. 25. 18:44

1. 프록시

엔티티를 조회할 때 연관된 엔티티들이 항상 사용되는 것이 아님.

여기서 보면 getTeam()은 사용할 때 가져오면 됨.


JPA는 이런 문제를 해결하려고 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공한다. 이것을 지연로딩이라 한다.


em.find(Member.class, "member1"); 이 메소드는 영속성 컨텍스트에 엔티티가 없으면 데이터베이스를 조회.

em.getReference(Member.class, "member1"); 이렇게 엔티티를 조회하면 JPA는 데이터베이스를 조회하지 않고 실제 엔티티 객체도 생성하지 않는다. 대신에 데이터베이스 접근을 위임한 프록시 객체를 반환한다.


프록시의 특징


프록시 클래스는 실제 클래스를 상속받아 겉 모양이 같다. 사용하는 입장에서는 이것이 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.

프록시 객체는 실제 객체에 대한 참조를 보관한다. 그리고 프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.


프록시의 초기화 과정

1. 프록시 객체에 member.getName()을 호출해서 실제 데이터를 조회

2. 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청하는데 이것을 초기화라 한다.

3. 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체를 생성한다.

4. 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버변수에 보관한다.

5. 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환한다.


프록시의 특징

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

- 프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다.

- 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시에 주의해서 사용해야 한다.

- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환한다.

- 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제가 발생한다. 하이버네이트는 org.hibernate.LazyIntializationException 예외를 발생시킨다.


프록시와 식별자

엔티티를 프록시로 조회할 때 식별자(PK) 값을 파라미터로 전달하는데 프록시 객체는 이 식별자 값을 보관한다.

Team team = em.getRefrence(Team.class, "team1");

team.getId(); // 초기화 되지 않음


엔티티 접근 방식을 필드 (@Access(AccessType.FIELD))로 설정하면 JPA는 getId() 메소드가 id만 조회하는 메소드인지 다른 필드까지 활용해서 어떤 일을 하는 메소드인지 알지 못하므로 프록시 객체를 초기화한다.


프록시는 연관관계를 설정할 때 유용하게 사용할 수 있다.

Member member = em.find(Member.class, "member1");

Team team = em.getReference(Team.class, "team1");

member.setTeam(team);


연관관계를 설정할 때는 식별자 값만 사용하므로 프록시를 사용하면 데이터베이스 접근 횟수를 줄일 수 있다.

엔티티 접근 방식을 필드로 설정해도 프록시를 초기화 하지 않는다.


프록시 확인

JPA가 제공하는 PersistenceUnitUtil.isLoaded(Object entity) 메소드를 사용하면 프록시 인스턴스의 초기화 여부를 확인할 수 있다.

초기화되지 않은 프록시 인스턴스는 false, 이미 초기화되었거나 프록시 인스턴스가 아니면 true 반환

boolean isLoad = em.getEntityMangerFactory().getPersistenceUnitUtil().isLoaded(entity);


2. 즉시 로딩과 지연로딩

프록시 객체는 주로 연관된 엔티티를 지연 로딩할 때 사용한다.

즉시로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.

지연로딩 : 연관된 엔티티를 실제 사용할 때 조회한다.


즉시로딩 

즉시 로딩을 사용하려면 @ManyToOne의 fetch 속성을 FetchType.EAGER로 지정한다.

JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용한다.

Member member = em.find(Member.class, "member1");

Team team = member.getTeam();


NULL 제약조건과 JPA 조인 전략

현재 회원 테이블에 TEAM_ID  외래키는 NULL 값을 허용하고 있다. 따라서 팀에 소속되지 않은 회원이 있을 가능성이 있다. 팀에 소속하지 않은 회원과 팀을 내부 조인하면 팀은 물온이고 회원 데이터도 조회할 수 없다.

JPA는 이런 상황을 고려해서 외부 조인을 사용한다. 하지만 외부 조인보다 내부 조인이 성능과 최적화에 더 유리하다.

내부 조인을 사용하려면 어떻게 해야 할까? 외래 키에 NOT NULL 제약 조건을 설정하면 값이 있는 것을 보장한다. 따라서 이때는 내부 조인만 사용해도 된다.

@JoinColumn에 nullabe = false을 설정해서 이 외래 키는 NULL 값을 허용하지 않는다고 알려주면 JPA는 외부 조인 대신에 내부 조인을 사용한다.


지연 로딩을 사용하려면 @ManyToOne의 fetch 속성을 FetchType.LAZY로 지정한다.

Member member = em.find(Member.class, "member");

Team team = member.getTeam(); //프록시 객체

team.getName(); // 초기화


JPA 기본 페치 전략

- @ManyToOne, @OneToOne : 즉시 로딩

- @OneToMany, @ManyToMany : 지연 로딩

모든 연관관계에 지연 로딩을 사용하고 애플리케이션 개발이 어느 정도 완료단계에 왔을 때 실제 사용하는 상황을 보고 꼭 필요한 곳에만 즉시 로딩을 사용하도록 최적화


3. 영속성 전이

JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 한다.


영속성 전이 : 저장

영속성 전이를 활성하는 CASCADE 옵션을 적용해 보자. 

이 옵션을 주면 간편하게 영속화 할 수 있다.


영속성 전이 : 삭제

CascadeType.REMOVE로 설정하면 된다.


em.persist()와 em.remove()는 실행할 때 전이가 발생하지 않고 플러시를 호출할 때 전이가 발생


4. 고아 객체

JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공 -> 이것을 고아 객체(ORPHAN) 제거라 한다.

Parent parent1 = em.find(Parent.class, id);

parent1.getChildren().remove(0);


참조가 제거된 엔티티는 다른 곳에 참조하지 않는 고아 객체로 보고 삭제하는 기능, 따라서 이기능은 참조하는 곳이 하나일 때만 사용해야 한다.

@OneToOne, @OneToMany에만 사용할 수 있다.


5. 영속성 전이 + 고아 객체, 생명주기

CascadeType.ALL + orphanRemoval = true를 동시에 사용하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다.

자식을 저장하려면 부모에만 등록하면 된다.

Parent parent = em.find(Parent.class, parentId);

parent.addChild(child1);


자식을 삭제하려면 부모에서 제거하면 된다.

Parent parent = em.find(Parent.class, parentId);

parent.getChilderen().remove(removeObject);
















'java > JPA' 카테고리의 다른 글

객체지향 쿼리 언어  (0) 2016.06.01
값 타입  (0) 2016.06.01
다양한 연관관계 매핑  (0) 2016.05.07
연관관계 매핑 기초  (0) 2016.05.06
엔티티 매핑  (0) 2016.04.18