[JPA] Casecade ์˜ต์…˜

๋ฉค๋ฒ„(Member)์™€ ์‚ฌ๋ฌผํ•จ(Locker) 1:1 ๋งคํ•‘ ์ƒํ™ฉ์„ ์‚ดํŽด๋ณด๊ณ , cascade ์˜ต์…˜์ด ์–ด๋–ค ์ƒํ™ฉ์—์„œ ์“ฐ์ด๋Š”์ง€ ์•Œ์•„๋ณด์ž.

์˜ˆ์ œ์ฝ”๋“œ

Member, Locker ์—”ํ‹ฐํ‹ฐ

@Data
@Table(name = "MEMBER")
@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "NAME")
    private String name;

    @Column(name = "AGE")
    private Integer age;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

}
@Data
@Entity
@Table
public class Locker {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "NAME")
    private String name;
}

Member -> Locker ๋‹จ๋ฐฉํ–ฅ 1:1 ๋งคํ•‘์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด์ž.

์ค‘์š”ํ•œ ๊ฒƒ์€, @OneToOne ๊ณผ @JoinColumn์„ ์‚ฌ์šฉํ•ด์„œ ๊ด€๊ณ„ ๋งคํ•‘์„ ํ–ˆ๊ณ , ์ฐธ์กฐํ•˜๋Š”(๋ฐฉํ–ฅ์„ฑ์„ ๊ฐ€์ง€๋Š”) Locker๋ฅผ ํ•ด๋‹น ๋ฉค๋ฒ„ ํ•„๋“œ์˜ Type์œผ๋กœ ์ง€์ •ํ–ˆ๋‹ค. ๋ช…์‹œ์ ์œผ๋กœ @JoinColumn์˜ name ํ”„๋กœํผํ‹ฐ๋ฅผ ์ฐธ์กฐํ•˜๋Š” LOCKER_ID๋กœ ์„ค์ •ํ–ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค

    @Test
    public void ๋‹จ๋ฐฉํ–ฅํ…Œ์ŠคํŠธ_Memmber_Locker_SaveTest() {

        //given
        Member member = new Member();
        member.setName("andrew");
        member.setAge(32);


        Locker locker = new Locker();
        locker.setName("1๋ฒˆ ์‚ฌ๋ฌผํ•จ");

        member.setLocker(locker);

        memberRepository.save(member);
        lockerRepository.save(locker);

        //when
        Member existMember = memberRepository.findById(member.getId()).get();

        //then
        assertEquals(existMember.getLocker().getName(), "1๋ฒˆ ์‚ฌ๋ฌผํ•จ");

    }

Member, Locker ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  ์ €์žฅ ํ•œํ›„์—, member.getLocker().getName() ์œผ๋กœ ๊ฐ์ฒด๋ฅผ ํƒ์ƒ‰ํ•˜๋Š” ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค.
์ด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋Š” ์‹คํŒจ ํ•œ๋‹ค.

`save the transient instance before flushing` : com.example.demo.spring.jpa.member.Member.locker -> com.example.demo.spring.jpa.locker.Locker;

๋‹ค์Œ๊ณผ ๊ฐ™์€ exception์ด ๋ฐœ์ƒํ•œ๋‹ค. flusing ํ•˜๊ธฐ ์ „์— transient ์ธ์Šคํ„ด์Šค๋ฅผ saveํ•˜๋ผ!

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์‚ดํŽด๋ณด๋ฉด, locker ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•˜๊ธฐ ์ „์—, ๊ฐ์ฒด ์ƒ์„ฑ๋งŒ ํ•˜๊ณ , member์˜ setLocker์˜ ์ธ์ž๋กœ ๋„˜๊ธฐ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์ฆ‰, locker๊ฐ€ save()๋ฅผ ํ†ตํ•ด์„œ db์— ์ €์žฅ ๋˜๊ธฐ๋„ ์ „์—, memberRepository.save()๋ฅผ ํ˜ธ์ถœ ํ•˜๋‹ˆ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์ด๋‹ค. (์ž์„ธํžˆ ์™„๋ฒฝํžˆ ์ดํ•ด ๋ชปํ•จ)

DB์— ํ”Œ๋Ÿฌ์‰ฌ ๋˜๋Š” ์‹œ์ 

์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, 2๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

ํ•ด๊ฒฐ๋ฐฉ๋ฒ• 1. ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ ์œผ๋กœ

๋…ผ๋ฆฌ์  ํ๋ฆ„์ด ๋งž์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, locker๋ฅผ ๋จผ์ € ์ €์žฅํ•˜๊ณ , member๋ฅผ ์ €์žฅํ•˜๋„๋ก ๋กœ์ง์„ ์ˆ˜์ •ํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ์„ฑ๊ณตํ•œ๋‹ค.

    ...
    lockerRepository.save(locker);
    memberRepository.save(member);

ํ•˜์ง€๋งŒ ๋งค๋ฒˆ ๋กœ์ง์„ ์ž‘์„ฑํ•  ๋•Œ๋งˆ๋‹ค ๊ณ ๋ คํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ๋ฆฌ ์‰ฌ์šด ๋ฐฉ๋ฒ•์€ ์•„๋‹ˆ๋‹ค.
๋‚˜์ค‘์— ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„์—์„œ๋Š” ํŽธ์˜ ๋ฉ”์†Œ๋“œ ๋ผ๋Š” ๊ฒƒ์„ ํ†ตํ•ด์„œ ์‹ค์ˆ˜ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์„ ์ค„์—ฌ ์ฃผ๊ธฐ๋Š” ํ•œ๋‹ค.

ํ•ด๊ฒฐ๋ฐฉ๋ฒ• 2. cascade ์˜ต์…˜์„ ์ฃผ๋Š” ๊ฒƒ

@OneToOne์— cascade = CascadeType.ALL ์˜ต์…˜์„ ์คŒ์œผ๋กœ์จ ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ๋‹ค.
์ด casecade๋Š” ์ „ํŒŒ๋˜๋Š” ์†์„ฑ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ์ฆ‰, member๊ฐ€ save()๋ฅผ ํ•  ๋•Œ, ์—ฐ๊ด€๊ด€๊ณ„์— ์žˆ๋Š” locker๋„ ์ €์žฅํ•จ์œผ๋กœ์จ ์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฒ˜์Œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋Š” ๊ทธ๋Œ€๋กœ ๋‘๊ณ , Member ์—”ํ‹ฐํ‹ฐ์—์„œ ํ•ด๋‹น ์˜ต์…˜๋งŒ์„ ์ฃผ๋ฉด ํ…Œ์ŠคํŠธ๋Š” ์„ฑ๊ณตํ•œ๋‹ค.

    ...
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

๐Ÿค” ์—ฌ๊ธฐ์„œ ๊ฐ‘์ž๊ธฐ ๊ถ๊ธˆํ•ด์ง„ ์ 

๊ทธ๋Ÿผ casecade์˜ต์…˜์œผ๋กœ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ๋„ ์ €์žฅ๋œ๋‹ค๊ณ  ํ–ˆ๋Š”๋ฐ, ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์—์„œ locker์˜ ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•˜๋Š” lockerRepository.save(locker); ์„ ์ฃผ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๋Œ๋ฆฌ๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”?

Hibernate:
    insert
    into
        locker
        (name, locker_id)
    values
        (?, ?)
Hibernate:
    insert
    into
        member
        (age, locker_id, name, member_id)
    values
        (?, ?, ?, ?)

๋†€๋ž๊ฒŒ๋„(?), ๋‹น์—ฐํ•˜๊ฒŒ๋„ ๊ฒฐ๊ณผ๋Š” ์„ฑ๊ณตํ•œ๋‹ค. locker insert ์ฟผ๋ฆฌ๋ฌธ๊ณผ member insert ์ฟผ๋ฆฌ๋ฌธ ๋™์‹œ์— ๋‘๊ฐœ๊ฐ€ ๋‚ ๋ผ๊ฐ

์ •๋ฆฌ: casecade ์˜ต์…˜์„ ํ™œ์šฉํ•˜๋ฉด, ๋“ฑ๋ก, ์‚ญ์ œ ์‹œ์— ๋ถˆํ•„์š”ํ•œ save() ๋ฉ”์†Œ๋“œ๋ฅผ ํ•˜์ง€ ์•Š์•„๋„ ์ฐธ์กฐํ•˜๋Š” ์ชฝ์˜ ํ•˜๋‚˜์˜ ์—”ํ‹ฐํ‹ฐ๋งŒ ์ €์žฅ/์‚ญ์ œ ํ•ด๋„ ๊ฐ™์ด ๋ฐ˜์˜๋œ๋‹ค. (ํŠนํžˆ, ์—ฐ๊ด€๊ด€๊ณ„์˜ DB ๋ฐ์ดํ„ฐ๋ฅผ ์ง€์šธ ๋•Œ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ •ํ•ฉ์„ฑ์„ ๊ณ ๋ คํ•˜๋ฉด ์ข‹์Œ)

์ด๋ฒˆ์—” ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„๋กœ ์„ค์ •!!

์œ„์—๋Š” ๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„ ์˜€์œผ๋‹ˆ, ์ด๋ฒˆ์—๋Š” ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ์„ค์ •์„ ๋งˆ๋ฌด๋ฆฌ ํ•ด๋ณด์ž ๐Ÿ’ช

@Data
@Table(name = "MEMBER")
@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "NAME")
    private String name;

    @Column(name = "AGE")
    private Integer age;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

}

Member ์—”ํ‹ฐํ‹ฐ๋Š” ๋ณ€ํ•จ์ด ์—†์Œ

@Data
@Entity
@Table
public class Locker {

    @Id
    @GeneratedValue
    @Column(name = "LOCKER_ID")
    private Long id;

    @Column(name = "NAME")
    private String name;

    // ์ด ๋ถ€๋ถ„์ด ์ถ”๊ฐ€๋จ
    @OneToOne(mappedBy = "locker")
    private Member member;
}

Locker ์—”ํ‹ฐํ‹ฐ์—์„œ๋Š” ์—ญ์‹œ 1:1 ์—ฐ๊ด€๊ด€๊ณ„ ์–ด๋…ธํ…Œ์ด์…˜์ธ @OneToOne์„ ์„ ์–ธํ•ด์คŒ.

์ค‘์š”ํ•œ ๊ฒƒ์€ ์™ธ๋ž˜ํ‚ค์˜ ๊ด€๋ฆฌ๊ฐ€ ๋‚จ์•„ ์žˆ์–ด์„œ
mappedBy ์†์„ฑ์„ ํ†ตํ•ด์„œ ๋งˆ๋ฌด๋ฆฌ ์ง€์—ˆ๋‹ค.

์ด๋ฒˆ์— ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋Š” ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„๊ฐ€ ์ž˜ ๋งคํ•‘๋˜์—ˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•œ๋‹ค.

    @Test
    public void ์–‘๋ฐฉํ–ฅ๊ด€๊ณ„_ํ…Œ์ŠคํŠธ() {

        //given
        Member member = new Member();
        member.setName("andrew");
        member.setAge(32);

        Locker locker = new Locker();
        locker.setName("1๋ฒˆ ์‚ฌ๋ฌผํ•จ");
        member.setLocker(locker);


        memberRepository.save(member);

        //when
        Member exsitMember = memberRepository.findById(member.getId()).get();
        Locker existLocker = lockerRepository.findById(locker.getId()).get();

        //then
        assertEquals(exsitMember.getLocker().getName(), "1๋ฒˆ ์‚ฌ๋ฌผํ•จ");
        assertEquals(existLocker.getMember().getName(), "andrew");

    }

ํ…Œ์ŠคํŠธ ์„ฑ๊ณต!

์—ฐ๊ด€ ํฌ์ŠคํŠธ