데이터베이스 모델에서는 관계를 맺어주기만 하면 자동으로 양방향 관계가 형성이 되지만(사실 외래키로 죠인만 해주면 되므로 방향이 없다고 볼 수 있음), 객체지향 모델에서는 구현하고자 하는 서비스에 따라 단방향 관계인지 혹은 양방향 관계인지 적절한 선택을 해야 합니다.
엄밀히 말하면 양방향 관계는 서로 다른 단방향 연관 관계 2개를 로직으로 양방향인 것처럼 보이게 할 뿐이라서 양방향 관계는 존재하지 않는다고 할 수 있습니다.
양방향 관계를 매핑할 경우에는 한 쪽 엔티티에 mappedBy
를 통해 연관 관계의 주인을 지정해 주어야 합니다.
양방향 매핑 규칙
mappedBy
속성을 추가해줍니다.@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
private String title;
@ManyToOne
private BookStore bookStore;
...
}
@Entity
public class BookStore {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "bookStore")
private List<Book> books = new ArrayList<>();
// NullPointerException을 방지하기 위해 초기화도 해줍니다
...
}
주인이 아닌쪽에 새로운 데이터를 추가만하고 주인쪽의 외래키를 설정하지 않은 경우
@Entity
public class BookStore {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "bookStore")
private List<Book> books = new ArrayList<>();
// NullPointerException을 방지하기 위해 초기화도 해줍니다
public void addBook(Book book) {
// book.setBookStore(this);
this.books.add(book);
}
...
}
@Test
@Transactional
void contextLoads() {
BookStore bookStore = new BookStore();
bookStore.setName("스프링 북스토어");
bookStoreRepository.save(bookStore);
LOGGER.info("Save Bookstore");
Book book = new Book();
book.setTitle("JPA & Hibernate");
bookStore.addBook(book);
LOGGER.info("Add a book to bookstore");
bookRepository.save(book);
LOGGER.info("Save book");
}
위의 코드에서처럼 BookStore
클래스 안에 있는 addBook()
메소드에서 주석된 코드를 실행하지 않고 테스트를 실행하면 Book
테이블에서 외래키인 book_store_id
가 null
값으로 저장이 됩니다.
즉, 관계를 추가할 때 주인이 아닌 쪽에만 추가를 하면 안되고 주인쪽에서도 추가를 해주는 작업을 해줘야 합니다.
그렇다면 주인인 쪽에만 추가하면되지 왜 데이터베이스에 반영도 안되는 쪽에도 추가를 하는지 의문이 생길 수도 있습니다.
이 질문은 JPA를 왜 사용하냐 ORM을 왜 사용하냐는 질문과 같다고 볼 수 있습니다. 객체 지향적으로 코드를 작성하기 위해서 JPA를 사용하는 것이고 객체 지향적인 관점에서 보면 당연히 연관관계에 있는 두 쪽 모두한테 관계를 추가해주는 것이 당연하다고 볼 수 있습니다.