π JPA μμμ± μ μ΄(CASCADE)
μλ νμΈμ! μ΄λ²μ μ 리ν λ΄μ©μ JPAμμ μμμ± μ μ΄(CASCADE)μ κ΄λ ¨λ λ΄μ©μ λλ€.
μ΅κ·Ό κ°λ°μ μ§ννλ©° λ μ°κ΄λ μν°ν°μμ save()λ₯Ό νλ κ³Όμ μμ μ€λ₯κ° λ°μνκ³ ,
μ΄λ₯Ό ν΄κ²°νλ κ³Όμ μμ μμμ± μ μ΄(CASCADE)λΌλ κ°λ μ λν΄ μκ² λμκ³ , μ΄λ₯Ό μ 리νκ³ μ ν©λλ€. π
(β» μλͺ»λ λ΄μ©μ΄ ν¬ν¨λμ΄ μμμλ μμ΅λλ€. μλͺ»λ λΆλΆμ λν΄ μ§μ ν΄μ£Όμλ©΄ κ°μ¬νκ² μ΅λλ€ π)
π― μμμ±μ μ΄(CASCADE)λ ?
λΆλͺ¨ μν°ν°κ° μμνλ λ μμ μν°ν°λ κ°μ΄ μμνλκ³ , λΆλͺ¨ μν°ν°κ° μμ λ λ μμ μν°ν°λ μμ λλ λ± νΉμ μν°ν°λ₯Ό μμ μνλ‘ λ§λ€ λ μ°κ΄λ μν°ν°λ ν¨κ» μμ μνλ‘ μ μ΄λλ κ²μ μλ―Έν©λλ€.
μ¦, νΉμ μν°ν°μ λν΄ νΉμ ν μμ μ μννλ©΄ κ΄λ ¨λ μν°ν°μλ λμΌν μμ μ μννλ€λ μλ―Έμ λλ€.
(ν΄μ λ§λμ? γ
γ
;)
JPAμμλ μ΄λ¬ν μ°κ΄λ μν°ν°κ°μ μμ‘΄μ±(dependency)μ μ€μ νκΈ° μν΄ Enum νμ μ javax.persistence.CascadeType μ μ 곡νκ³ μμ΅λλ€.
/*
* Copyright (c) 2008, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Linda DeMichiel - 2.1
// Linda DeMichiel - 2.0
package javax.persistence;
/**
* Defines the set of cascadable operations that are propagated
* to the associated entity.
* The value <code>cascade=ALL</code> is equivalent to
* <code>cascade={PERSIST, MERGE, REMOVE, REFRESH, DETACH}</code>.
*
* @since 1.0
*/
public enum CascadeType {
/** Cascade all operations */
ALL,
/** Cascade persist operation */
PERSIST,
/** Cascade merge operation */
MERGE,
/** Cascade remove operation */
REMOVE,
/** Cascade refresh operation */
REFRESH,
/**
* Cascade detach operation
*
* @since 2.0
*
*/
DETACH
}
CascadeTypeλ μ½λμ κ°μ΄ PERSIST, MERGE, DETACH, PREFRESH, REMOVE, ALL λ‘ κ΅¬μ±λμ΄ μμ΅λλ€.
κ°κ°μ operationsμ λν μ€λͺ μ λ€μκ³Ό κ°μ΅λλ€.
- CascadeType.ALL: λͺ¨λ Cascadeλ₯Ό μ μ©
- CascadeType.PERSIST: μν°ν°λ₯Ό μμνν λ, μ°κ΄λ μν°ν°λ ν¨κ» μ μ§
- CascadeType.MERGE: μν°ν° μνλ₯Ό λ³ν©(Merge)ν λ, μ°κ΄λ μν°ν°λ λͺ¨λ λ³ν©
- CascadeType.REMOVE: μν°ν°λ₯Ό μ κ±°ν λ, μ°κ΄λ μν°ν°λ λͺ¨λ μ κ±°
- CascadeType.DETACH: λΆλͺ¨ μν°ν°λ₯Ό detach() μννλ©΄, μ°κ΄ μν°ν°λ detach()μνκ° λμ΄ λ³κ²½ μ¬ν λ°μ X
- CascadeType.REFRESH: μμ μν°ν°λ₯Ό μλ‘κ³ μΉ¨(Refresh)ν λ, μ°κ΄λ μν°ν°λ λͺ¨λ μλ‘κ³ μΉ¨
π€ λ¬Έμ μν©
μλ‘ μμ μΈκΈνλ―μ΄, μ°κ΄λμ΄ μλ λ μν°ν°μμ save()λ₯Ό μ§ννλ κ³Όμ μμ μ€λ₯κ° λ°μνμ΅λλ€.
νμ¬ μ°κ΄λ μν°ν°μ μ½λλ λ€μκ³Ό κ°μ΅λλ€.
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
@Getter
@ToString(exclude = "member")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Board extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long bno;
@Column
private String title;
@Column
private String content;
@ManyToOne(fetch = FetchType.LAZY)
private Member member;
public void changeTitle(String title) {
this.title = title;
}
public void changeContent(String content) {
this.content = content;
}
}
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member extends BaseEntity {
@Id
private String email;
@Column
private String password;
@Column
private String name;
}
Board(κ²μκΈ) κ³Ό Member(νμ) μ N:1μ κ΄κ³λ₯Ό κ°μ§κ³ μμ΅λλ€.
λν Boardμμ @ManyToOne μ΄λ Έν μ΄μ μ ν΅ν΄ Member μν°ν°λ₯Ό κ°μ§κ³ μμ΅λλ€.
μ λ μν°ν°λ₯Ό ν΅ν΄ save()λ₯Ό νλ μ½λλ λ€μκ³Ό κ°μ΅λλ€.
@Test
@DisplayName("μ°κ΄κ΄κ³λ₯Ό κ°μ§ λ μν°ν°μ CASCADE μμ±μ μ€μ νμ§ μμΌλ©΄ μμΈκ° λ°μνλ€.")
void insertBoard() {
IntStream.rangeClosed(301, 350)
.forEach(i -> {
Member member = Member.builder().email("user" + i + "@aaa.com").build();
Board board = Board.builder()
.title("Title..." + i)
.content("Content..." + i)
.member(member)
.build();
boardRepository.save(board);
});
}
μ ν μ€νΈμ½λλ₯Ό μ€ννλ©΄ λ€μκ³Ό κ°μ μλ¬κ° λ°μν©λλ€.
Caused by: java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing ...
Caused by: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing ...
TransientPropertyValueException ν΄λμ€μ λν μ€λͺ μ λλ€.
λλ΅μ μΈ μλ―Έλ μΌμμ μΌλ‘ μ μ₯λμ§ μμ μν°ν°μμ κ΄κ³λ‘ μΈν΄ persist(μμν) ν μ μμλ μμΈλ₯Ό λμ§λ€κ³ μ€λͺ νκ³ μμ΅λλ€.
π€ ν΄κ²° λ°©λ²
λ°λΌμ μν°ν°μ μ°κ΄κ΄κ³μ CASCADE μ€μ μ ν΄μ£Όμ΄μΌ ν©λλ€.(CascadeType)
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
@Getter
@ToString(exclude = "member")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Board extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long bno;
@Column
private String title;
@Column
private String content;
// μμμ± μ μ΄(Cascade)
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private Member member;
public void changeTitle(String title) {
this.title = title;
}
public void changeContent(String content) {
this.content = content;
}
}
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member extends BaseEntity {
@Id
private String email;
@Column
private String password;
@Column
private String name;
}
νμ¬ μ μ½λμμλ Board μν°ν°λ§ Member μν°ν°λ₯Ό κ°μ§κ³ μμ΅λλ€.
λ°λΌμ Boardμ @ManyToOne μ΄λ Έν μ΄μ μ μμμ± μ μ΄ μ€μ (CascadeType.PERSIST)μ μΆκ°ν©λλ€.
κ·Έ ν ν μ€νΈμ½λλ₯Ό λ€μ μ€νν΄λ³΄λ©΄ μ μμ μΌλ‘ μλμ΄ λ©λλ€.
@Test
@DisplayName("μ°κ΄κ΄κ³λ₯Ό κ°μ§ λ μν°ν°μ CASCADE μμ±μ μ€μ νλ©΄ μ μμ μΌλ‘ save()λ©μλκ° μνλλ€.")
void insertBoard() {
IntStream.rangeClosed(351, 400)
.forEach(i -> {
Member member = Member.builder().email("user" + i + "@aaa.com").build();
Board board = Board.builder()
.title("Title..." + i)
.content("Content..." + i)
.member(member)
.build();
boardRepository.save(board);
});
}
Preferences
- https://www.javatpoint.com/jpa-cascading-operations
- https://www.baeldung.com/jpa-cascade-types
- https://docs.jboss.org/hibernate/orm/5.1/javadocs/org/hibernate/TransientPropertyValueException.html
λκΈ