즉시 로딩과 지연 로딩
다대일 연관관계가 있는 멤버와 팀이 있을 때, 단순히 멤버 정보만 사용하는 비즈니스 로직이라서 멤버를 조회할 때 팀이 조회가 안되어도 된다고 가정하자.
이런 경우에는 지연 로딩을 쓸 수 있다.
지연 로딩
⇒ 연관된 것을 proxy로 가져오고, 연관된 것의 실제 값을 사용하는 시점에 실제 쿼리가 나감
ex) 멤버를 사용할 땐 팀은 proxy로 가져오고, 팀의 실제 값을 사용할 때 DB에서 가져옴
Member
@ManyToOne(fetch = FetchType.LAZY) // proxy 객체로 조회한다.
@JoinColumn
private Team team;
Member 클래스만 DB에서 조회한다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member1= new Member();
member1.setUsername("member1");
member1.setTeam(team);
em.persist(member1);
em.flush();
em.clear();
Member m = em.find(Member.class, member1.getId()); // 멤버만 DB에서 조회
System.out.println("m.getTeam().getClass() = " + m.getTeam().getClass()); // Team은 proxy로 조회
System.out.println("========");
m.getTeam().getName(); // 초기화: 실제 team 의 값에 접근할 때 프록시가 초기화되면서 쿼리가 날라가 DB에서 값을 가져옴
System.out.println("========");
반면에 멤버와 팀을 자주 함께 사용한다면, 즉시 로딩을 쓸 수 있다.
즉시 로딩
Member 조회 시 항상 Team 도 조회한다.
JPA 구현체는 가능하면 조인을 사용해 SQL 한번에 함께 조회한다.
Member
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn
private Team team;
Member m = em.find(Member.class, member1.getId()); // 멤버와 팀을 조인해 가져옴
System.out.println("m.getTeam().getClass() = " + m.getTeam().getClass()); // class hellojpa.Team (실제 팀 객체)
실무에서는 즉시 로딩을 쓰면 안된다.
즉시 로딩을 쓰면 예상하지 못한 SQL이 발생하기 때문이다.
예를 들어 JPQL로 Member를 조회하게 되면, member와 team 따로 각각 select 문이 나간다.
List<Member> members = e.createQuery("select m from Member m", Member.class).getResultList();
만약 member1과 membr2에 각각 team1, team2를 넣고 위와 같이 멤버를 조회하면
team1, team2를 각각 select하는 쿼리가 나간다.
이걸 JPQL에서의 N+1 문제라고 한다.
처음에 멤버를 조회하는 쿼리를 하나 날렸는데, 추가 쿼리가 N개(멤버 조회시 나온 결과 값 개수만큼) 나간다는 것이다.
모든 관계를 지연 로딩으로 쓰고, 정말 꼭 같이 가져와야 하는 경우에는 3가지 방법을 쓰면 된다.
- 패치 조인(기본) - 런타임에 동적으로 원하는 것을 조인해 같이 가져오게 함
- 엔티티 그래프 어노테이션 사용
- 배치 사이즈
@ManyToOne, @OneToOne 은 기본이 즉시 로딩이므로 LAZY로 설정하자.
나머지는 기본이 지연로딩이다.
영속성 전이와 고아 객체
영속성 전이 : CASCADE
특정 엔티티를 영속성 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
cascade = CascadeType.ALL 를 사용한다.
ex) 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장
*영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없다.
엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐이다.
CASCADE의 종류
ALL : 모두 적용
PERSIST : 영속
REMOVE : 삭제
MERGE : 병합
REFRESH : REFRESH
DETATCH : DETACH
Parent
...
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();
public void addChild(Child child){
childList.add(child);
child.setParent(this);
}
Child
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name="parent_id")
private Parent parent;
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
tx.commit();
parent를 persist했는데 casecade를 선언한 child들까지도 persist 된다.
고아 객체
고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
Parent findParent = em.find(Parent.class, parent.getId());
findParent.getChildList().remove(0); // 자식 엔티티를 컬렉션에서 제거
컬렉션에서 빠진 객체는 삭제가 된다.
delete문이 나가는 것을 볼 수 있다.(DELETE FROM CHILD WHERE ID=?)
참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이다.
참조하는 곳이 하나일 때 사용해야 한다.
특정 엔티티가 개인 소유할 때만 써야 한다.
@OneToOne, @OneToMany 만 가능하다.
영속성 전이 + 고아 객체
CasecadeType.ALL + orphanRemoval = true
두 옵션을 활성화 하면 부모 엔티티를 통해 자식의 생명 주기를 관리할 수 있다.
도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용하다.
인프런 자바 ORM 표준 JPA 프로그래밍 - 기본편을 수강하고 정리한 글입니다.
'BACK > JPA' 카테고리의 다른 글
[JPA] TIL 7일차 - JPA 다양한 연관관계 매핑 (0) | 2024.04.03 |
---|---|
[JPA] JPA의 값 타입 (기본 값, 임베디드, 컬렉션 타입) (0) | 2024.04.02 |
[JPA] JPA 프록시의 특징 (0) | 2024.04.02 |
[JPA] 엔티티 매핑 (1) | 2024.04.02 |
[JPA] 영속성 관리(1차 캐시, 쓰기 지연, 변경 감지) (1) | 2024.04.01 |