'분류 전체보기' 카테고리의 글 목록 :: 잡다한 프로그래밍
반응형

페이징 처리

JPA는 페이징을 다음과 같이 추상화합니다:

String jpql = "SELECT m FROM Member m ORDER BY m.name DESC";
List<Member> result = em.createQuery(jpql, Member.class)
                        .setFirstResult(10)
                        .setMaxResults(20)
                        .getResultList();

→ 실제 SQL로 번역되어 효율적인 페이징 처리 가능 (LIMIT, OFFSET 등)

 

 

JOIN

1. 내부 조인 (INNER JOIN)

 
// jpql
SELECT m FROM Member m INNER JOIN m.team t

// sql
SELECT m.* FROM Member m JOIN Team t ON m.team_id = t.id

 

2. 외부 조인 (LEFT JOIN)

// jpql
SELECT m FROM Member m LEFT JOIN m.team t

//sql
SELECT m.* FROM Member m LEFT OUTER JOIN Team t ON m.team_id = t.id

 

3. 세타 조인 (연관관계 없는 조인)

SELECT COUNT(m) FROM Member m, Team t WHERE m.username = t.name

 

ON절 활용 (JOIN 필터링)

1. 조건 필터링 조인

SELECT m, t FROM Member m LEFT JOIN m.team t ON t.name = '팀A'

SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.team_id = t.id AND t.name = '팀A'
 

 

2. 연관 관계가 없는 엔티티 외부 조인

SELECT m, t FROM Member m LEFT JOIN Team t ON m.username = t.name

 

서브쿼리

JPQL에서는 WHERE / HAVING / SELECT(하이버네이트) 절에서 서브쿼리를 지원합니다 (단, FROM 절 서브쿼리는 불가).

예시 1: 평균보다 나이 많은 회원

SELECT m FROM Member m 
WHERE m.age > (SELECT AVG(m2.age) FROM Member m2)

 

2: 주문한 적 있는 회원

SELECT m FROM Member m 
WHERE (SELECT COUNT(o) FROM Order o WHERE o.member = m) > 0

 

 

서브쿼리 지원 함수

  • [NOT] EXIST: 서브쿼리에 결과가 존재하면 참
  • ALL: 모두만족하면 참
  • ANY, SOME: 하나라도 만족하면참
  • [NOT] IN: 서브쿼리 결과중 하나라도 같은것이 있으면 참
 

 

JPQL 타입 표현과 기타

 

조건식 CASE

기본 CASE

SELECT CASE 
  WHEN m.age <= 10 THEN '학생요금' 
  WHEN m.age >= 60 THEN '경로요금' 
  ELSE '일반요금' 
END FROM Member m

단순 CASE

SELECT CASE t.name 
  WHEN '팀A' THEN '인센티브110%' 
  WHEN '팀B' THEN '인센티브120%' 
  ELSE '인센티브105%' 
END FROM Team t

 

JPQL 기본 함수

함수 설명
CONCAT(a, b) 문자열 결합
SUBSTRING(s, i, len) 문자열 자르기
TRIM(s) 공백 제거
LOWER, UPPER 소문자/대문자
LENGTH(s) 문자열 길이
LOCATE(sub, s) 위치 검색
ABS, SQRT, MOD 수치 연산
SIZE(c) 컬렉션 크기
INDEX(c) 컬렉션 인덱스 (JPA 전용)

 

사용자 정의 함수

SELECT function('group_concat', i.name) FROM Item i

💡 사용자 정의 함수는 DB 방언(Dialect)에 등록 필요 (이미 대부분 등록되어있음) 사용만 하면돼

 

 

반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 프로젝션  (2) 2025.07.31
[JPA] JPQL 기본 문법과 기능  (2) 2025.07.30
[JPA] JPQL 소개  (2) 2025.07.30
[JPA] 값 타입 컬렉션  (1) 2025.07.25
[JPA] 값 타입과 불변 객체  (2) 2025.07.23
반응형

JPA에서 프로젝션(Projection)은

SELECT 절에서 어떤 데이터를 조회할지 명시하는 것을 말합니다.

프로젝션의 3가지 대상

엔티티 프로젝션

String jpql = "SELECT m FROM Member m";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();

 

  • 영속성 컨텍스트 관리 대상 → result 안에 들어있는 각 Member는 수정 시 dirty checking 되어 UPDATE 발생

연관된 엔티티 조회 (Team)

String jpql = "SELECT m.team FROM Member m";
List<Team> result = em.createQuery(jpql, Team.class).getResultList();

주의: 내부적으로 JOIN 발생
추천 방식은 아래처럼 명시적 JOIN으로 작성 (쿼리랑 비슷해야 예측이 가능함)

String jpql = "SELECT t FROM Member m JOIN m.team t";
List<Team> result = em.createQuery(jpql, Team.class).getResultList();

임베디드 타입 프로젝션

String jpql = "SELECT o.address FROM Order o";
List<Address> result = em.createQuery(jpql, Address.class).getResultList();

 

더보기

해당 Address 객체는 영속성 컨텍스트 상태인가?

=> 즉 변경하면 update쿼리 나가?

 

안나감! Embedded는 값타입이니까!

=> 얘는 영속성 컨텍스트 돼? 찾아보기

스칼라 타입 프로젝션

// 강의에서 설명한 쿼리방식은 생략 너무 보기 불편함

String jpql = "SELECT m.username, m.age FROM Member m";
List<Object[]> result = em.createQuery(jpql).getResultList();

for (Object[] row : result) {
    String username = (String) row[0];
    int age = (int) row[1];
}

new 명령어로 DTO 직접 조회

String jpql = "SELECT NEW com.example.dto.MemberDto(m.username, m.age) FROM Member m";
List<MemberDto> result = em.createQuery(jpql, MemberDto.class).getResultList();

 

  • 단순 값을 DTO로 바로 조회
  • 패키지명 포함한 전체 클래스명 필수
  • DTO에는 해당 생성자가 있어야 함

 

반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 페이징, 조인, 서브쿼리 등  (3) 2025.07.31
[JPA] JPQL 기본 문법과 기능  (2) 2025.07.30
[JPA] JPQL 소개  (2) 2025.07.30
[JPA] 값 타입 컬렉션  (1) 2025.07.25
[JPA] 값 타입과 불변 객체  (2) 2025.07.23
반응형

JPQL 설명에 필요할 예제 모델은 아래와 같다

 

JPQL 문법 구조

1. SELECT 문

 

select_절

from_절

[where_절]

[groupby_절]

[having_절]

[orderby_절]

SELECT m FROM Member m WHERE m.age > 18

 

2. UPDATE 문

update_절 [where_절]
UPDATE Member m SET m.age = 30 WHERE m.username = :name

3. DELETE 문

delete_절 [where_절]
DELETE FROM Member m WHERE m.age < 18
  • Member는 테이블이 아니라 엔티티 이름 (@Entity(name="이값")
  • m은 필수 별칭(alias) (as 생략 가능)
  • 대소문자 구분:
    • 엔티티와 필드: 구분함 (username ≠ USERNAME)
    • JPQL 키워드: 구분 안함 (select, SELECT 둘 다 가능)

 

집계 함수

SELECT 
    COUNT(m),     -- 총 회원 수
    SUM(m.age),   -- 나이 합
    AVG(m.age),   -- 평균 나이
    MAX(m.age),   -- 최대 나이
    MIN(m.age)    -- 최소 나이
FROM Member m

SELECT m.team.name, AVG(m.age)
FROM Member m
GROUP BY m.team.name
HAVING AVG(m.age) > 20
ORDER BY AVG(m.age) DESC

TypedQuery vs Query

구분 설명 사용 예

 

구분 설명
TypedQuery<T> 반환 타입이 명확할 때
Query 반환 타입이 다양한 경우 (e.g. username, age 같이 복합)
// 반환 타입 명확
TypedQuery<Member> query1 = em.createQuery("SELECT m FROM Member m", Member.class);

// 반환 타입 모호 (String, int 두개 반환)
Query query2 = em.createQuery("SELECT m.username, m.age FROM Member m");

 

결과 조회 API

메서드 설명 예외
메서드 설명 예외
getResultList() 결과가 0개 이상일 때 리스트 반환 없음 (0개면 빈 리스트)
getSingleResult() 결과가 정확히 1개여야 함 0개: NoResultException, 2개 이상: NonUniqueResultException

파라미터 바인딩

1. 이름 기준 바인딩 (권장)

TypedQuery<Member> query = em.createQuery(
    "SELECT m FROM Member m WHERE m.username = :username",
    Member.class
);
query.setParameter("username", "kim");

2. 위치 기준 바인딩 (비추천)

Query query = em.createQuery(
    "SELECT m FROM Member m WHERE m.username = ?1"
);
query.setParameter(1, "kim");
반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 페이징, 조인, 서브쿼리 등  (3) 2025.07.31
[JPA] 프로젝션  (2) 2025.07.31
[JPA] JPQL 소개  (2) 2025.07.30
[JPA] 값 타입 컬렉션  (1) 2025.07.25
[JPA] 값 타입과 불변 객체  (2) 2025.07.23
반응형

JPA는 CRUD 수준에서는 EntityManager.find() 나 Repository.save() 등으로 충분하지만

복잡한 조회나 조건 검색이 필요한 시점부터 다양한 쿼리 전략이 필요해집니다.

 

1. JPQL (Java Persistence Query Language)

객체를 대상으로 쿼리하는 SQL (가장 단순한 조회 방법)

  • SQL과 문법 유사하지만 테이블이 아닌 엔티티를 대상으로 쿼리
  • SELECT m FROM Member m WHERE m.age > 18
  • 내부적으로는 SQL로 변환되어 실행됨

장점

  • SQL 문법에 익숙하다면 금방 적응 가능
  • 표준 JPA 기능으로 설정 없이 사용 가능

단점

  • 동적 쿼리 작성이 어려움
    → 문자열을 직접 조합해야 함 → 유지보수 어렵고 버그 유발
  • 오타나 구조 오류는 런타임에 발견됨
List<Member> result = em.createQuery(
    "SELECT m FROM Member m WHERE m.username LIKE '%kim%'",
    Member.class
).getResultList();

2. Criteria API (JPA 표준 동적 쿼리 빌더)

자바 코드로 JPQL을 빌드하는 방식

장점

  • 동적 쿼리에 적합
  • 컴파일 타임에 타입 오류 검출 가능

단점

  • 너무 복잡하고 장황함 → 실무에서는 거의 사용하지 않음
  • 가독성과 유지보수성 떨어짐
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
Root<Member> m = query.from(Member.class);
query.select(m).where(cb.equal(m.get("username"), "kim"));
List<Member> resultList = em.createQuery(query).getResultList();

 

3. QueryDSL (실무 추천)

장점

  • 코드로 JPQL을 작성 → 문법 오류는 컴파일 시점에 검출
  • 동적 쿼리 작성이 쉽고 가독성이 좋음
  • IDE 자동완성, 리팩토링 쉬움
  • 실무에서 가장 많이 사용되는 JPA 쿼리 도구

단점

  • 초기 설정 필요 (Q클래스 생성, Annotation Processor 설정)
  • JPA에 대한 이해도 필요
QMember m = QMember.member;
List<Member> result = queryFactory
    .selectFrom(m)
    .where(m.age.gt(18))
    .orderBy(m.name.desc())
    .fetch();

4. Native SQL (순수 SQL 사용)

JPA가 지원하지 않는 DB 고유 기능 사용할 때

장점

  • 복잡한 SQL, DB 전용 문법 사용 가능 (예: CONNECT BY, WITH RECURSIVE, 힌트 등)
  • 성능 튜닝 시 유용

단점

  • SQL에 강하게 의존 → DB 이식성 X
  • 결과 매핑이 번거로움 (DTO나 엔티티 수동 매핑 필요)
String sql = "SELECT ID, NAME FROM MEMBER WHERE NAME = 'kim'";
List<Member> result = em.createNativeQuery(sql, Member.class).getResultList();

5. JDBC 직접 사용 / Spring JdbcTemplate / MyBatis 병행 사용

 

  • 단, JPA의 영속성 컨텍스트 플러시를 수동으로 해줘야 함

 

 

 

반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 프로젝션  (2) 2025.07.31
[JPA] JPQL 기본 문법과 기능  (2) 2025.07.30
[JPA] 값 타입 컬렉션  (1) 2025.07.25
[JPA] 값 타입과 불변 객체  (2) 2025.07.23
[JPA] 값 타입  (1) 2025.07.22
반응형

JPA 값 타입 컬렉션 완전 정리

값 타입을 Collection(Set, List) 형태로 저장하는 것

 

기본 관계형 DB는 컬렉션 저장을 지원하지 않는다, 그래서 이미지 처럼 멤버:FAVORITE_FOOD, 멤버:ADDRESS를 1:N구조로 매핑해야한다.

 

멤버 클래스

@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD", joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<>();

@ElementCollection
@CollectionTable(name = "ADDRESS", joinColumns = @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();

 

  • @ElementCollection → 값 타입 컬렉션임을 선언
  • @CollectionTable → 별도 테이블과 조인 매핑
  • @Column → 단일 값 타입일 경우 컬럼명 지정 가능
  • 관계형 데이터베이스는 객체 컬렉션을 하나의 테이블에 바로 저장할 수 없음 → 값 타입 컬렉션은 별도의 테이블에 저장됨

 

더보기

PostgreSQL의 ARRAY 타입은?

PostgreSQL 등은 ARRAY 컬럼을 지원하지만, JPA에서 매핑하려면 커스텀 @Type, UserType 구현이 필요하고 포팅성, 관리 이슈로 권장되지 않음.
JPA는 기본적으로 RDB의 정규화 방식에 맞춰 설계하는 것이 좋음

 

특징

- 멤버를 persist할때 insert쿼리는 컬렉션의 add한 수만큼 발생한다.

- 멤버만 persist해도 컬렉션 insert쿼리가 발생한다 (이유 = 값타입 이므로 member의 라이프사이클에 의존한다, 즉 멤버의 변경에 따라 달라진다)

- 값 타입 컬렉션은 영속성 전이 + 고아객체 제거 기능을 필수로 가진다고 볼 수 있다

- 멤버 조회시, 값 타입 컬렉션은 가져오지 않는다 (지연로딩)

 

Set<String> food 바꾸려면 어케함?

member.getFavoriteFoods().remove("치킨");

member.getFavoriteFoods().add("한식"); // 이렇게 바꿀 수 있음

 

값 타입 컬렉션의 단점

값 타입 컬렉션은 변경 추적이 불가능합니다. 예를 들어:

member.getAddressHistory().remove(oldAddress);
member.getAddressHistory().add(newAddress);

위처럼 변경하면:

  • 기존 관련 row 전체 delete
  • 현재 리스트의 모든 값 bulk insert

즉, 1개 바꿨는데도 전체 delete + insert 발생 → 성능 저하 + Audit 추적 어려움

 

실무 대안: 일대다 매핑을 이용한 값 타입 유사 모델링

@Entity
public class Member {

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "MEMBER_ID") // 단방향 매핑
    private List<AddressEntity> addressHistory = new ArrayList<>();
}
@Entity
public class AddressEntity {

    @Id @GeneratedValue
    private Long id;

    @Embedded
    private Address address; // 불변 값 타입으로 구성
}
반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] JPQL 기본 문법과 기능  (2) 2025.07.30
[JPA] JPQL 소개  (2) 2025.07.30
[JPA] 값 타입과 불변 객체  (2) 2025.07.23
[JPA] 값 타입  (1) 2025.07.22
[JPA] 영속성 전이 CASCADE  (2) 2025.07.17
반응형

JPA 값 타입(Value Type)과 불변 객

JPA에서 값 타입은 복잡한 객체 세계를 단순하고 안전하게 만들기 위해 도입된 개념이다.
값 타입을 안전하게 사용하지 않으면 공유 참조로 인한 예기치 않은 부작용이 발생할 수 있다.

 

 

값 타입(임베디드)의 공유 참조 문제

임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험합니다.
예를 들어 Address 인스턴스를 두 명의 회원에게 할당하면?

Address address = new Address("서울", "강남", "12345");

Member member1 = new Member();
member1.setAddress(address);

Member member2 = new Member();
member2.setAddress(address);

member1.getAddress().setCity("부산"); // ❗ member2의 city도 "부산"으로 바뀜

 

값 타입 복사

공유하지 않고 안전하게 사용하려면 값(인스턴스)을 복사해서 사용해야 합니다.

 

Address address = new Address("서울", "강남", "12345");

Member member1 = new Member();
member1.setAddress(address);

Member member2 = new Member();
member2.setAddress(
    new Address(address.getCity(), address.getStreet(), address.getZipcode())
);

 

객체 타입의 한계와 불변 객체

자바에서 기본 타입은 값 복사, 하지만 객체 타입은 참조 복사가 기본입니다.

객체 타입은 참조값을 직접 대입하는것을 막을 방법이 없다

불변 객체로 설계하는 방법

객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단할 수 있다

 

  • 생성자로만 필드 초기화
  • setter 금지
  • 모든 필드는 final로 선언 권장
  • 변경 시에는 새로운 인스턴스 생성

참고: Integer, String은 자바가 제공하는 대표적인 불변객체

@Embeddable
public class Address {
    private final String city;
    private final String street;
    private final String zipcode;

    protected Address() {} // JPA용 기본 생성자

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}

 

 

값 타입 비교

  • 동일성(identity): == → 인스턴스 참조값 비교
  • 동등성(equality): .equals() → 인스턴스 내부 값 비교

값 타입은 항상 동등성으로 비교해야 함
→ equals()와 hashCode() 반드시 오버라이드 필요

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Address)) return false;
    Address address = (Address) o;
    return Objects.equals(city, address.city)
        && Objects.equals(street, address.street)
        && Objects.equals(zipcode, address.zipcode);
}
반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] JPQL 소개  (2) 2025.07.30
[JPA] 값 타입 컬렉션  (1) 2025.07.25
[JPA] 값 타입  (1) 2025.07.22
[JPA] 영속성 전이 CASCADE  (2) 2025.07.17
[JPA] 즉시 로딩과 지연 로딩  (2) 2025.07.16
반응형

JPA 값 타입(Value Type) 정리

JPA에서는 데이터를 다루는 방식에 따라 엔티티(Entity Type)와 값 타입(Value Type)으로 구분합니다.

1. 엔티티 타입 (Entity Type)

  • @Entity로 정의되는 클래스
  • 식별자(PK)를 통해 영속성 컨텍스트에서 추적 가능
  • 변경이 발생해도 식별자 기준으로 동일 객체로 인식
  • 생명주기를 독립적으로 가짐

예: Member 엔티티의 age가 바뀌어도 여전히 같은 식별자(PK)를 가진 동일한 객체로 추적 가능

2. 값 타입 (Value Type)

  • int, Integer, String 처럼 단순히 값으로 사용하는 자바 기본타입이나 객체
  • 엔티티처럼 독립적인 식별자 없음
  • 단순히 값 자체로만 의미를 가짐 → 변경 시 완전히 다른 값으로 교체됨
  • 생명주기는 소유한 엔티티에 의존

기본값 타입

  • Java 기본 타입 혹은 래퍼 클래스 (int, Integer, String, Long 등)
  • 예: String name, int age
  • 엔티티와 함께 저장되고, 삭제됨 (생명주기를 엔티티에 의존함)

Java 기본 타입은 값 자체를 복사 → 공유 없음

int a = 10;
int b = a;
a = 20; // b는 여전히 10

주의: 래퍼 클래스나 String은 공유될 수 있으나, 불변성 때문에 값 변경이 불가함

 

3. 임베디드 타입 (Embedded Type)

 

  • 여러 개의 기본 값 타입을 묶어서 하나의 값 타입으로 정의
  • 복합 값 타입 또는 임베디드 타입이라고 부름
  • @Embeddable + @Embedded 조합으로 사용
  • @Embeddable: 값 타입을 정의하는 곳에 표시
  • @Embedded: 값 타입을 사용하는 곳에표시
  • 기본 생성자 필요

 

회원 엔티티는 이름, 근무 시작일, 근무 종료일, 주소 도시, 주소 번지, 우편번호를 가진다

좀더 객체 지향적으로 설계하면 회원 엔티티는 이름, 근무기간, 집주소를 가진다

[적용 예시]

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

    private String username;

    @Embedded
    private Period workPeriod;

    @Embedded
    private Address homeAddress;
}

@Embeddable
public class Period {
    private LocalDateTime startDate;
    private LocalDateTime endDate;

    public boolean isWork() {
        return startDate != null && endDate == null;
    }
}

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;
}

Period와 Address는 객체일 뿐, DB에서는 각각의 필드가 컬럼으로 매핑됨

장점

  • 재사용 가능
  • 응집력 높은 설계 (ex: Period.isWork() 같은 도메인 메서드 추가 가능)
  • 엔티티 클래스의 가독성 개선
  • 테이블 구조는 변하지 않음

 

 

임베디드 타입 + 연관관계

  • 임베디드 타입 내부에 다른 엔티티를 가질 수 있음 (ex: PhoneNumber 내에 Phone 엔티티 FK)
  • 단, 임베디드 타입 자체는 엔티티가 아님 → 식별자 없음

임베디드 값이 null일 때

  • 해당 값 타입의 모든 필드가 null로 들어감
  • ex: workAddress == null → WORK_CITY, WORK_STREET, WORK_ZIPCODE 모두 null

 

동일한 임베디드 객체를 2개이상 쓰고싶다면? 속성 재정의: @AttributeOverrides

같은 임베디드 타입을 두 번 이상 쓰려면 속성 이름이 충돌함 → 이때 @AttributeOverrides 사용

@Embedded
private Address homeAddress;

@Embedded
@AttributeOverrides({
    @AttributeOverride(name = "city", column = @Column(name = "WORK_CITY")),
    @AttributeOverride(name = "street", column = @Column(name = "WORK_STREET")),
    @AttributeOverride(name = "zipcode", column = @Column(name = "WORK_ZIPCODE"))
})
private Address workAddress;
반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 값 타입 컬렉션  (1) 2025.07.25
[JPA] 값 타입과 불변 객체  (2) 2025.07.23
[JPA] 영속성 전이 CASCADE  (2) 2025.07.17
[JPA] 즉시 로딩과 지연 로딩  (2) 2025.07.16
[JPA] 프록시와 연관관계 관리  (0) 2025.07.15
반응형

JPA에서 연관된 객체들을 저장하거나 삭제할 때, 매번 em.persist() 또는 em.remove()를 수동으로 호출하는 건 매우 번거롭다.

이때 등장하는 개념이 영속성 전이(Cascade)고아 객체 제거(orphanRemoval) 이다.

영속성 전이(Cascade)란?

연관된 엔티티도 자동으로 함께 영속성 상태로 관리하고 싶을 때 사용하는 기능입니다.

예를 들어, 게시글(Parent)과 댓글(Child)의 관계가 있다고 할 때, 게시글을 저장할 때 댓글도 자동으로 저장되길 원한다면 cascade가 필요합니다.

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

    private String name;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)  // 또는 ALL
    private List<Child> childList = new ArrayList<>();

    public void addChild(Child child) {
        childList.add(child);
        child.setParent(this);
    }
}

@Entity
public class 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);  // ✅ child1, child2도 함께 persist됨
// cascade옵션을 쓰지 않았다면 child1, child2를 두번 persist해줘야함

언제 사용하는가?

  • 연관된 엔티티의 생명주기를 부모가 전적으로 관리할 때
  • 대표적인 예: 게시글 - 댓글, 주문 - 주문상품, 설문 - 질문 등 "소유 관계"

여러 주인이 존재하는 연관관계에서는 절대 사용하면 안 됨
→ ex. 여러 게시글이 하나의 댓글을 공유하는 경우 (잘못된 모델링이지만)

🎯 Cascade 옵션 종류

옵션 설명
ALL 모든 Cascade 기능을 포함
PERSIST 연관 객체도 persist() 자동 호출
MERGE 병합 시 함께 병합
REMOVE 삭제 시 함께 삭제
REFRESH DB로부터 갱신할 때 함께 갱신
DETACH 영속성 컨텍스트에서 분리할 때 함께 분리

🧹 고아 객체 제거 (orphanRemoval)

부모 객체에서 연관관계를 끊어버리면, JPA가 해당 자식 엔티티를 자동으로 DELETE 하는 기능

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
Parent parent = em.find(Parent.class, 1L);
parent.getChildList().remove(0); // 해당 child 엔티티 DELETE 쿼리 발생

 

  • JPA가 자동으로 DELETE FROM child WHERE id = ? 실행
  • 연관관계가 끊어진 자식 엔티티를 고아(Orphan) 라고 판단하고 제거

언제 사용하는가?

  • Cascade와 마찬가지로 참조하는곳이 하나일 때 사용
  • @One ToOne, @OneToMany만 사용가능

 

Cascade vs OrphanRemoval 차이점

구분 Cascade OrphanRemoval
역할 부모 저장/삭제 시 자식도 함께 처리 연관관계 끊긴 자식을 자동 삭제
대상 persist, remove 등 엔티티 상태 변화 컬렉션에서 제거된 객체
필요 조건 연관관계 유지 참조가 제거됨 (null 또는 List.remove())
주의사항 생명주기 공유일 때만 사용 참조하는 곳이 하나일 때만 사용

 

CascadeType.ALL + orphanRemoval = true

이 둘을 함께 사용하면 아래와 같은 설계가 가능해집니다:

  • parent.addChild(child) → 자동으로 저장
  • parent.getChildList().remove(child) → 자동 삭제
  • em.remove(parent) → 연관된 child도 자동 삭제

이 조합은 부모 엔티티를 통해 자식의 생명주기를 완벽하게 제어하는 것을 의미합니다.

반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 값 타입과 불변 객체  (2) 2025.07.23
[JPA] 값 타입  (1) 2025.07.22
[JPA] 즉시 로딩과 지연 로딩  (2) 2025.07.16
[JPA] 프록시와 연관관계 관리  (0) 2025.07.15
[JPA] Mapped Superclass - 매핑 정보 상속  (0) 2025.07.03

+ Recent posts