개요
나는 Spring을 배운 이래로 항상 JPA를 사용해서 프로젝트를 진행했다. 이력서나 자기소개에도 JPA을 쓸 줄 안다~고 쓰고 다녔고. 그런데 요즈음, 내가 JPA를 잘 이해해서 100% 활용하기보단 관습적인 코드를 죽어라 쓰고 있는 것 같다고 느꼈다 🤔
예를 들자. cascade를 들으면 제일 먼저 생각나는 건 DBMS의 on update, on delete cascade 옵션이다. 하지만 JPA의 cascade가 이들과 동일한 개념이라고 생각하면 아주 곤란해진다. 엄밀하게는 달라! 😇
고런고로 이번 기회에 JPA 영속성 전이 개념을 좀 더 확실히 정리해보려 한다.
영속성 컨텍스트 (persistence context)
이 포스트를 읽으시는 분들 중 영속성 개념이 아직 생소한 분들도 계실 것 같다. 그런 분들이 참고하시면 좋을 것 같아, 영속성 컨텍스트 개념에 대해 간단하게 정리해봤다 🤟 (필요 없으신 분은 바로 다음 목차로!)
영속성 컨텍스트를 직역하면, '엔티티를 영구 저장하는 환경' 이라는 뜻이다. 들어도 무슨 소린지 모르겠다면 정상이다. 좀 더 풀어서 설명해보자.
1️⃣ 엔티티 매니저
영속성 컨텍스트를 알기 위해, 먼저 엔티티 매니저 개념에 대해 알아야 한다. 자바 어플리케이션 위에서 데이터에 대한 CRUD 처리를 하기 위해, 자바 객체(= 엔티티)와 데이터베이스(= 테이블)를 매핑하고 엔티티를 관리해야 한다.
우리가 동물원을 운영한다고 가정해보자. 운영자는 동물원에 있는 동물들을 관리해야 한다. 하지만 매일 아침 각 우리를 돌면서 동물이 몇 마리 있는지 세고, 동물원에 있는 모든 동물의 이름을 외우고, 각각의 동물의 암수를 구분하는 건 너무 힘들고 번거롭다. 그래서 우리는 좀 더 편하게 동물들을 관리하기 위해, 어떤 메모장에 모든 동물의 정보를 정리해두기로 했다.
이 과정을 통해, 이제 우리는 메모장만 보면 동물원에 있는 모든 동물들의 정보를 알 수 있게 되었다. A 우리에 있는 암사자가 몇 마리인지, 그 사자들의 이름과 나이는 무엇인지를 외우지 않고도 쉽게 파악할 수 있게 된 것!
여기서 사용한 메모장이 바로 엔티티 매니저이다. 즉, 고수준 (= 자바)에서 저수준 (=데이터베이스)를 다룰 수 있게 해주는 일종의 가상의 데이터베이스라고 이해하면 쉽다.
2️⃣ 영속성 컨텍스트
엔티티 매니저를 이해했다면 영속성 컨텍스트는 간단하다!
위에서 엔티티 매니저를 이용해 고수준(자바)에서 저수준(DB)를 다룰 수 있다고 말했다. 하지만 곰곰히 생각해보자. 자바 객체를 new로 뚝딱 만든다고 그게 DB에 저장될까? 반대로, DB에 새로운 레코드가 추가되었다고 그 데이터와 맵핑된 자바 객체가 자동으로 생길까?
아쉽게도 아니다. 인스턴스는 메모리에 할당되었다가, GC가 돌 때 사라질 것이다. 그리고 DB엔 아무 정보도 남지 않는다. 우리가 물건을 일억개 팔았는데 DB에 아무 기록도 남지 않아 정산을 받지 못 했다고 생각해보자. 큰일이다...
따라서 이런 불상사를 피하기 위해, 엔티티 매니저가 엔티티를 저장 또는 조회할 때 이들 데이터는 영구히 저장될 필요가 있다. 이렇게 엔티티가 저장되고, 실제 데이터베이스의 값과 동기화되는 환경을 영속성 컨텍스트라고 한다.
정리하면, 엔티티 매니저는 실제 데이터베이스의 값과 자바 객체를 동기화하는 환경인 영속성 컨텍스트에서 엔티티를 보관하고 관리한다고 볼 수 있다.
영속성 컨텍스트는 이 외에도 식별자, 1차 캐시, 더티 체킹, 지연 로딩 등등 많은 특징을 갖지만, 이번 포스트는 영속성 전이에 대한 내용이므로 생략한다. 대신 잘 정리한 포스트를 첨부하니 관심 있으신 분들은 방문해보셔도?! 👀
영속성 전이 : CASCADE
앞에서 살펴본 영속성 컨텍스트와 관련하여, 엔티티는 총 4가지의 상태를 갖는다.
비영속 (new / transient) | 영속성 컨텍스트와 전혀 관계가 없는 상태 |
영속 (managed) | 영속성 컨텍스트에 저장된 상태 |
준영속 (detached) | 영속성 컨텍스트에 저장되었다가 분리된 상태 |
삭제 (removed) | 삭제된 상태 |
즉, 위에서 말한 '영속성 컨텍스트에서 관리되는 엔티티' 들은 엄밀히 말하면 '영속' 상태의 엔티티 이다.
영속성 전이는 앞서 살펴본 개념들에 비해 완전 간단하다. 말 그대로, 직역해서 특정 엔티티의 영속성이 다른 엔티티로 전이되는 기능을 의미한다. 엥 그런게 왜 필요한데요? 앉아 봐요. 그런게 있음.
우리는 자바 어플리케이션에서 데이터를 관리하기 위해, 엔티티들이 영속 상태에 있어야 함을 알고 있다. 따라서 위와 같이 일대다 관계의 엔티티가 있다면, 새로운 데이터를 저장하기 위해 아래의 과정을 거쳐야 한다.
1. 학과 저장하기
2. 해당 학과의 학생 1 저장하기
3. 해당 학과의 학생 2 저장하기
코드로 치면 이런 모습이 될 거다.
public static void saveNoCascade(EntityManager em) {
// 새로운 학과 생성
Department department = new Department();
em.persist(department);
// 학생 1 저장
Student student = new Student();
student.setDepartment(department); // 부모 지정
department.getStudent().add(student); // 부모에도 자식 연결
em.persist(student);
// 학생 2 저장
Student student2 = new Student();
student2.setDepartment(department); // 부모 지정
department.getStudent().add(student2); // 부모에도 자식 연결
em.persist(student2);
}
학생이 둘밖에 없는데도 아주 번거롭다... 대형 학과라서 학생이 200명씩 있으면? 그냥 슬퍼지는거다. 이 친구들 각각을 persist 시키는건 좀 아닌 것 같다는 생각이 든다. (Spring Data JPA로 따지면, 객체 하나하나를 각각 save해줘야 하는 상황)
이럴 때 영속성 전이를 사용해 부모와 자식 엔티티를 한 번에 영속화할 수 있다!
public static void saveWithCascade(EntityManager em) {
// 학생 생성
Student student = new Student();
Student student2 = new Student();
// 학과 생성
Department department = new Department();
// 연관 관계 추가
student.setDepartment(department);
student2.setDepartment(department);
department.getStudent().add(student);
department.getStudent().add(student2);
// 연결된 부모, 자식을 한 번에 저장
em.persist(department);
}
위 코드처럼, 부모-자식을 나타내는 연관 관계 설정 + cascade 옵션만 되어 있다면, 부모를 영속 상태로 만들 때 연관된 자식까지 한 번에 영속 상태로 만들 수 있다 🤩 부모 엔티티의 영속성을 자식 엔티티로 전이한 것!
(❗️ 영속성 전이는 연관된 엔티티를 함께 영속해 줄 뿐, 연관 관계를 설정해주는 건 아니라는 점에 주의하자❗️ )
위의 예제는 생성(저장)에 관한 코드였지만, 삭제도 동일하다. 여기는 MySQL의 on delete, on update cascade를 알고 있으면 쉽다.
외래키 제약 조건이 존재하기 때문에 부모 엔티티를 지우기 위해선 해당 부모와 연관이 있는 자식 엔티티들도 모두 지워주어야 한다. 위 예제의 경우, student 둘을 지운 후 department를 지우는 식으로 총 3번의 delete 과정이 필요하다.
저장과 마찬가지로 연관 관계 설정 + cascade 옵션만 설정되어 있다면, 부모 엔티티를 삭제할 때 연관된 자식 엔티티들도 함께 삭제할 수 있다.
이런 영속성 전이를 가능하게 만들어주는 CASCADE 종류들을 살펴보자. 다음은 CascadeType enum의 옵션들이다.
ALL (모두 적용) | 아래 모든 동작을 포함하는 종합 옵션 |
PERSIST (영속화) | 부모 엔티티가 영속 상태가 될 때, 자식 엔티티도 영속 상태가 된다. |
MERGE (병합) | 부모 엔티티가 병합될 때, 자식 엔티티도 병합된다. |
REMOVE (삭제) | 부모 엔티티가 삭제될 때, 자식 엔티티도 삭제된다. |
REFRESH (새로고침) | 부모 엔티티가 새로고침 될 때, 자식 엔티티도 자동으로 새로고침된다. (= DB 동기화) |
DETACH (분리) | 부모 엔티티가 영속성 컨텍스트에서 분리될 때, 자식 엔티티도 분리된다. |
사용 방법은 다음과 같이 엔티티의 연관 관계 필드에 지정하는 식이다.
@Entity
public class Department {
@OneToMany(mappedBy = "department", cascade = CascadeType.PERSIST)
private List<Student> students = new ArrayList<Student>();
}
고아 객체
위에서 우리는 영속성 전이를 이용해, 부모 엔티티를 삭제할 때 연관된 자식 엔티티도 함께 삭제하는 CascadeType.REMOVE에 대해 알아봤다. 그럼 이제 이런 생각이 든다.
부모 엔티티를 삭제하는게 아니라, 부모-자식 간의 연관 관계를 끊어도 자식 엔티티가 삭제될까? 🤔
A. 아니오. 관계를 제거해도 자식 엔티티는 고대로 남아있습니다 🙄
이처럼, 부모 엔티티와 연관 관계가 끊어진 자식 엔티티를 '고아 객체 (ORPHAN)' 라고 부른다. 참말로 직관적인 네이밍..
JPA는 개발자의 수고를 덜어주기 위해, 연관 관계가 끊어질 때 자식 엔티티를 자동으로 삭제해주는 고아 객체 제거 기능을 지원해준다. 참조가 제거된 엔티티는 다른 곳에서는 참조하지 않는 고아 객체로 보고 정리해주는 것이다. 코드로는 다음과 같이 연관 관계 필드에 orphanRemoval 옵션을 설정하면 된다.
@Entity
public class Department {
@OneToMany(mappedBy = "department", orphanRemoval = true)
private List<Student> students = new ArrayList<Student>();
}
❗️ 고아 객체 삭제 기능을 사용할 때는 다음을 참고하자.
- 자식 엔티티가 참조하는 부모가 하나 일 때만 사용하자! 해당 자식을 갖는 부모가 아직 남아있는데 낼름 삭제되면 곤란하다.
- 즉, @OneToOne, @OneToMany에만 사용할 수 있는 기능이다.
- 부모 엔티티를 제거해도 자식 엔티티는 고아가 된다. 즉, CascadeType.REMOVE와 동일한 기능을 제공한다.
- CascadeType.REMOVE < orphanRemoval = true로, 고아 객체 삭제 쪽의 범위가 더 크다고 보면 된다.
마무리
오늘은 JPA의 영속성 전이와 고아 객체에 대해 정리해봤다. 이 기능들은 이렇게만 보면 굉장히 편리해보이지만, 실제로 프로젝트에서 사용하면 맥락에 따라 조금 곤란한 상황을 만들기도 한다. 항상 그렇듯 트레이드 오프를 고려하여, 내게 필요한 기능인지 검토해보는 과정이 필요하겠다 👊
레퍼런스
- 자바 ORM 표준 JPA 프로그래밍 : https://www.yes24.com/Product/Goods/19040233
- JPA CascadeType.REMOVE vs orphanRemoval = true : https://tecoble.techcourse.co.kr/post/2021-08-15-jpa-cascadetype-remove-vs-orphanremoval-true/
'Develop > Spring' 카테고리의 다른 글
[Spring Boot/JPA] @Transactional을 썼는데도 Dirty Checking에 실패한다면? (feat. clearAutomatically = true) (7) | 2023.09.30 |
---|---|
[Spring Boot] 외부 API 호출 도구 OpenFeign : RestTemplate, WebClient와 비교까지 (0) | 2023.09.20 |
[Spring Boot] Auto Increment PK Id를 노출하지 않으면서 API에 활용하는 방법 (4) | 2023.06.28 |
[Spring Boot] 자바 스프링에서 처리율 제한 기능을 구현하는 4가지 방법 (2) | 2023.06.21 |
[Spring Boot] Jsoup으로 OG태그 메타 데이터 크롤링하기 (1) | 2023.06.14 |