티스토리 뷰

Java/jpa

JPA 양방향 연관관계 매핑

LichKing 2020. 4. 25. 14:56

JPA 유튜브 중에 아래와같은 동영상이 있어 이 문제를 해결해보고자한다.

https://www.youtube.com/watch?v=brE0tYOV9jQ&t=156s

 

먼저 방송에서도 언급하지만 코드베이스는 매우작다. BOOK 엔티티와 BOOK_STORE 엔티티 두개만 존재하고 이 둘은 BOOK(N) : BOOK_STORE(1) 관계이다.

 

@Entity
@Getter
@Setter
public class Book {
    @Id @GeneratedValue
    @Column(name = "BOOK_ID")
    private Long id;

    @Column(name = "ISBN")
    private String isbn;

    @Column(name = "TITLE")
    private String title;

    @ManyToOne
    private BookStore bookStore;
}
@Entity
@Getter
@Setter
public class BookStore {
    @Id @GeneratedValue
    @Column(name = "BOOKSTORE_ID")
    private Long id;

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

    @OneToMany(mappedBy = "bookStore")
    private Set<Book> books = new HashSet<>();

    public void add(Book book) {
        books.add(book);
    }
}

 

 

그리고 엔티티는 서로 양방향으로 참조를 하고있다.

해당 엔티티를 이용한 테스트코드 역시 간단하다.

 

@SpringBootTest
public class JpaTest {
    @Autowired
    private BookStoreRepository bookStoreRepository;
    @Autowired
    private BookRepository bookRepository;

    @Test
    public void test() {
        BookStore bookStore = new BookStore();
        bookStore.setName("책방");
        bookStoreRepository.save(bookStore);

        Book book = new Book();
        book.setTitle("JPA");

        bookStore.add(book);
        bookRepository.save(book);
    }
}

 

테스트코드를 돌려보면 잘 저장이 되는걸 볼수있다. 다만 BOOK 테이블에 BOOK_STORE 와의 관계를 이어주는 외래키가 정상적으로 저장되지않는다.

 

 

 

이는 JPA 의 일대다 양방향 연관관계를 잘못사용해 발생하는 문제이다. 또한 객체와 관계형의 차이에서 발생하는 임피던스 불일치라고도 볼 수 있는데 RDB 에서는 한쪽에(1:N 관계에서 N쪽에) 외래키를 저장해두고 어느쪽에서는 반대편을 참조할 수 있다. 하지만 객체관계에서는 양방향 참조를 위해서는 서로가 서로를 참조로 갖고있어야한다. 이 차이때문에 bookStore(1) 에서 book(N) 을 넣어두고 저장하는 문제가 발생한것이다.

 

다시 RDB 관점에서 생각해보자. RDB 관계에서 외래키는 N 이 보유하게된다. 즉 bookStore 가 book 을 갖고있는게 아니라 book 이 bookStore 를 들고있어야한다.

 

@SpringBootTest
public class JpaTest {
    @Autowired
    private BookStoreRepository bookStoreRepository;
    @Autowired
    private BookRepository bookRepository;

    @Test
    public void test() {
        BookStore bookStore = new BookStore();
        bookStore.setName("책방");
        bookStoreRepository.save(bookStore);

        Book book = new Book();
        book.setTitle("JPA");

        // 변경부분
        book.setBookStore(bookStore);
        bookRepository.save(book);
    }
}

코드를 변경하고 실행하면 정상적으로 외래키가 들어간걸 볼 수 있다.

JPA 에서 @OneToMany 애노테이션을 사용하면 mappedBy 속성을 지정해주게된다. 상대편의 어떤 엔티티에 의해 매핑될것인지를 명시해주는것이다. 그리고 1:N 관계에서 연관관계의 주인은 N이 되게된다. 때문에 mappedBy 를 이용한 필드는 JPA 에서 read-only 로 이용되어 실제 insert 시에는 insert 되지않는다.

 

이는 알고있는 상태에서 JPA 를 쓸때도 아무생각없이 잘 발생할 수 있는 실수이므로 테이블의 제약조건을 잘 걸어놓거나(nullable), 연관관계를 이어주는 메서드를 이용하는등의 습관을 들이는게 좋을것같다.

공유하기 링크
TAG
댓글
댓글쓰기 폼