beadss / jpa-study

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

14장 정리중 #39

Open 2xel opened 5 years ago

2xel commented 5 years ago

14. 컬렉션과 부가 기능

1. 컬렉션

1.1 JPA와 컬렉션

@Entity
public class Team {
    @Id
    private String id;

    @OneToMany
    @JoinColumn
    private Collection<Member> members = new ArrayList<>();
}
Team team = new Team();

System.out.println("Before persist : " + team.getMembers().getClass());
em.persist(parent);
System.out.println("After persist : " + team.getMembers().getClass());

// Before persist = class java.util.ArrayList
// After persist = class org.hibernate.collection.internal.PersistentBag

컬렉션 인터페이스|내장 컬렉션|중복 허용|순서 보관

---|---|---|---

Collection, List|PersistenceBag|O|X

Set|PersistenceSet|X|X

List + @OrderColumn|PersistentList|O|O

1.2 Collection, List

1.3 Set

1.4 List + @OrderColumn

@Entity
public class Board {
    @Id @GeneratedValue
    private Long id;

    private String title;
    private String content;

    @OneToMany(mappedBy="board")
    @OrderColumn(name="POSITION")
    private List<Comment> comments = new ArrayList<>();
    ...
}

@Entity
public class Commnet {
    @Id @GeneratedValue
    private Long id;

    private String comment;

    @ManyToOne
    @JoinColumn(name="BOARD_ID")
    private Board board;
    ...
}
// 사용 코드
Board board = new Board("제목", "내용1");
em.persist(board);

Comment commnet1 = new Commnet("댓글1");
commnet1.setBoard(board);
board.getComments().add(comment1);  // POSITION 0
em.persist(comment1);

Comment commnet2 = new Comments("댓글2");
comment2.setBoard(board);
board.getComments().add(comment2);  // POSITION 1
em.persist(comment2);

Comment commnet3 = new Comments("댓글3");
comment3.setBoard(board);
board.getComments().add(comment3);  // POSITION 2
em.persist(comment3);

Comment commnet4 = new Comments("댓글4");
comment4.setBoard(board);
board.getComments().add(comment4);  // POSITION 3
em.persist(comment4);

@OrderColumn 의 단점

1.5 @OrderBy

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy="team")
    @OrderBy("username desc, id asc")
    private Set<Member> members = new HashSet<>();
    ...
}

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    @Column(name="MEMBER_NAME")
    private String username;

    @ManyToOne
    private Team team;
    ...
}

2. @Converter

@Entity
public class Member {
    @Id
    private String id;
    private String usernamel;

    @Convert(converter=BooleanToYNConverter.class)
    private boolean vip;
    ...
}

@Converter
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {
    @Override
    public String convertToDatabaseColumn(Boolean attribute) {
        return (attribute != null && attribute) ? "Y" : "N";
    }

    @Override
    public Boolean convertToEntityAttribute(String dbData) {
        return "Y".equals(dbData);
    }
}
public interface AttributeConverter<X, Y> {
    public Y convertToDatabaseColumn (X attribute);
    public X convertToEntityAttribute (Y dbData);
}

2.1 글로벌 설정

@Converter(autoApply=true)
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {
    @Override
    public String convertToDatabaseColumn(Boolean attribute) {
        return (attribute != null && attribute) ? "Y" : "N";
    }

    @Override
    public Boolean convertToEntityAttribute(String dbData) {
        return "Y".equals(dbData);
    }
}

3. 리스너

3.1 이벤트 종류

  1. PostLoad : 엔티티가 영속성 컨텍스트에 조회된 직후 또는 refresh를 호출한 후(2차 캐시에 저장되어 있어도 호출)
  2. PrePersist : persist() 메소드를 호출해서 엔티티를 영속성 컨텍스트에 관리하기 직전에 호출된다. 식별자 생성 전략을 사용한 경우 엔티티에 식별자는 아직 존재하지 않는다. 새로운 인스턴스를 merge할 때도 수행된다.
  3. PreUpdate : flush나 commit을 호출해서 엔티티를 데이터베이스에 수정하기 직전에 호출된다.
  4. PreRemove : remove() 메소드를 호출해서 엔티티를 영속성 컨텍스트에서 삭제하기 직전에 호출된다. 또한 삭제 명령어로 영속성 전이가 일어날 때도 호출된다. orphanRemoval에 대해서는 flush나 commit시 호출
  5. PostPersist : flush나 commit을 호출해서 엔티티를 데이터베이스에 저장한 직후에 호출된다. 식별자가 항상 존재한다. 참고로 식별자 생성 전략이 IDENTITY면 식별자를 생성하기 위해 persist()를 호출하면서 데이터베이스에 해당 엔티티를 저장하므로 이때는 persist()를 호출한 직후에 바로 PostPersist가 호출된다.
  6. PostUpdate : flush나 commit을 호출해서 엔티티를 데이터베이스에 수정한 직후에 호출된다.
  7. PostRemove : flush나 commit을 호출해서 엔티티를 데이터베이스에 삭제한 직후에 호출된다.

3.2 이벤트 적용 위치

엔티티에 직접 적용

별도의 리스너 등록

기본 리스너 사용

더 세밀한 설정

4. 엔티티 그래프

4.1 Named 엔티티 그래프

@NamedEntityGraph(name="Order.withMember", attribute={
    @NamedAttirubteNode("member")
})
@Entity
@Table(name="ORDERS")
public class Order{
    @Id @GeneratedValue
    @Column(name="ORDER_ID")
    private Long id;

    @ManyToOne(fetch=FetchType.LAZY, optional=false)
    @JoinColumn(name="MEMBER_ID")
    private Member member;  // 주문 회원
}

4.2 em.find()에서 엔티티 그래프 사용

EntityGraph graph = em.getEntityGraph("Order.withMember");

Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);

Order order = em.find(Order.class, orderId, hints);
select o.*, m.*
from
    Order o
inner join
    Member m
        on o.MEMBER_ID=m.MEMBER_ID
where
    o.ORDER_ID=?

4.3 subgraph

@NamedEntityGraph(name="Order.withMember", attribute={
    @NamedAttriubteNode("member"),
    @NamedAttributeNode(value="orderItems", subgraph="orderItems")
    },
    subgraphs=@NamedSubgraph(name="orderItems", attributeNodes= {
        @NamedAttributeNode("item")
    })
)
@Entity
@Table(name="ORDERS")
public class Order{
    @Id @GeneratedValue
    @Column(name="ORDER_ID")
    private Long id;

    @ManyToOne(fetch=FetchType.LAZY, optional=false)
    @JoinColumn(name="MEMBER_ID")
    private Member member;  // 주문 회원

    @OneToMany(mappedBy="order", cascade=CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<>();
    ...
}

@Entity
@Table(name="ORDER_ITEM")
public class OrderItem{
    @Id @GeneratedValue
    @Column(name="ORDER_ITEM_ID")
    private Long id;

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="ITEM_ID")
    private Item item;  // 주문 상품
    ...
}

// 사용하는 코드
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", em.getEntityGraph("Order.withAll"));

Order order = em.find(Order.class, orderId, hints);

4.4 JPQL에서 엔티티 그래프 사용

// JPQL에서 엔티티 그래프 힌트
List<Order> resultList = 
    em.createQuery("select o from Order o where o.id=:orderId", Order.class)
        .setParameter("orderId", orderId)
        .setHint("javax.persistence.fetchgraph", em.getEntityGraph("Order.withAll"))
        .getResultList();
-- 실행된 SQL
select o.*, m.*, oi.*, i.*
from Order o
inner join  
    Member m
        on o.MEMBER_ID=m.MEMBER_ID
left outer join
    ORDER_ITEM oi
        on o.ORDER_ID=oi.ORDER_ID
left outer join
    Item i
        on oi.ITEM_ID=i.ITEM_ID
where
    o.ORDER_ID=?

4.5 동적 엔티티 그래프

// 동적 엔티티 그래프
EntityGraph<Order> graph = em.createEntityGraph(Order.class);
graph.addAttributeNodes("member");

Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
Order order = em.find(Order.class, orderId, hints);

// 동적 엔티티 그래프 subgraph
EntityGraph<Order> graph = em.createEntityGraph(Order.class);
graph.addAttributeNodes("member");
Subgraph<OrderItem> orderItems = graph.addSubgraph("orderItems");
orderItems.addAttributeNodes("item");

Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
Order order = em.find(Order.class, orderId, hints);

엔티티 그래프 정리

ROOT에서 시작

이미 로딩된 엔티티

fetchgraph, loadgraph의 차이