beadss / jpa-study

jpa슽터디입니다
1 stars 2 forks source link

8장 정리(mjy) #23

Open beadss opened 5 years ago

beadss commented 5 years ago

프록시와 연관관계 관리

이번 장은 Hibernate의 엔티티 부가기능들에 대한 설명이 나온다. 단순한 데이터 매핑, 읽기, 쓰기 기능을 넘어서, 성능이나 개발 편의를 위한 내용이다.

이런 기능들의 특징은, 추측만으로 적용해서는 안된다는 것이다. 정확히 어떻게 동작하는지 이해하고, 현재 구현돼있는 케이스들에 가장 적합한 것을 골라서 사용해야한다.

프록시(즉시 로딩, 지연 로딩)

스프링을 사용하는 사람이라면 아주 익숙한 기술이다. 아래는 Proxy의 극히 단순한 예시이다. 실제로 사용하는 프록시 구현체들은 매우 복잡하게 구현돼있지만, 기본 원리는 모두 동일하다.

public class Proxy extends Origin { private IntProcessor processor; //뭔가 해주는 놈 private Origin origin;

public Proxy(IntProcessor processor) {
    this.processor = processor;
}

public int getValue() {
    return processor.process(origin.getValue());
}

}

public class Main { public void main(String[] args) { Origin origin = new Proxy(new IntProcessor());

    origin.getValue(); //Proxy.getValue를 수행함
}

}

Hibernate에서는 이 Proxy 개념을 사용해서 '지연 로딩'이라는 기능을 제공한다.
당연하게도 '즉시 로딩'에는 Proxy가 필요 없다. 즉시 불러와서 원본 객체를 만들면 되기 때문이다.

사용 방식은 두가지이다.
EntityManager.getReference, FetchType.LAZY

중요한 특징
* 이미 영속성 컨텍스트에 데이터가 로드돼있었다면, Proxy가 아닌 원본 객체를 되돌려준다.
* 영속 상태일때만 지연 로딩 기능을 사용할 수 있다.

### EntityManager.getReference
`em.getReference(Member.class, "id1")` 식으로 사용한다.
아래 상술할 **실무에서 FK를 안쓴다는 현실** 때문에, Proxy를 이해하는 용도로만 쓰는게 좋을 것 같다.

아래 내용들은 **FetchType.LAZY를 단일 엔티티에 사용했을 때에도 동일하게 적용된다.**
엔티티를 지연 로드 했을 때 특징
* 엔티티의 데이터를 get 해올 때 지연 로딩이 발동된다.
    * PK property를 get 해올때는 지연 로딩이 발동하지 않는다.(이미 pk는 알고 있으니까)
    * 단, AccessType.FIELD로 설정하면 발동 된다. property(getId 메서드)가 뭔지 jpa는 모르기 때문이다.
* 이렇게 불러온 Proxy A 엔티티를 B 엔티티에 연관 지어준 후, B엔티티를 영속화하면 지연 로딩이 발동되지 않으므로 데이터베이스 접근 횟수를 줄일 수 있다.
    * 연관관계에는 pk만이 사용되기 때문이다.
    * 이때는 AccessType.FIELD여도 지연 로딩이 발동하지 않는다.
    * getId()를 호출한게 아닌, PK 자체를 가져다 쓰기 때문이고, 이건 JPA에게 미리 알려줬기 때문이다.
* DB에 FK 제약조건이 걸려있다면 괜찮겠지만, 현업은 보통 그렇지 않기 때문에 매우 위험한 방식 같다.
        * Member <1---*> Orders 관계인 경우, 아래와 같은 문제가 있다.
    * 초기화되지 않은 Member 프록시 객체가 실제로 존재하지 않는 Member ID를 가지고 있을 경우, DB 레벨에서 체크가 안된다.
    * Proxy가 아닌 원본 Entity도 동일한 위험이 존재하긴 하지만, 작정하지 않는 한 문제가 일어날 일은 거의 없다.

### FetchType.LAZY
실무에서 사용할 유일한 지연 로딩 방식이다.
사용 방식은 아래와 같다.

```java
@Entity
public class Member {
    @ManyToOne(fetch = FetchType.LAZY) // default == EAGER
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    @OneToMany(mappedBy = "member", fetch = FetchType.LAZY) // default == LAZY, FetchType.EAGER일 때 optional에 관계 없이 무조건 OUTER JOIN
    private List<Order> orders;
}

public class Main {
    public void main(String[] args) {
        ...
        Member member = em.find(Member.class, "member1");
        Team team = member.getTeam(); // get Proxy
        team.getName(); // Load!

        List<Order> orderList = member.getOrders(); // get Collection Warpper(Proxy)
        orderList.get(0); // Collection Load!
    }
}

질문1: 엔티티들을 DTO로 변환할때, 보통 어떻게 하는지? 노가다는 피하고 싶은데.. 질문2: 멀티 스레드에서 컬렉션 래퍼 엘리먼트에 접근했을 때, 중복 로딩에 대한 방지가 돼있는지? Java8 Stream API의 parallelStream때문에 든 의문임 질문2-1: 단일 엔티티 Proxy도 멀티 스레드에 안전한지 의문이 있긴 함

영속성 전이와 고아 객체

영속성 전이

쉽게 말해서, 엔티티가 영속화될 때 관계가 맺어진 엔티티들도 영속화 해주거나, 엔티티가 삭제될 때 관계가 맺어진 엔티티들도 삭제해주는 기능이다.

영속성 전이 기능 없이 엔티티간의 관계를 맺는 코드는 영속화 순서도 맞춰줘야하고 코드의 양도 매우 길어지게 된다.(예제 수정할때마다 매우 열받음)

ALL, PERSIST, REMOVE, MERGE, REFRESH, DETACH 등이 존재한다.

PERSIST는 거의 필수적으로 사용될 것 같고, REMOVE는 상황에 따라 선택적으로 사용될 것 같다.(입력은 맘대로지만 삭제는 안된단다)

나머지 종류들은 생각하는대로 EntityManager.merge, detach, refesh 등이 수행될 때, 연관 엔티티들도 동일한 동작을 수행하게 하는 것인데, 그다지 효용이 없어보인다.

질문1: REMOVE cascade로 delete_yn 컬럼을 Y로 바꿔준달지 하는 기능은 없나? 질문2: 편의메서드 다넣었다가 결국 다 빼는중.. CascadeType.ALL에 no 편의메서드로 다 개발되있는걸(jpa를 잘 모르고 개발) 체크하면서 바꿀려니 헬이네요.. 이게 어떤 의미인지 궁금함

고아 객체

Java의 가비지 컬렉션과 비슷한 기능이라고 보면 된다. @OneToMany(mappedBy = "parent", orphanRemoval = true 식으로 사용하는데, 연관관계가 제거될 때, 더이상 사용되지 않는 엔티티라고 생각하고 DB에서도 삭제해주는 기능이다.

@OneToOne이나 @OneToMany에서만 사용 가능하다. @ManyToOne은 어디서 또 참조되고 있을지 알 수 없으므로, 지원되지 않는다.

질문1: 영속성 전이에서 REMOVE cascade와 마찬가지로, 삭제 말고 다른 행동을 하게는 할 수 없는지?

beadss commented 5 years ago

질답 정리

엔티티들을 DTO로 변환할때, 보통 어떻게 하는지? 노가다는 피하고 싶은데..

아직 답변 없음

멀티 스레드에서 컬렉션 래퍼 엘리먼트에 접근했을 때, 중복 로딩에 대한 방지가 돼있는지?

Java8 Stream API의 parallelStream때문에 든 의문임 단일 엔티티 Proxy도 멀티 스레드에 안전한지 의문이 있긴 함

아직 답변 없음

REMOVE cascade로 delete_yn 컬럼을 Y로 바꿔준달지 하는 기능은 없나?

REMOVE cascade와 마찬가지로, orphanRemoval도 삭제 말고 다른 행동을 하게는 할 수 없는지?

row를 실제로 delete하는게 아니고, delete_yn 컬럼 등으로 처리할 때, 아래 링크처럼 하면 된다고 합니다. https://stackoverflow.com/questions/23990008/hibernate-soft-delete-using-update-cascade 참고: http://docs.jboss.org/hibernate/core/4.0/manual/en-US/html/querysql.html#querysql-cud

가끔가다 재사용을 원하는 케이스도 있는데, (FK 관계가 존재하면, insert 대신 delete_yn = 'N' 업데이트)

그럴땐 유니크 제약조건 걸어두고, @SQLInsert를 아래처럼 사용하면 됨 INSERT ~~ ON DUPLICATED KEY UPDATE delete_yn = 'N'

CascadeType.MERGE는 어떤 상황에서 쓰이는지?

참고

의문을 유발시킨 현상

CascadeType.MERGE를 사용하면, 아래 상황에서 order3은 관계가 맺어지지 않음 PERSIST는 상태로, MERGE는 단발성으로 이해하긴 했는데.. 왜 필요한지는 이해가 안됨

//beginTran

Member member = new Member();
Order order1 = new Order();
Order order2= new Order();

member.addOrder(order1);
member.addOrder(order2);

member = em.merge(member);

Order order3 = new Order();
member.addOrder(order3); // commit해도 관계 안맺어짐

//commit