BackEnd๐ŸŒฑ/Spring

๋‹จ๋ฐฉํ–ฅ @OneToMany์˜ ๋ฌธ์ œ์ 

dkswnkk 2024. 7. 26. 01:19

๊ฐœ์š”

JPA๋ฅผ ํ•™์Šตํ•˜๋‹ค ๋ณด๋ฉด @OneToMany๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋‹จ๋ฐฉํ–ฅ๋ณด๋‹ค๋Š” ์–‘๋ฐฉํ–ฅ ๋งคํ•‘์„ ์‚ฌ์šฉํ•˜๋ผ๋Š” ๋ง์„ ์ž์ฃผ ๋“ฃ๊ฒŒ ๋œ๋‹ค.

์™œ ๊ทธ๋Ÿฐ์ง€ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ๋‹จ๋ฐฉํ–ฅ @OneToMany๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์ œ์ ์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๊ณ , ์–‘๋ฐฉํ–ฅ์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ์–ด๋–ค ์ ์ด ๊ฐœ์„ ๋˜๋Š”์ง€ ์•Œ์•„๋ณด์ž

  1. ๋งคํ•‘ ํ…Œ์ด๋ธ”์„ ์‚ฌ์šฉํ•œ ๋‹จ๋ฐฉํ–ฅ @OneToMany ๋™์ž‘ ๊ณผ์ •
  2. @JoinColumn ์‚ฌ์šฉํ•œ ๋‹จ๋ฐฉํ–ฅ @OneToMany  ๋™์ž‘ ๊ณผ์ •
  3. ์–‘๋ฐฉํ–ฅ @OneToMany๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ์˜ ๋™์ž‘ ๊ณผ์ •

 

1. ๋งคํ•‘ ํ…Œ์ด๋ธ” ์‚ฌ์šฉ

๋งคํ•‘ ํ…Œ์ด๋ธ”์„ ์ด์šฉํ•œ @OneToMany ์—”ํ‹ฐํ‹ฐ ์„ค๊ณ„ ์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

@Entity
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String isbn;
}
@Entity
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int age;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Book> books = new ArrayList<>();
    
    public void addBook(Book book) {
        books.add(book);
    }
    
    public void removeBook(Book book) {
        books.remove(book);
    } 
}

๊ทธ๋ฆฌ๊ณ  ํ…Œ์ด๋ธ”์˜ ๊ด€๊ณ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋  ๊ฒƒ์ด๋‹ค.

@OneToMany ํ…Œ์ด๋ธ” ๊ด€๊ณ„

์ด ๊ฒฝ์šฐ ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”(author_books)์—๋Š” ๋‘ ๊ฐœ์˜ ์™ธ๋ž˜ํ‚ค(author_id, books_id)๊ฐ€ ์žˆ๋‹ค. ๋•Œ๋ฌธ์— ์ธ๋ฑ์‹ฑ ์‹œ book ํ…Œ์ด๋ธ”์—์„œ author์˜ id๋ฅผ ์ง์ ‘ ์ฐธ์กฐํ•˜๋Š” ๊ฒฝ์šฐ๋ณด๋‹ค ๋” ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๋˜ํ•œ book, author_books, author ๊ฐ„์˜ 3๊ฐœ์˜ ์กฐ์ธ์ด ํ•„์š”ํ•˜์—ฌ ์ฟผ๋ฆฌ ์ž‘์—…์—๋„ ์˜ํ–ฅ์„ ๋ฏธ์นœ๋‹ค.

์ด์ œ INSERT์™€ DELETE์‹œ ์–ด๋– ํ•œ ๊ณผ์ •์ด ์ผ์–ด๋‚˜๋Š”์ง€ ์‚ดํŽด๋ณด์ž

1. ์ƒˆ๋กœ์šด ์ž‘๊ฐ€ ๋ฐ์ดํ„ฐ ๋“ฑ๋ก

์ƒˆ๋กœ์šด ์ž‘๊ฐ€์™€ ๊ทธ ์ž‘๊ฐ€์˜ ์ฑ…์„ ๋“ฑ๋กํ•˜๋Š” ์˜ˆ์‹œ์ด๋‹ค.

@Transactional
public void insertAuthorWithBooks() {
    Author author = new Author("JK ๋กค๋ง", 42);

    Book book1 = new Book("ํ•ด๋ฆฌํฌํ„ฐ์™€ ๋งˆ๋ฒ•์‚ฌ์˜ ๋Œ", "978-89-12345-00-1");
    Book book2 = new Book("ํ•ด๋ฆฌํฌํ„ฐ์™€ ๋น„๋ฐ€์˜ ๋ฐฉ", "978-89-12345-00-2");

    author.addBook(book1);
    author.addBook(book2);

    authorRepository.save(author);
}

์œ„ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ SQL ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

Hibernate: 
    insert into author (age, name) 
    values (?, ?) 
    returning id

Hibernate: 
    insert into book (isbn, title) 
    values (?, ?) 
    returning id

Hibernate: 
    insert into book (isbn, title) 
    values (?, ?) 
    returning id

-- ์•„๋ž˜๋Š” ์–‘๋ฐฉํ–ฅ @OneToMany์—์„œ๋Š” ๋‚ ๋ผ๊ฐ€์ง€ ์•Š๋Š” ์ถ”๊ฐ€ insert ์ฟผ๋ฆฌ๋“ค
Hibernate: 
    insert into author_books (author_id, books_id) 
    values (?, ?)

Hibernate: 
    insert into author_books (author_id, books_id) 
    values (?, ?)

 

2. ๊ธฐ์กด ์ €์ž์˜ ์ƒˆ๋กœ์šด ๋„์„œ ๋“ฑ๋ก

์œ„์—์„œ ๋“ฑ๋กํ•œ ์ž‘๊ฐ€์˜ ์ƒˆ๋กœ์šด ๋„์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•ด ๋ณด์ž.

@Transactional
public void insertNewBook() {
    Author author = authorRepository.findByName("JK ๋กค๋ง");
 
    Book newBook = new Book("ํ•ด๋ฆฌํฌํ„ฐ์™€ ์•„์ฆˆ์นด๋ฐ˜์˜ ์ฃ„์ˆ˜", "978-89-12345-00-3");
    author.addBook(newBook);
    
    authorRepository.save(author);
}

์œ„ ๋ฉ”์„œ๋“œ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ๋ผ๊ฐ„๋‹ค.

Hibernate: 
    select a1_0.id, a1_0.age, a1_0.name 
    from author a1_0 
    where a1_0.name=?

Hibernate: 
    select b1_0.author_id, b1_1.id, b1_1.isbn, b1_1.title 
    from author_books b1_0 
    join book b1_1 
    on b1_1.id=b1_0.books_id 
    where b1_0.author_id=?

Hibernate: 
    insert into book (isbn, title) 
    values (?, ?) 
    returning id

-- ์•„๋ž˜ DML๋ฌธ๋“ค์€ ์–‘๋ฐฉํ–ฅ @OneToMany์—์„œ๋Š” ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š”๋‹ค.
Hibernate: 
    delete from author_books 
    where author_id=?

Hibernate: 
    insert into author_books (author_id, books_id) 
    values (?, ?)

Hibernate: 
    insert into author_books (author_id, books_id) 
    values (?, ?)

Hibernate: 
    insert into author_books (author_id, books_id) 
    values (?, ?)

์ž˜ ๋ณด๋ฉด author_books์— delete์ฟผ๋ฆฌ ํ•œ ๊ฐœ๊ฐ€ ๋‚ ๋ผ๊ฐ€๊ณ , ํ•œ ๊ฐœ์˜ ์ €์„œ๋ฅผ ๋“ฑ๋กํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  3๊ฐœ์˜ insert์ฟผ๋ฆฌ๊ฐ€ ๋‚ ๋ผ๊ฐ€๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” ์ƒˆ ๋„์„œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ ์ž JPA๋Š” ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์—์„œ ๋ชจ๋“  ์—ฐ๊ด€ ๋„์„œ๋ฅผ ์‚ญ์ œํ•œ ํ›„ ๋ฉ”๋ชจ๋ฆฌ์— ์ƒˆ ๋„์„œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋‹ค์‹œ ๋“ฑ๋กํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

3. ๋งˆ์ง€๋ง‰ ๋„์„œ ์‚ญ์ œ

๋‹ค์Œ์€ ์ €์ž์™€ ์—ฐ๊ด€๋œ ๋„์„œ ๋ฆฌ์ŠคํŠธ์—์„œ ๋งˆ์ง€๋ง‰ ๋„์„œ๋ฅผ ์‚ญ์ œํ•˜๋Š” ์˜ˆ์‹œ์ด๋‹ค. ๋งˆ์ง€๋ง‰ ๋„์„œ ์‚ญ์ œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ €์ž์™€ ์—ฐ๊ด€๋œ List<Book>์„ ๊ฐ€์ ธ์˜ค๊ณ  ์ด ๋ฆฌ์ŠคํŠธ์—์„œ ๋งˆ์ง€๋ง‰ ๋„์„œ๋ฅผ ์‚ญ์ œํ•œ๋‹ค.

@Transactional
public void deleteLastBook() {
    Author author = authorRepository.findByName("JK ๋กค๋ง");

    List<Book> books = author.getBooks();
    Book lastBook = books.get(books.size() - 1);

    author.removeBook(lastBook);
}
Hibernate: 
    select a1_0.id, a1_0.age, a1_0.name 
    from author a1_0 
    where a1_0.name=?

Hibernate: 
    select b1_0.author_id, b1_1.id, b1_1.isbn, b1_1.title 
    from author_books b1_0 
    join book b1_1 
    on b1_1.id=b1_0.books_id 
    where b1_0.author_id=?

Hibernate: 
    delete from author_books 
    where author_id=?

Hibernate: 
    insert into author_books (author_id, books_id) 
    values (?, ?)

Hibernate: 
    insert into author_books (author_id, books_id) 
    values (?, ?)

-- ์–‘๋ฐฉํ–ฅ @OneToMany์—์„œ ์œ ์ผํ•˜๊ฒŒ ํ•„์š”ํ•œ DML
Hibernate: 
    delete from book 
    where id=?

๋งˆ์ง€๋ง‰ ๋„์„œ๋ฅผ ์‚ญ์ œํ•˜๊ณ ์ž JPA๋Š” ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”(author_books)์—์„œ ๋ชจ๋“  ์—ฐ๊ด€ ๋„์„œ๋ฅผ ์‚ญ์ œํ•˜๊ณ , ๋ฉ”๋ชจ๋ฆฌ์—์„œ ๋งˆ์ง€๋ง‰ ๋„์„œ๋ฅผ ์ œ๊ฑฐํ•œ ํ›„ ๋‚˜๋จธ์ง€ ๋„์„œ๋ฅผ ๋‹ค์‹œ ์‚ฝ์ž…ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์–‘๋ฐฉํ–ฅ @OneToMany ์—ฐ๊ด€๊ด€๊ณ„์™€ ๋น„๊ตํ–ˆ์„ ๋•Œ ๋ถˆํ•„์š”ํ•œ DML๋ฌธ์ด ์ถ”๊ฐ€๋กœ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜์–ด ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์ดˆ๋ž˜ํ•  ์ˆ˜ ์žˆ๊ณ , ํŠนํžˆ ์—ฐ๊ด€๋œ ๋„์„œ๊ฐ€ ๋งŽ์„์ˆ˜๋ก ์œ„ํ—˜์„ฑ์ด ๋”์šฑ ์ปค์ง„๋‹ค.

 

4. ์ฒซ ๋ฒˆ์งธ ๋„์„œ ์‚ญ์ œ

๋‹ค์Œ์€ ์ €์ž์™€ ์—ฐ๊ด€๋œ ๋„์„œ ๋ฆฌ์ŠคํŠธ์—์„œ ์ฒซ ๋ฒˆ์งธ ๋„์„œ๋ฅผ ์‚ญ์ œํ•˜๋Š” ์˜ˆ์‹œ์ด๋‹ค. 

@Transactional
public void deleteFirstBook() {
    Author author = authorRepository.findByName("JK ๋กค๋ง");

    List<Book> books = author.getBooks();
    Book firstBook = books.get(0);

    author.removeBook(firstBook);
}
Hibernate: 
    select a1_0.id, a1_0.age, a1_0.name 
    from author a1_0 
    where a1_0.name=?

Hibernate: 
    select b1_0.author_id, b1_1.id, b1_1.isbn, b1_1.title 
    from author_books b1_0 
    join book b1_1 
    on b1_1.id=b1_0.books_id 
    where b1_0.author_id=?

Hibernate: 
    delete from author_books 
    where author_id=?

Hibernate: 
    insert into author_books (author_id, books_id) 
    values (?, ?)

Hibernate: 
    insert into author_books (author_id, books_id) 
    values (?, ?)

-- ์–‘๋ฐฉํ–ฅ @OneToMany์—์„œ ์œ ์ผํ•˜๊ฒŒ ํ•„์š”ํ•œ DML
Hibernate: 
    delete from book 
    where id=?

์ฒซ ๋ฒˆ์งธ ๋„์„œ๋ฅผ ์‚ญ์ œํ•˜๋Š” ์ž‘์—…๋„ ๋งˆ์ง€๋ง‰ ๋„์„œ๋ฅผ ์‚ญ์ œํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ๊ณผ์ •์„ ๊ฑฐ์น˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

์ „์ฒด์ ์œผ๋กœ ์ •๋ฆฌํ•˜๋ฉด, ๋งคํ•‘ ํ…Œ์ด๋ธ”์„ ์‚ฌ์šฉํ•œ ๋‹จ๋ฐฉํ–ฅ @OneToMany ๊ด€๊ณ„๋Š” ์•„๋ž˜์˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์กด์žฌํ•œ๋‹ค.

  • ์ถ”๊ฐ€ SQL ๋ฌธ์˜ ๋™์  ๊ฐœ์ˆ˜๋กœ ์ธํ•œ ์„ฑ๋Šฅ ์ €ํ•˜
  • ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์˜ ์™ธ๋ž˜ํ‚ค ์ปฌ๋Ÿผ๊ณผ ๊ด€๋ จ๋œ ์ธ๋ฑ์Šค ํ•ญ๋ชฉ์˜ ์‚ญ์ œ ๋ฐ ์žฌ์ถ”๊ฐ€๋กœ ์ธํ•œ ์„ฑ๋Šฅ ์ €ํ•˜ ๋ฌธ์ œ

๋Œ€๋ถ€๋ถ„์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ์™ธ๋ž˜ํ‚ค์— ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์—์„œ ๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ด€๋œ ๋ชจ๋“  ํ…Œ์ด๋ธ” ํ–‰์„ ์‚ญ์ œํ•  ๋•Œ ํ•ด๋‹น ์ธ๋ฑ์Šค ํ•ญ๋ชฉ๋„ ์‚ญ์ œ๋˜๊ณ , ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์— ๋‹ค์‹œ ์ถ”๊ฐ€๋  ๋•Œ ์ธ๋ฑ์Šค ํ•ญ๋ชฉ์ด ์ถ”๊ฐ€๋œ๋‹ค.

 

2. @JoinColumn ์‚ฌ์šฉ

์ด์ œ @JoinColumn์„ ์‚ฌ์šฉํ•œ ๋‹จ๋ฐฉํ–ฅ @OneToMany ๊ด€๊ณ„์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด์ž.

@Entity
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "author_id")
    private Long authorId; // authorId ์ถ”๊ฐ€

    private String title;
    private String isbn;
}
@Entity
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int age;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "author_id") // @JoinColumn ์‚ฌ์šฉ
    private List<Book> books = new ArrayList<>();
    
    public void addBook(Book book) {
        books.add(book);
    }
    
    public void removeBook(Book book) {
        books.remove(book);
    } 
}

@JoinColumn์„ ์ง€์ •ํ•˜๋ฉด @OneToMany ์—ฐ๊ด€๊ด€๊ณ„์—์„œ ์ž์‹ ํ…Œ์ด๋ธ”์˜ ์™ธ๋ž˜ํ‚ค๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Œ์„ Hibernate์— ์ง€์‹œํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์ด ์—†์–ด์ง€๊ณ , ํ…Œ์ด๋ธ” ์ˆ˜๊ฐ€ 3๊ฐœ์—์„œ 2๊ฐœ๋กœ ์ค„์–ด๋“ ๋‹ค.

@JoinColumn์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์ด ์ œ๊ฑฐ๋จ

์ด์ œ ๋™์ผํ•œ ๋ฉ”์„œ๋“œ๋“ค์˜ ๋™์ž‘์„ ํ™•์ธํ•ด ๋ณด์ž. ์‹คํ–‰ ์ฝ”๋“œ๋Š” ์ด์ „๊ณผ ๋™์ผํ•˜๋‹ค.

1. ์ƒˆ๋กœ์šด ์ž‘๊ฐ€ ๋ฐ์ดํ„ฐ ๋“ฑ๋ก

@Transactional
public void insertAuthorWithBooks() {
    Author author = new Author("JK ๋กค๋ง", 42);

    Book book1 = new Book("ํ•ด๋ฆฌํฌํ„ฐ์™€ ๋งˆ๋ฒ•์‚ฌ์˜ ๋Œ", "978-89-12345-00-1");
    Book book2 = new Book("ํ•ด๋ฆฌํฌํ„ฐ์™€ ๋น„๋ฐ€์˜ ๋ฐฉ", "978-89-12345-00-2");

    author.addBook(book1);
    author.addBook(book2);

    authorRepository.save(author);
}
Hibernate: 
    insert into author (age, name) 
    values (?, ?) 
    returning id

Hibernate: 
    insert into book (author_id, isbn, title) 
    values (?, ?, ?) 
    returning id

Hibernate: 
    insert into book (author_id, isbn, title) 
    values (?, ?, ?) 
    returning id

-- ์–‘๋ฐฉํ–ฅ @OneToMany์—์„œ๋Š” ํ•„์š” ์—†๋Š” ์ถ”๊ฐ€ DML
Hibernate: 
    update book 
    set author_id=? 
    where id=?

Hibernate: 
    update book 
    set author_id=? 
    where id=?

๋งคํ•‘ ํ…Œ์ด๋ธ”์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ์™€ ๋‹ฌ๋ฆฌ, @JoinColumn์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋“ฑ๋ก๋˜๋Š” ๊ฐ ๋„์„œ์— ๋Œ€ํ•ด Hibernate๋Š” author_id๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€์ ์ธ UPDATE ์ฟผ๋ฆฌ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ๋ฌผ๋ก  ์ด ๋ถ€๋ถ„๋„ ์–‘๋ฐฉํ–ฅ @OneToMany ๊ด€๊ณ„์—์„œ๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๋Š” ๋ถˆํ•„์š”ํ•œ ์ฟผ๋ฆฌ๋“ค์ด๋‹ค.

 

2. ๊ธฐ์กด ์ €์ž์˜ ์ƒˆ๋กœ์šด ๋„์„œ ๋“ฑ๋ก

@Transactional
public void insertNewBook() {
    Author author = authorRepository.findByName("JK ๋กค๋ง");

    Book newBook = new Book("ํ•ด๋ฆฌํฌํ„ฐ์™€ ์•„์ฆˆ์นด๋ฐ˜์˜ ์ฃ„์ˆ˜", "978-89-12345-00-3");
    author.addBook(newBook);
    
    authorRepository.save(author);
}
Hibernate: 
    select a1_0.id, a1_0.age, a1_0.name 
    from author a1_0 
    where a1_0.name=?

Hibernate: 
    select b1_0.author_id, b1_0.id, b1_0.isbn, b1_0.title 
    from book b1_0 
    where b1_0.author_id=?

Hibernate: 
    insert into book (author_id, isbn, title) 
    values (?, ?, ?) 
    returning id

-- ์–‘๋ฐฉํ–ฅ @OneToMany์—์„œ๋Š” ํ•„์š” ์—†๋Š” ์ถ”๊ฐ€ DML
Hibernate: 
    update book 
    set author_id=? 
    where id=?

Hibernate๋Š” ์ƒˆ๋กœ ๋“ฑ๋ก๋œ ๋„์„œ์— ๋Œ€ํ•ด author_id๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€์ ์ธ UPDATE๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ์ผ๋‹จ ๊ธฐ์กด์˜ ๋งคํ•‘ ํ…Œ์ด๋ธ”์„ ํ™œ์šฉํ•œ ๋‹จ๋ฐฉํ–ฅ @OneToMany ์—ฐ๊ด€๊ด€๊ณ„์— ๋น„ํ•ด ์ฟผ๋ฆฌ์˜ ๊ฐœ์ˆ˜๊ฐ€ ์ค„์–ด๋“ค์–ด ๋‚˜์˜์ง€๋Š” ์•Š์ง€๋งŒ, ์—ฌ์ „ํžˆ @OneToMany ์—ฐ๊ด€๊ด€๊ณ„์—์„œ๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์€ UPDATE๋ฌธ์ด ์ฒ˜๋ฆฌ๋œ๋‹ค.

 

3. ๋งˆ์ง€๋ง‰ ๋„์„œ ์‚ญ์ œ

@Transactional
public void deleteLastBook() {
    Author author = authorRepository.findByName("JK ๋กค๋ง");

    List<Book> books = author.getBooks();
    Book lastBook = books.get(books.size() - 1);

    author.removeBook(lastBook);
}
Hibernate: 
    select a1_0.id, a1_0.age, a1_0.name 
    from author a1_0 
    where a1_0.name=?

Hibernate: 
    select b1_0.author_id, b1_0.id, b1_0.isbn, b1_0.title 
    from book b1_0 
    where b1_0.author_id=?

Hibernate: 
    update book 
    set author_id=null 
    where author_id=? 
    and id=?

-- ์–‘๋ฐฉํ–ฅ @OneToMany์—์„œ ํ•„์š”ํ•œ ์œ ์ผํ•œ DML
Hibernate: 
    delete from book 
    where id=?

Hibernate๋Š” author_id๋ฅผ null๋กœ ์„ค์ •ํ•ด ์ €์ž๋กœ๋ถ€ํ„ฐ ๋„์„œ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋Š๋Š”๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ orphanRemoval = true ๋•๋ถ„์— ์—ฐ๊ด€์ด ํ•ด์ œ๋œ ๋„์„œ๊ฐ€ ์‚ญ์ œ๋œ๋‹ค. ์ด๊ฒƒ ๋˜ํ•œ ๋งคํ•‘ ํ…Œ์ด๋ธ”์„ ํ™œ์šฉํ–ˆ์„ ๋•Œ๋ณด๋‹ค๋Š” ์ฟผ๋ฆฌ์˜ ๊ฐœ์ˆ˜๊ฐ€ ์ค„์–ด๋“ค์ง€๋งŒ, ์—ฌ์ „ํžˆ ์–‘๋ฐฉํ–ฅ @OneToMany๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ๋ณด๋‹ค๋Š” ๋น„ํšจ์œจ์ ์ด๋‹ค.

 

4. ์ฒซ ๋ฒˆ์งธ ๋„์„œ ์‚ญ์ œ

@Transactional
public void deleteFirstBook() {
    Author author = authorRepository.findByName("JK ๋กค๋ง");

    List<Book> books = author.getBooks();
    Book firstBook = books.get(0);

    author.removeBook(firstBook);
}
Hibernate: 
    select a1_0.id, a1_0.age, a1_0.name 
    from author a1_0 
    where a1_0.name=?

Hibernate: 
    select b1_0.author_id, b1_0.id, b1_0.isbn, b1_0.title 
    from book b1_0 
    where b1_0.author_id=?

-- ์–‘๋ฐฉํ–ฅ @OneToMany์—์„œ ํ•„์š”ํ•œ ์œ ์ผํ•œ DML
Hibernate: 
    update book 
    set author_id=null 
    where author_id=? 
    and id=?

Hibernate: 
    delete from book 
    where id=?

UPDATE๊ฐ€ ์—ฌ์ „ํžˆ ์กด์žฌํ•œ๋‹ค. 

์ •๋ฆฌํ•˜๋ฉด, @JoinColumn์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋งคํ•‘ ํ…Œ์ด๋ธ”์„ ํ™œ์šฉํ•œ ๋‹จ๋ฐฉํ–ฅ @OneToMany๋ณด๋‹ค๋Š” ์ด์ ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์—ฌ์ „ํžˆ ์–‘๋ฐฉํ–ฅ @OneToMany ์—ฐ๊ด€๊ด€๊ณ„๋ณด๋‹ค๋Š” ๋‚ซ์ง€ ์•Š์œผ๋ฉฐ, ์ถ”๊ฐ€ UPDATE๋ฌธ์„ ํ†ตํ•ด ์—ฌ์ „ํžˆ ์„ฑ๋Šฅ ์ €ํ•˜ ๋ฌธ์ œ๊ฐ€ ์กด์žฌํ•œ๋‹ค.

 

 

์–‘๋ฐฉํ–ฅ @OneToMany

์ด์ œ ์–‘๋ฐฉํ–ฅ @OneToMany๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ์–ผ๋งˆ๋‚˜ ํšจ์œจ์ ์ธ์ง€ ์‚ดํŽด๋ณด์ž. ์•„๋ž˜๋Š” ์–‘๋ฐฉํ–ฅ @OneToMany ๊ด€๊ณ„๋ฅผ ๋งบ์€ ์˜ˆ์‹œ ์ฝ”๋“œ์ด๋‹ค.

@Entity
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private Author author;

    private String title;
    private String isbn;
}
@Entity
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int age;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "author", orphanRemoval = true)
    private List<Book> books = new ArrayList<>();
    
    public void addBook(Book book) {
        books.add(book);
        book.setAuthor(this);
    }

    public void removeBook(Book book) {
        books.remove(book);
        book.setAuthor(null);
    }
}

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„์—์„œ๋Š” mappedBy ์†์„ฑ์ด ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•˜๋Š”๋ฐ, ๋ถ€๋ชจ ์ธก์— ์„ค์ •๋˜๋Š” mappedBy ์†์„ฑ์€ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„์˜ ํŠน์„ฑ์„ ๋ถ€์—ฌํ•œ๋‹ค. ๋‹ค์‹œ ๋งํ•ด ์–‘๋ฐฉํ–ฅ @OneToMany ์—ฐ๊ด€๊ด€๊ณ„์—์„œ ๋ถ€๋ชจ ์ธก @OneToMany์— mappedBy๊ฐ€ ์ง€์ •๋˜๊ณ , mappedBy์— ์˜ํ•ด ์ฐธ์กฐ๋˜๋Š” ์ž์‹ ์ธก์— @ManyToOne์ด ์ง€์ •๋œ๋‹ค.

์ด์ œ ๋™์ผํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์ˆ˜ํ–‰ํ–ˆ์„ ๋•Œ ์–ด๋–ป๊ฒŒ ์ฟผ๋ฆฌ๊ฐ€ ์ˆ˜ํ–‰๋˜๋Š”์ง€ ๋น ๋ฅด๊ฒŒ ์‚ดํŽด๋ณด์ž.

1. ์ƒˆ๋กœ์šด ์ž‘๊ฐ€ ๋ฐ์ดํ„ฐ ๋“ฑ๋ก

@Transactional
public void insertAuthorWithBooks() {
    Author author = new Author("JK ๋กค๋ง", 42);

    Book book1 = new Book("ํ•ด๋ฆฌํฌํ„ฐ์™€ ๋งˆ๋ฒ•์‚ฌ์˜ ๋Œ", "978-89-12345-00-1");
    Book book2 = new Book("ํ•ด๋ฆฌํฌํ„ฐ์™€ ๋น„๋ฐ€์˜ ๋ฐฉ", "978-89-12345-00-2");

    author.addBook(book1);
    author.addBook(book2);

    authorRepository.save(author);
}
Hibernate: 
    insert into author (age, name) 
    values (?, ?) 
    returning id

Hibernate: 
    insert into book (author_id, isbn, title) 
    values (?, ?, ?) 
    returning id

Hibernate: 
    insert into book (author_id, isbn, title) 
    values (?, ?, ?) 
    returning id

 

2. ๊ธฐ์กด ์ €์ž์˜ ์ƒˆ๋กœ์šด ๋„์„œ ๋“ฑ๋ก

@Transactional
public void insertNewBook() {
    Author author = authorRepository.findByName("JK ๋กค๋ง");

    Book newBook = new Book("ํ•ด๋ฆฌํฌํ„ฐ์™€ ์•„์ฆˆ์นด๋ฐ˜์˜ ์ฃ„์ˆ˜", "978-89-12345-00-3");
    author.addBook(newBook);

    authorRepository.save(author);
}
Hibernate: 
    select a1_0.id, a1_0.age, a1_0.name 
    from author a1_0 
    where a1_0.name=?

Hibernate: 
    insert into book (author_id, isbn, title) 
    values (?, ?, ?) 
    returning id

 

3. ๋งˆ์ง€๋ง‰ ๋„์„œ ์‚ญ์ œ

@Transactional
public void deleteLastBook() {
    Author author = authorRepository.findByName("JK ๋กค๋ง");

    List<Book> books = author.getBooks();
    Book lastBook = books.get(books.size() - 1);

    author.removeBook(lastBook);
}
Hibernate: 
    select a1_0.id, a1_0.age, a1_0.name 
    from author a1_0 
    where a1_0.name=?

Hibernate: 
    select b1_0.author_id, b1_0.id, b1_0.isbn, b1_0.title 
    from book b1_0 
    where b1_0.author_id=?

Hibernate: 
    delete from book 
    where id=?

 

4. ์ฒซ ๋ฒˆ์งธ ๋„์„œ ์‚ญ์ œ

@Transactional
public void deleteFirstBook() {
    Author author = authorRepository.findByName("JK ๋กค๋ง");

    List<Book> books = author.getBooks();
    Book firstBook = books.get(0);

    author.removeBook(firstBook);
}
Hibernate: 
    select a1_0.id, a1_0.age, a1_0.name 
    from author a1_0 
    where a1_0.name=?

Hibernate: 
    select b1_0.author_id, b1_0.id, b1_0.isbn, b1_0.title 
    from book b1_0 
    where b1_0.author_id=?

Hibernate: 
    delete from book 
    where id=?

์ž˜ ๋ณด๋ฉด ๋‹จ๋ฐฉํ–ฅ @OneToMany์—์„œ ๋ณด์˜€๋˜ ๋ถˆํ•„์š”ํ•œ INSERT๋‚˜ UPDATE๋ฌธ์ด ์ˆ˜ํ–‰๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ „์ฒด์ ์œผ๋กœ ์–‘๋ฐฉํ–ฅ์ผ ๋•Œ๋Š” ์ฝ”๋“œ๋ฅผ ๋ดค์„ ๋•Œ ์ง๊ด€์ ์œผ๋กœ ๋”ฑ ์˜ˆ์ƒ๋˜๋Š” ์ฟผ๋ฆฌ๋“ค๋งŒ ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ •๋ฆฌ

@JoinColumn์„ ์ถ”๊ฐ€ํ•œ ๋‹จ๋ฐฉํ–ฅ @OneToMany ์—ฐ๊ด€๊ด€๊ณ„๋Š” ๋งคํ•‘ ํ…Œ์ด๋ธ”์„ ํ™œ์šฉํ•œ ๋‹จ๋ฐฉํ–ฅ @OneToMany๋ณด๋‹ค๋Š” ์ด์ ์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ์—ฌ์ „ํžˆ ์–‘๋ฐฉํ–ฅ @OneToMany ์—ฐ๊ด€๊ด€๊ณ„๋ณด๋‹ค๋Š” ๋น„ํšจ์œจ์ ์ด๋‹ค. ์ถ”๊ฐ€ UPDATE๋ฌธ์ด ๋ฐœ์ƒํ•˜์—ฌ ์„ฑ๋Šฅ ์ €ํ•˜ ๋ฌธ์ œ๊ฐ€ ์กด์žฌํ•œ๋‹ค.

์–‘๋ฐฉํ–ฅ @OneToMany ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ, ์“ฐ๊ธฐ, ์‚ญ์ œ ์ž‘์—…์—์„œ ๋” ํšจ์œจ์ ์ด๋‹ค. ๋”ฐ๋ผ์„œ ๋‹จ๋ฐฉํ–ฅ @OneToMany๋ณด๋‹ค๋Š” ์–‘๋ฐฉํ–ฅ ๋งคํ•‘์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์„ฑ๋Šฅ ๋ฐ ํšจ์œจ์„ฑ ์ธก๋ฉด์—์„œ ๊ถŒ์žฅ๋œ๋‹ค.