위와 같은 상황에서 Member를 조회할 때 Team도 같이 DB에서 조회를 해야할까?
Member만 사용하고 Team은 사용 안하는 경우 둘다 조회하면 손해다.
JPA는 이것을 지연 로딩과 프록시로 해결한다.
em.find()
: 데이터베이스를 통해 실제 엔티티 객체 조회
em.getReference()
: 데이터베이스 조회를 미루는 가짜 엔티티 객체 조회
getReference()
를 호출하는 시점에는 db에 쿼리를 안한다.
그 값이 실제 사용되는 시점에는 쿼리가 나간다.
getReference()
는 껍데기는 똑같은데 안이 텅텅 빈 프록시 객체를 반환함. 그리고 target이 진짜 엔티티 객체를 가리킴
프록시는 실제 엔티티 객체를 상속해서 만들어진다.
실제 클래스와 겉 모양이 같다.
사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.
프록시 객체는 실제 객체의 참조 target을 보관한다.
프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
처음에 getName
을 호출하면 target에 값이 없기 때문에 영속성 컨텍스트에 초기화를 요청한다. 영속성 컨텍스트가 DB를 조회해서 실제 Entity 객체를 생성하고 target과 연결해준다.
==
비교가 아닌 instance of
를 사용해야함 instance of
를 사용해라m1 instanceof Member
em.getReference()
를 호출해도 실제 엔티티를 호출한다. ==
비교하면 항상 true가 되어야 함em.find()
해도 프록시를 조회한다em.detach(refMember)
LazyInitializationException
PersistenceUnitUtil.isLoaded(Object entity)
Hibernate.initalize(refMember)
로 강제 초기화 가능 다시 예시로 돌아가서 Member만 조회할 것인데 Team 까지 조회하면 손해다. JPA는 지연로딩이라는 옵션을 제공한다
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn
private Team team;
이렇게 할 경우 Team은 프록시객체로 조회하고 Member만 db에서 조회한다. 지연 로딩으로 설정하면 연관된 객체를 프록시로 가져온다.
team.getName()
으로 실제 Team을 사용하는 시점에 Team이 초기화된다.
비즈니스 로직 상 Member를 조회했을 때 Member만 사용하고 Team은 거의 쓰지 않는다면 지연 로딩으로 설정하는게 맞다.
Member와 Team을 자주 함께 사용할 수도 있다. 그러면 즉시로딩으로 바꿀 수 있다.
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn
private Team team;
설정값을 Eager로 주면 즉시 로딩을 한다.
한번에 진짜 객체를 전부 가져온다.
즉 Member를 조회할 때 Team까지 같이 가져온다.
대부분의 JPA 구현체는 가능하면 조인을 사용해서 SQL 한번에 함께 조회한다.
@ManyToOne
@OneToOne
은 기본이 즉시 로딩이다. @OneToMany
@ManyToMany
는 기본이 지연 로딩이다특정 엔티티를 영속 상태로 만들 때 연관된 엔티티를 함께 영속 상태로 만들고 싶을 때 사용한다.
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
List<Child> childList = new ArrayList<>();
public void addChild(Child child){
childList.add(child);
child.setParent(this);
}
}
addChild는 연관관계 편의 메소드이다.
@Entity
public class Child {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name="parent_id")
private Parent parent;
}
이런 경우 Parent 하나에 Child 객체 2개가 있을 경우, 아래와 같이 영속성 컨텍스트에 모두 persist
해줘야 한다.
Parent parent = new Parent();
Child child1 = new Child();
Child child2 = new Child();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
em.persist(child1);
em.persist(child2);
Parent 중심으로 코드를 작성하는데 Child까지 persist 해야 하는 귀찮은 상황이 발생한다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
List<Child> childList = new ArrayList<>();
Parent에서 위와 같이 수정해준다.
그러면 em.persist(parent)
만 해도 child1,child2가 persist 된다.
연관관계와 상관없이 Parent를 persist 할 때 Child까지 persist 하고 싶으면 Parent의 Child 필드에 cascade = CascadeType.ALL)
를 걸어주면 된다.
Parent - Child 처럼 Parent가 Child 여럿을 관리하고 Child랑 관련된게 Parent 하나일 때는 이렇게 사용해도 된다.
하지만 Child랑 연관된 객체가 또 있으면 CASCADE를 사용하면 안된다.
고아 객체 제거는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 것이다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL,
orphanRemoval = true)
List<Child> childList = new ArrayList<>();
orpahnRemoval=true
속성으로 사용한다.
Parent가 관리하는 컬렉션 리스트에서 Child가 제거되면 Child에 대한 delete쿼리가 나간다.
@OneToOne
, @OneToMany
만 가능 CascadeType ALL + orphanRemoval=true
같이 사용하면?
em.persist()
와 em.remove()
로 관리