ํ”„๋ก์‹œ


Member ๋ฅผ ์กฐํšŒํ•  ๋•Œ Team ๋„ ํ•จ๊ป˜ ์กฐํšŒํ•ด์•ผ ํ• ๊นŒ?

Member ๋ฅผ ์กฐํšŒํ•ด์„œ username ๋งŒ ์‚ฌ์šฉํ• ๊ฑด๋ฐ, Team ๊นŒ์ง€ ๊ฐ™์ด ์กฐํšŒํ•ด์˜ค๋ฉด ์†ํ•ด๊ฐ€ ์•„๋‹๊นŒ? ์ด๋ฅผ ์ตœ์ ํ™”ํ•˜๊ธฐ ์œ„ํ•ด JPA ์—์„  ํ”„๋ก์‹œ์™€ ์ง€์—ฐ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•œ๋‹ค.

ํ”„๋ก์‹œ ๊ธฐ์ดˆ

em.find() ๋Š” DB ๋ฅผ ํ†ตํ•ด์„œ ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ฐ˜๋ฉด, em.getReference() ๋Š” DB ์กฐํšŒ๋ฅผ ๋ฏธ๋ฃจ๋Š” ๊ฐ€์งœ(ํ”„๋ก์‹œ) ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•œ๋‹ค. ์‹ค์ œ๋กœ em.find(Member.class, member.getId()) ๋ฅผ ํ†ตํ•ด member ๋ฅผ ์กฐํšŒํ•˜๋ฉด SELECT ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ์•„๊ฐ€์ง€๋งŒ, em.getReference(Member.class, member.getId()) ๋ฅผ ํ†ตํ•ด member ๋ฅผ ์กฐํšŒํ•˜๋ฉด SELECT ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ์•„๊ฐ€์ง€ ์•Š๋Š”๋‹ค. ํ•œ ํŽธ, Member findMember = em.getReference(Member.class, member.getId()) ๋กœ ์กฐํšŒํ•œ ํ›„ findMember.getUsername() ์„ ํ˜ธ์ถœํ•  ๊ฒฝ์šฐ ๊ทธ์ œ์„œ์•ผ SELECT ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ์•„๊ฐ€๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ”„๋ก์‹œ ํŠน์ง•

ํ”„๋ก์‹œ๋Š” ์‹ค์ œ ํด๋ž˜์Šค๋ฅผ ์ƒ์† ๋ฐ›์•„์„œ ๋งŒ๋“ค์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ ํด๋ž˜์Šค์™€ ๊ฒ‰ ๋ชจ์Šต์ด ๊ฐ™๋‹ค. ์ด๋ก ์ƒ ์‚ฌ์šฉํ•˜๋Š” ์ž…์žฅ์—์„  ์ง„์งœ ๊ฐ์ฒด์ธ์ง€ ํ”„๋ก์‹œ ๊ฐ์ฒด์ธ์ง€ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๊ณ  ์‚ฌ์šฉํ•˜๋ฉด๋œ๋‹ค. ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์‹ค๊ฒŒ ๊ฐ์ฒด์˜ ์ฐธ์กฐ(target)๋ฅผ ๋ณด๊ด€ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์‹ค์ œ ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

ํ”„๋ก์‹œ ๊ฐ์ฒด์˜ ์ดˆ๊ธฐํ™”

Member member = em.getReference(Member.class, "id1");
member.getName();

ํ”„๋ก์‹œ์˜ ํŠน์ง•

ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์ฒ˜์Œ ์‚ฌ์šฉํ•  ๋•Œ ํ•œ ๋ฒˆ๋งŒ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค. ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ์ดˆ๊ธฐํ™”๋˜๋ฉด ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด์„œ ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ์— ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•ด์ง€๋Š” ๊ฒƒ์ด์ง€, ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™” ํ•  ๋•Œ, ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๋กœ ๋ฐ”๋€Œ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค. ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์›๋ณธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ƒ์†๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ํƒ€์ž… ์ฒดํฌ ์‹œ ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค. == ๋น„๊ต ๋Œ€์‹ ์— instance of ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์ฐพ๊ณ ์ž ํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ด๋ฏธ em.find() ๋ฅผ ํ†ตํ•ด ์กด์žฌํ•œ๋‹ค๋ฉด em.getReference() ๋ฅผ ํ˜ธ์ถœํ•ด๋„ ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋ฐ˜๋Œ€๋กœ, em.getReference() ๋กœ ๋จผ์ € ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•˜๊ณ  em.find() ๋กœ ๊ฐ™์€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ฐพ๋Š”๋‹ค๋ฉด, em.find() ๋กœ ๋ฐ˜ํ™˜๋œ ์—”ํ‹ฐํ‹ฐ๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด์ž„์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์—†๋Š” ์ค€์˜์† ์ƒํƒœ์ผ ๋•Œ, ํ”„๋ก์‹œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. Hibernate ์˜ ๊ฒฝ์šฐ LazyInitializationException ์˜ˆ์™ธ๋ฅผ ํ„ฐํŠธ๋ฆฐ๋‹ค.

ํ”„๋ก์‹œ ํ™•์ธ

emf.getPersistenceUnitUtil().isLoaded(refMember) ๋ฅผ ํ†ตํ•ด ํ”„๋ก์‹œ ์ธ์Šคํ„ด์Šค์˜ ์ดˆ๊ธฐํ™” ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. refMember.getClass() ๋ฅผ ํ†ตํ•ด ํ”„๋ก์‹œ ํด๋ž˜์Šค๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. Hibernate.initialize(refMember) ๋ฅผ ํ†ตํ•ด ํ”„๋ก์‹œ๋ฅผ ๊ฐ•์ œ๋กœ ์ดˆ๊ธฐํ™” ํ•  ์ˆ˜ ์žˆ๋‹ค. JPA ์—๋Š” ๊ฐ•์ œ ์ดˆ๊ธฐํ™”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ๋ƒฅ refMember.getName() ์œผ๋กœ ๊ฐ•์ œ ์ดˆ๊ธฐํ™” ํ•ด๋„ ๋œ๋‹ค.

์ฆ‰์‹œ ๋กœ๋”ฉ๊ณผ ์ง€์—ฐ ๋กœ๋”ฉ


์ง€์—ฐ ๋กœ๋”ฉ LAZY ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ”„๋ก์‹œ๋กœ ์กฐํšŒ

@Entity
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "name")
    private String name;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;
}
Member member = em.find(Member.class, 1L);
Team team = member.getTeam();
team.getName(); // ์‹ค์ œ team ์„ ์‚ฌ์šฉํ•˜๋Š” ์‹œ์ ์— ์ดˆ๊ธฐํ™”

Member ์™€ Team ์„ ์ž์ฃผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด? EAGER ์‚ฌ์šฉ

Member member = em.find(Member.class, 1L); // ์ด ์‹œ์ ์— team ๋„ ํ•จ๊ป˜ ์กฐํšŒ
Team team = member.getTeam();
team.getName();

ํ”„๋ก์‹œ์™€ ์ฆ‰์‹œ๋กœ๋”ฉ ์ฃผ์˜

๊ฐ€๊ธ‰์ ์ด๋ฉด ์ง€์—ฐ ๋กœ๋”ฉ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค. ํŠนํžˆ ์‹ค๋ฌด์—์„  ์ฆ‰์‹œ ๋กœ๋”ฉ์€ ๊ฑฐ์˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ฆ‰์‹œ ๋กœ๋”ฉ์„ ์ ์šฉํ•˜๋ฉด ์ „ํ˜€ ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ SQL ์ด ๋‚ ์•„๊ฐ„๋‹ค. ๋˜ํ•œ, ์ฆ‰์‹œ ๋กœ๋”ฉ์€ JPQL ์—์„œ N + 1 ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚จ๋‹ค. N + 1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด, ์‹ค๋ฌด์—์„œ๋Š” LAZY ๋กœ ๋‹ค ๋ฐ”๋ฅด๊ณ , A, B, C ๋ฅผ ํ•œ๋ฒˆ์— ์กฐํšŒํ•ด์•ผ ํ•  ๊ฒฝ์šฐ fetch join ์œผ๋กœ ํ•œ ๋ฐฉ์— ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์„ ์ฃผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

์ง€์—ฐ ๋กœ๋”ฉ ํ™œ์šฉ

Member ์™€ Team ์€ ์ž์ฃผ ํ•จ๊ป˜ ์‚ฌ์šฉ โ†’ ์ฆ‰์‹œ ๋กœ๋”ฉ Member ์™€ Order ๋Š” ๊ฐ€๋” ์‚ฌ์šฉ โ†’ ์ง€์—ฐ ๋กœ๋”ฉ Order ์™€ Product ๋Š” ์ž์ฃผ ํ•จ๊ป˜ ์‚ฌ์šฉ โ†’ ์ฆ‰์‹œ ๋กœ๋”ฉ

์ง€์—ฐ ๋กœ๋”ฉ ํ™œ์šฉ - ์‹ค๋ฌด

  • ๋ชจ๋“  ์—ฐ๊ด€๊ด€๊ณ„์— ์ง€์—ฐ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•ด๋ผ!
  • ์‹ค๋ฌด์—์„œ ์ฆ‰์‹œ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ๋ผ!
  • JPQL fetch join ์ด๋‚˜, ์—”ํ‹ฐํ‹ฐ ๊ทธ๋ž˜ํ”„ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด๋ผ!

์˜์†์„ฑ ์ „์ด(CASCADE)์™€ ๊ณ ์•„ ๊ฐ์ฒด


์˜์†์„ฑ ์ „์ด 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<>();
}
@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);
// cascade ์ ์šฉ ์‹œ ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ ์€ ๊ฒƒ๊ณผ ๊ฐ™์€ ํšจ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.
// em.persist(child1);
// em.persist(child2);

์ฃผ์˜

์˜์†์„ฑ ์ „์ด๋Š” ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งคํ•‘ํ•˜๋Š” ๊ฒƒ๊ณผ ์•„๋ฌด ๊ด€๋ จ์ด ์—†๋‹ค. ์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜์†ํ™”ํ•  ๋•Œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋„ ํ•จ๊ป˜ ์˜์†ํ™”ํ•˜๋Š” ํŽธ๋ฆฌํ•จ๋งŒ์„ ์ œ๊ณตํ•  ๋ฟ์ด๋‹ค.

CASCADE ์˜ ์ข…๋ฅ˜

  • ALL ๋ชจ๋‘ ์ ์šฉ - Life Cycle ์„ ๋งž์ถฐ์•ผํ•  ๋•Œ
  • PERSIST ์˜์† - ๊ฐ™์ด ์‚ญ์ œ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์œ„ํ—˜ํ•  ๋•Œ

๊ณ ์•„ ๊ฐ์ฒด

๊ณ ์•„ ๊ฐ์ฒด ์ œ๊ฑฐ - ๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ด€ ๊ด€๊ณ„๊ฐ€ ๋Š์–ด์ง„ ์ž์‹ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ž๋™์œผ๋กœ ์‚ญ์ œํ•˜๋Š” ๊ฒƒ์ด๋‹ค. orphanRemoval = true ์„ค์ •์„ ์ฃผ์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Parent parent = em.find(Parent.class, id);
parent.getChildren().remove(0);
// ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ๋Š์–ด์ง€๋ฉด DELETE ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ์•„๊ฐ

๊ณ ์•„ ๊ฐ์ฒด - ์ฃผ์˜

์ฐธ์กฐ๊ฐ€ ์ œ๊ฑฐ๋œ ์—”ํ‹ฐํ‹ฐ๋Š” ๋‹ค๋ฅธ ๊ณณ์—์„œ ์ฐธ์กฐํ•˜์ง€ ์•Š๋Š” ๊ณ ์•„ ๊ฐ์ฒด๋กœ ๋ณด๊ณ  ์‚ญ์ œํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. ๋”ฐ๋ผ์„œ, ์ฐธ์กฐํ•˜๋Š” ๊ณณ์ด ํ•˜๋‚˜์ผ ๋•Œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋ฉฐ ํŠน์ • ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๊ฐœ์ธ ์†Œ์œ ํ•  ๋•Œ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. @OneToOne @OneToMany ์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค. orphanRemoval = true ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ถ€๋ชจ๋ฅผ ์ œ๊ฑฐํ•  ๋•Œ ์ž์‹๋„ ํ•จ๊ป˜ ์ œ๊ฑฐ๋œ๋‹ค.

์˜์†์„ฑ ์ „์ด + ๊ณ ์•„ ๊ฐ์ฒด, ์ƒ๋ช…์ฃผ๊ธฐ

์Šค์Šค๋กœ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ๋Š” em.persist() ๋กœ ์˜์†ํ™” ํ˜น์€ em.remove() ๋กœ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค. CascadeType.ALL orphanRemovel=true ๋ชจ๋‘ ํ™œ์„ฑํ™”ํ•œ๋‹ค๋ฉด ๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ†ตํ•ด์„œ ์ž์‹์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

์‹ค์ „ ์˜ˆ์ œ 5 - ์—ฐ๊ด€๊ด€๊ณ„ ๊ด€๋ฆฌ


๊ธ€๋กœ๋ฒŒ ํŽ˜์น˜ ์ „๋žต ์„ค์ •

๋ชจ๋“  ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ ์„ค์ •ํ•œ๋‹ค.

์˜์†์„ฑ ์ „์ด ์„ค์ •

Order โ†’ Delivery ๋ฅผ ์˜์†์„ฑ ์ „์ด ALL ๋กœ ์„ค์ •ํ•œ๋‹ค. Order โ†’ OrderItem ์„ ์˜์†์„ฑ ์ „์ด ALL ๋กœ ์„ค์ •ํ•œ๋‹ค.