이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.
즉시 로딩과 지연 로딩
public void printUserAndTeam(String memberId) {
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("회원 이름: " + member.getUsername());
System.out.println("소속팀: " + team.getName());
}
회원과 팀을 함게 출력해야하는 로직에서는 상관 없지만, 단순히 회원만 출력하는 로직에서는 굳이 팀까지 DB에서 조회해서 가져올 필요가 없다.
public void printUser(String memberId) {
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("회원 이름: " + member.getUsername());
}
지연 로딩
@ManyToOne(fetch = FetchType.LAZY) 어노테이션은 JPA에서 연관 관계를 정의할 때 사용되며, fetch = FetchType.LAZY 옵션을 통해 지연 로딩(Lazy Loading)을 지정할 수 있다.
이는 연관된 엔티티를 실제로 사용할 때까지 데이터베이스 조회를 지연시키는 방식이다.
- @ManyToOne, @OneToOne은 기본(default)이 즉시 로딩
- @OneToMany, @ManyToMany는 기본(default)이 지연 로딩
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long ID;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.LAZY) //지연 로딩 설정
@JoinColumn(name = "TEAM_ID")
private Team team;
//getter and setter...
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
//getter and setter...
}
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());
System.out.println("m = " + m.getTeam().getClass()); //프록시
System.out.println("=======");
m.getTeam().getName(); //프록시 초기화
즉시 로딩
@ManyToOne(fetch = FetchType.EAGER) 어노테이션은 JPA에서 연관된 엔티티를 즉시 로딩하도록 설정하는 방법이다.
즉, 해당 엔티티를 조회할 때 연관된 엔티티도 함께 로드된다. 이는 기본적으로 사용되는 로딩 방식이기도 하다.
하지만 실무에서는 즉시 로딩을 사용해선 안된다!
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.EAGER) //즉시 로딩 설정
@JoinColumn(name = "TEAM_ID")
private Team team;
}
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());
System.out.println("m = " + m.getTeam().getClass());
System.out.println("=======");
m.getTeam().getName();
즉시 로딩의 문제점
- 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
- @ManyToOne, @OneToOne은 기본이 즉시 로딩 -> LAZY로 설정을 변경해서 사용
- @OneToMany, @ManyToMany는 기본이 지연 로딩
- 가급적 지연 로딩을 사용해야 한다.
영속성 전이(CASCADE)
JPA에서 영속성 전이(Cascade)는 엔티티의 상태 변화가 연관된 엔티티에게도 전이되는 것을 의미한다.
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들도 싶을 때 사용한다.
예를 들어, 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장해야할 때 사용한다.
참조하는 곳이 하나일 때 사용해야한다. 여러 곳에 연관관계가 있을 때 사용하면 안 된다.
이를 통해 개발자는 연관된 엔티티의 상태를 관리하기 쉽게 할 수 있다. @ManyToOne, @OneToMany, @OneToOne, @ManyToMany와 같은 관계에서 cascade 속성을 설정할 수 있다.
@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)
주의
영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없다.
엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐이다.
Cascade 유형
- CascadeType.PERSIST: 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화한다.
- CascadeType.MERGE: 엔티티를 병합할 때 연관된 엔티티도 함께 병합한다.
- CascadeType.REMOVE: 엔티티를 삭제할 때 연관된 엔티티도 함께 삭제한다.
- CascadeType.REFRESH: 엔티티를 새로고침할 때 연관된 엔티티도 함께 새로고침한다.
- CascadeType.DETACH: 엔티티를 준영속 상태로 만들 때 연관된 엔티티도 함께 준영속 상태로 만든다.
- CascadeType.ALL: 모든 Cascade 유형을 포함한다.
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) //영속성 전이 설정
private List<Child> childList = new ArrayList<>();
public void addChild(Child child) {
childList.add(child);
child.setParent(this);
}
//getter and setter...
}
@Entity
public class Child {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
//getter and setter...
}
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
//em.persist(parent);
//em.persist(child1);
//em.persist(child2);
em.persist(parent);
고아 객체
orphanRemoval = true
JPA에서 고아 객체(orphan entity)란 부모 엔티티와의 연관 관계가 제거되어 더 이상 참조되지 않는 자식 엔티티를 의미한다. 이러한 고아 객체는 데이터베이스에서 자동으로 삭제되도록 설정할 수 있다. 이를 위해 JPA에서는 orphanRemoval 속성을 제공한다
고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
orphanRemoval = true 설정을 통해 고아 객체가 발생할 경우 자동으로 삭제되도록 할 수 있다.
이 설정은 @OneToMany 및 @OneToOne 관계에서만 사용 가능하다.
Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);//자식 엔티티를 컬렉션에서 제거
//DELETE FROM CHILD WHERE ID=?
참조하는 곳이 하나일 때 사용해야 한다.
특정 엔티티가 개인 소유할 때 사용해야 한다.
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) //영속성 전이 설정, 고아객체 삭제 설정
private List<Child> childList = new ArrayList<>();
public void addChild(Child child) {
childList.add(child);
child.setParent(this);
}
//getter and setter...
}
@Entity
public class Child {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
//getter and setter...
}
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
em.flush();
em.clear();
Parent findParent = em.find(Parent.class, parent.getId());
findParent.getChildList().remove(0);
개념적으로 부모를 제거하면 자식은 고아가 된다.
따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다.
이것은 CascadeType.REMOVE처럼 동작한다.
CascadeType.ALL + orphanRemoval=true
두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다.
쉽게 설명하면, 부모 엔티티는 영속성 컨텍스트를 통해 관리되지만, 자식 엔티티는 영속성 컨텍스트가 아닌 부모 엔티티를 통해서 관리되는 것 처럼 보이는 것이다
'Java Category > JPA' 카테고리의 다른 글
[JPA] JPQL 기본 문법 (0) | 2024.07.29 |
---|---|
[JPA] 값 타입(Value Type) (0) | 2024.07.28 |
[JPA] 프록시(Proxy) (0) | 2024.07.26 |
[JPA] 상속관계 매핑 (0) | 2024.07.25 |
[JPA] 다양한 연관관계 매핑 (5) | 2024.07.24 |