๐ง ์์์ฑ ์ ์ด(CASCADE)
์ด๋ ํ ์ํฐํฐ๋ฅผ ์์ํ(persist) ํ ๋, ํด๋น ์ํฐํฐ์ ์ฐ๊ด๋ ์ํฐํฐ๋ ํจ๊ป ์์ํํ๊ณ ์ถ์ ๊ฒฝ์ฐ ํด๋น ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
JPA๋ CASCADE ์ต์ ์ผ๋ก ์์์ฑ ์ ์ด๋ผ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋๋ฐ, ์ด๋ฆ ๊ทธ๋๋ก ์์์ฑ์ ์ ์ดํ๋ ๊ฒ์ ๋๋ค.
์ค๋ช ์ ๋๊ธฐ ์ํด ๊ฐ๋จํ ์์๋ฅผ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
@Setter
@Getter
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
@OneToMany(mappedBy = "parent")
private List<Child> childList = new ArrayList<>();
}
@Getter
@Setter
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Parent parent;
}
๐ง ์์์ฑ ์ ์ด : ์ ์ฅ (PERSIST)
๋ถ๋ชจ 1๋ช ์ ์์ 2๋ช ์ ์ ์ฅํ๋ ์ฝ๋๋ฅผ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
Parent parent = new Parent();
em.persist(parent);//๋ถ๋ชจ ์์ํ
Child child1 = new Child();
child1.setParent(parent);
parent.getChildList().add(child1);
em.persist(child1);//์์ 1 ์์ํ
Child child2 = new Child();
child2.setParent(parent);
parent.getChildList().add(child2);
em.persist(child2);//์์ 2 ์์ํ
JPA์์ ์ํฐํฐ๋ฅผ ์ ์ฅ(flush ํธ์ถ)ํ ๋, ์ฐ๊ด๋ ์ํฐํฐ๊ฐ ๋ชจ๋ ์์ ์ํ์ฌ์ผ ํฉ๋๋ค.
๋ฐ๋ผ์ ์์ ์์๋ ๋ถ๋ชจ๋ฅผ ์์ ์ํ๋ก ๋ง๋ค๊ณ , ์์ ์ํฐํฐ๋ ๊ฐ๊ฐ ์์ ์ํ๋ก ๋ง๋ค์ด์ฃผ์์ต๋๋ค.
ํด๋น ๊ณผ์ ์์ child1๊ณผ child2๋ฅผ ๋ชจ๋ ๋ฑ๋ก์์ผ ์ฃผ๋ ๊ฒ์ด ์กฐ๊ธ ๋ฒ๊ฑฐ๋กญ๋ค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
์ด๋ด ๋ ์์์ฑ ์ ์ด๋ฅผ ์ฌ์ฉํ์ฌ ๋ถ๋ชจ๋ง ์์ ์ํ๋ก ๋ง๋ค๋ฉด, ์ฐ๊ด๋ ์์๋ค๊น์ง ํ ๋ฒ์ ์์ ์ํ๋ก ๋ง๋ค ์ ์์ต๋๋ค.
@Setter
@Getter
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> childList = new ArrayList<>();
}
Parent parent = new Parent();
Child child1 = new Child();
Child child2 = new Child();
child1.setParent(parent); // ์๋ต๋๋ฉด ์ ์ฅ์ ๋์ง๋ง child์ FK์ null์ด ์ ์ฅ๋๋ค.
child2.setParent(parent);
parent.getChildList().add(child1); // ์๋ต๋๋ฉด ์ ์ฅ๋์ง ์๋๋ค.
parent.getChildList().add(child2);
em.persist(parent);
์ฃผ์ํ ์ ์ ์์์ฑ ์ ์ด๋ ์ฐ๊ด๊ด๊ณ ๋งคํ๊ณผ๋ ์๋ฌด๋ฐ ์ฐ๊ด์ด ์๋ค๋ ๊ฒ์ ๋๋ค.
๋จ์ง ์ํฐํฐ๋ฅผ ์์ํํ ๋ ์ฐ๊ด๋ ์ํฐํฐ๋ ํจ๊ป ์์ํํ๋ ํธ๋ฆฌํจ์ ์ ๊ณตํ๋ ๊ฒ์ ๋๋ค.
๋ฐ๋ผ์ ์์ ์์ ์ฒ๋ผ ์๋ฐฉํฅ ์๊ด๊ด๊ณ๋ฅผ ๊ผญ ์ค์ ํด ์ค ํ ์์ ์ํ๋ก ๋ง๋ค์ด์ฃผ์ด์ผ ํฉ๋๋ค.
๐ง ์์์ฑ ์ ์ด : ์ญ์ (REMOVE)
๋ฐฉ๊ธ ์ ์ฅํ ๋ถ๋ชจ์ ์์ ์ํฐํฐ๋ฅผ ๋ชจ๋ ์ ๊ฑฐํ๊ธฐ ์ํด์๋ ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
Parent fParent = em.find(Parent.class, 1L);
Child fChild1 = em.find(Child.class, 2L);
Child fChild2 = em.find(Child.class, 3L);
em.remove(fChild1);
em.remove(fChild2);
em.remove(fParent);
์์์ฑ ์ ์ด๋ ์ํฐํฐ๋ฅผ ์ญ์ ํ ๋๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํฉ๋๋ค.
@OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
์ด์ ๋ค์ ์ฝ๋๋ฅผ ์คํํ๋ฉด ์์ ์ํฐํฐ๊น์ง ๋ชจ๋ ์ญ์ ๋ฉ๋๋ค.
Parent fParent = em.find(Parent.class, 1L);
em.remove(fParent);
๋ง์ผ ์์์ฑ ์ ์ด๋ฅผ ์ค์ ํด์ฃผ์ง ์์๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ธ๋ํค ๋ฌด๊ฒฐ์ฑ ์ ์ฝ์กฐ๊ฑด ์๋ฐ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
๐ง ์์์ฑ ์ ์ด์ ์ข ๋ฅ
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
}
์ฌ๋ฌ๊ฐ์ ์์ฑ์ ๊ฐ์ด ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
์ฐธ๊ณ ๋ก PERSIST์ REMOVE๋ em.persist(), em.remove() ์์ ์ ์ ์ด๊ฐ ๋ฐ์ํ๋ ๊ฒ์ด ์๋ ํ๋ฌ์๋ฅผ ํธ์ถํ ๋ ์ ์ด๊ฐ ๋ฐ์ํฉ๋๋ค.
์ด๋ฅผ ํ์ธํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด ํด๋์ค๋ฅผ ๊ตฌํํด ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
@Setter
@Getter
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> childList = new ArrayList<>();
}
@Getter
@Setter
@Entity
public class Child {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@ManyToOne(fetch = FetchType.LAZY)
private Parent parent;
}
@SpringBootTest
@Transactional
class ParentRepositoryTest {
@Autowired
private EntityManager em;
@Autowired
private ParentRepository parentRepository;
@Autowired
private ChildRepository childRepository;
@Test
void test() {
Parent parent = new Parent();
Child child1 = new Child();
Child child2 = new Child();
child1.setParent(parent); // ์๋ต๋๋ฉด child์ FK์ null ์ ์ฅ
child2.setParent(parent);
parent.getChildList().add(child1); // ์๋ต๋๋ฉด ์ ์ฅ X
parent.getChildList().add(child2);
System.out.println("PERSIST ํธ์ถ ์ง์ ");
em.persist(parent);
System.out.println("FLUSH ํธ์ถ ์ง์ ");
em.flush();
System.out.println("FLUSH ํธ์ถ ์๋ฃ");
}
}
๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๊ทธ๋ฌ๋ @GeneratedValue(strategy = GenerationType.IDENTITY)๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ,
save() ํน์ persist() ํ๋ ์์ ์ flush()๊ฐ ์๋ ํธ์ถ๋๊ธฐ ๋๋ฌธ์ save() ํน์ persist() ํ๋ ์๊ฐ ์์์ฑ ์ ์ด๊ฐ ๋ฐ์ํฉ๋๋ค.
๐ง ์์์ฑ ์ ์ด ์ฃผ์์
์์์ฑ ์ ์ด๋ฅผ ํจ๋ถ๋ก ์ฌ์ฉํด์๋ ์๋ฉ๋๋ค.
๊ด๋ฆฌํ๋ ๋ถ๋ชจ๊ฐ ๋จ ํ๋์ผ ๊ฒฝ์ฐ(๋จ์ผ ์ํฐํฐ์ ์ข ์์ ์ผ ๊ฒฝ์ฐ)์๋ง ์ฌ์ฉํ๋๊ฒ์ด ๋ฐ๋์งํฉ๋๋ค.
๐ง ๊ณ ์ ๊ฐ์ฒด
JPA๋ ๋ถ๋ชจ ์ํฐํฐ์ ์ฐ๊ด๊ด๊ณ๊ฐ ๋์ด์ง ์์ ์ํฐํฐ๋ฅผ ์๋์ผ๋ก ์ญ์ ํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
์ด ๊ธฐ๋ฅ์ ๊ณ ์(ORPHAN) ๊ฐ์ฒด ์ ๊ฑฐ๋ผ๊ณ ํฉ๋๋ค.
์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ๋ถ๋ชจ ์ํฐํฐ์ ์ปฌ๋ ์ ์์ ์์ ์ํฐํฐ์ ์ฐธ์กฐ๋ง ์ ๊ฑฐํ๋ฉด ์์ ์ํฐํฐ๊ฐ ์๋์ผ๋ก ์ญ์ ๋ฉ๋๋ค.
orphanRemoval = true
์์ ์์์์ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ ํ, ์์ ์ํฐํฐ๋ฅผ ํ๋ ์ ๊ฑฐํด๋ณด๊ฒ ์ต๋๋ค.
@Setter
@Getter
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
}
Parent parent = new Parent();
em.persist(parent);
Child child1 = new Child();
child1.setParent(parent);
parent.getChildList().add(child1);
Child child2 = new Child();
child2.setParent(parent);
parent.getChildList().add(child2);
em.flush();
em.clear();
Parent fParent = em.find(Parent.class, 1L);
fParent.getChildList().remove(0);
์์ ์ฝ๋๋ฅผ ์คํํ๋ฉด delete ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
๐ง ๊ณ ์ ๊ฐ์ฒด ์ ๊ฑฐ ์ฌ์ฉ์ ์ฃผ์์
๊ณ ์ ๊ฐ์ฒด ์ ๊ฑฐ๋ ์ฐธ์กฐ๊ฐ ์ ๊ฑฐ๋ ์ํฐํฐ๋ ๋ค๋ฅธ ๊ณณ์์ ์ฐธ์กฐํ์ง ์๋ ๊ณ ์ ๊ฐ์ฒด๋ก ๋ณด๊ณ ์ญ์ ํ๋ ๊ธฐ๋ฅ์ ๋๋ค.
๋ฐ๋ผ์ ์ด ๊ธฐ๋ฅ์ ์ฐธ์กฐํ๋ ๊ณณ์ด ํ๋์ผ ๋๋ง ์ฌ์ฉํด์ผ ํฉ๋๋ค.
์ฆ ํน์ ์ํฐํฐ๊ฐ ๊ฐ์ธ์์ ํ๋ ์ํฐํฐ์๋ง ์ด ๊ธฐ๋ฅ์ ์ ์ฉํด์ผ ํ๋ค๋ ๊ฒ์ ๋๋ค.
๋ง์ฝ ์ญ์ ํ ์ํฐํฐ๋ฅผ ๋ค๋ฅธ ๊ณณ์์๋ ์ฐธ์กฐํ๋ค๋ฉด ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
๊ทธ๋ฌ๊ธฐ์ ๊ธฐ๋ณธ์ ์ผ๋ก orphanRemoval์ @OneToOne๊ณผ @OneToMany ์๋ง ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ์ ์คํ ์ฌ์ฉํ์ฌ์ผ ํฉ๋๋ค.
๐ง ๊ณ ์ ๊ฐ์ฒด ์ ๊ฑฐ์ CascadeType.REMOVE
๋ถ๋ชจ๊ฐ ์์์ ๋ฒ๋ ค๋ ๊ณ ์๊ฐ ๋์ง๋ง, ๋ถ๋ชจ๊ฐ ์ฃฝ์ด๋ ๊ณ ์๊ฐ ๋ฉ๋๋ค.
์ฆ ๋ถ๋ชจ๋ฅผ ์ ๊ฑฐํ๋ฉด ์์์ ๊ณ ์๊ฐ ๋๋ ๊ฒ์ ๋๋ค.
๋ฐ๋ผ์ ๊ณ ์ ๊ฐ์ฒด ์ ๊ฑฐ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ๋ถ๋ชจ๋ฅผ ์ ๊ฑฐํ ๋ ์์๋ ํจ๊ป ์ ๊ฑฐ๋๋๋ฐ, ์ด๊ฒ์ ๋ง์น CascadeType.REMOVE ์ฒ๋ผ ๋์ํฉ๋๋ค.
๐ง CascadeType.ALL + orphanRemoval = true (์์์ฑ ์ ์ด + ๊ณ ์ ๊ฐ์ฒด ์ ๊ฑฐ)
์์์ฑ ์ ์ด์ ๊ณ ์ ๊ฐ์ฒด ์ ๊ฑฐ๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ์ํฐํฐ๋ em.persist()๋ฅผ ํตํด ์์ํ๋๊ณ , em.remove()๋ฅผ ํตํด ์ ๊ฑฐ๋๋ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ฐ์ต๋๋ค.
์ด๊ฒ์ ์ํฐํฐ ์ค์ค๋ก๊ฐ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ด๋ฆฌํ๋ค๋ ๋ป์ ๋๋ค.
๊ทธ๋ฐ๋ฐ ์ด ๋ ์ต์ ์ ๋ชจ๋ ํ์ฑํํ๊ฒ ๋๋ฉด, ๋ถ๋ชจ ์ํฐํฐ๋ฅผ ํตํด์ ์์์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ด๋ฆฌํ ์ ์๊ฒ๋ฉ๋๋ค.
์์์ ์ ์ฅํ๋ ค๋ฉด ๋ถ๋ชจ์ ๋ฑ๋ก๋ง ํ๋ฉด ๋๊ณ , ์์์ ์ญ์ ํ๋ ค๋ฉด ๋ถ๋ชจ์์ ์ ๊ฑฐ๋ง ํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋๋ฉ์ธ ์ฃผ๋ ์ค๊ณ (DDD)์ Aggregate Root ๊ฐ๋ ์ ๊ตฌํํ ๋ ์ ์ฉํฉ๋๋ค.
๐ง Reference
[์๋ฐ ORM ํ์ค JPA ํ๋ก๊ทธ๋๋ฐ - ๊น์ํ]
'๐๏ธ Spring > JPA' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[JPA] JPA์ ๋ค์ํ ์ฟผ๋ฆฌ ๋ฐฉ๋ฒ (JPQL, JdbcTemplate) (0) | 2021.12.16 |
---|---|
[JPA] @AttributeOverride - ๋งคํ ์ ๋ณด ์ฌ์ ์ (1) | 2021.12.16 |
[JPA] ์ฆ์ ๋ก๋ฉ๊ณผ ์ง์ฐ ๋ก๋ฉ ( + PersistentBag) (0) | 2021.12.15 |
[JPA] JPA์์์ ํ๋ก์ (0) | 2021.12.15 |
[JPA] @AssocicationOverride - ์ฐ๊ด๊ด๊ณ(์ธ๋ ํค ์ปฌ๋ผ๋ช ) ์ฌ์ ์ (0) | 2021.12.15 |