서론
오늘 spring을 혼자 공부하는 중, 유튜브에서 백기선님의 "이 문제 답 모르면 JPA를 쓰지말아라!" 하는 영상을 보게되었다.
그래서 객체간의 관계에 대해서 다시 공부하는 시간을 가지게 되었다.
객체 간 관계 매핑?
"객체 간 관계"는 예를 들어, 하나의 클래스가 다른 클래스를 참조하거나, 여러 클래스들이 상호작용하고 서로 연결되어 있는 것을 의미한다.
하지만 SQL 쿼리문으로 수동으로 객체간의 관계를 관리할 수 있다. 하지만 이 방법은 매우 번거로울 수 있다.
이 관계를 데이터베이스에 저장하고 유지하기 위해 JPA는 몇 가지 어노테이션을 제공한다.
JPA에서의 객체 간 관계 매핑은 다음과 같은 상황을 다룬다.
- 1대1(One-to-One): 하나의 객체가 다른 하나의 객체와 연결된 경우.
- 1대다(One-to-Many): 하나의 객체가 여러 개의 다른 객체와 연결된 경우.
- 다대1(Many-to-One): 여러 개의 객체가 하나의 객체에 연결된 경우.
- 다대다(Many-to-Many): 여러 개의 객체가 서로 다수의 다른 객체와 연결된 경우.
JPA(Java Persistence API)에서 객체 간 관계를 매핑한다는 것은 객체 지향 프로그래밍에서 사용되는 클래스와 그들 간의 관계를 관계형 데이터베이스의 테이블과 그들 간의 관계로 매핑하는 작업을 의미한다.
양방향 관계 매핑
양방향 관계 매핑은 두 엔티티 간의 서로 다른 방향으로의 연관 관계를 설정하는 것을 의미한다. 이는 객체 지향 모델에서 서로를 참조하는 상황을 의미하는데, 예를 들어, 두 엔티티가 서로를 참조하고 있을 때, 한 엔티티의 인스턴스를 통해 다른 엔티티에 접근할 수 있고, 반대의 경우도 가능한 관계다.
문제
@Entity
@Getter
@Setter
public class Book {
@Id @GeneratedValue
private Integer id;
private String isbn;
private String title;
@ManyToOne
private BookStore bookSotre;
}
책의 데이터를 가지는 Book엔티티를 만들어준다.
Book 엔티티에서 @ManyToOne을 통해서 여러개의 Book엔티티가 하나의 BookStore에 속할 수 있다는 것을 나타낸다.
@Entity
@Getter
@Setter
public class BookStore {
@Id @GeneratedValue
private Integer id;
private String name;
@OneToMany(mappedBy = "bookStore")
private Set<Book> books = new HashSet<>;
void add(Book book) {
this.books.add(book);
}
}
Book들을 담게될 BookStore엔티티를 생성한다.
BookStore에서는 @OneToMany를 통해서 BookStroe엔티티가 Book엔티티와 1:n의 관계를 가지게 된다.
mappedBy는 연관된 엔티티(Book)에 해당 관계 속성을 나타낸다.
books라는 Book엔티티가 여러개 포함될 수 있는 필드가 존재한다. 이곳에 add라는 메서드로 Book을 BookStore에 추가한다.
@SpringBootTest
public class DemoJpaTestApplicationTests {
@Autowired
BookStoreRepository bookStoreRepository;
@Autowired
BookRepository bookRepository;
@Test
public void contextLoads() {
BookStore bookStore = new BookStore();
bookStore.setName("책방");
bookSotreRepository.save(bookStore);
Book book = new Book();
book.setTitle("백과사전");
bookStore.add(book);
bookRepository.save(book);
}
}
- 테스트
BookStore 인스턴스를 생성하고, 이름을 세팅하고 저장해준다.
Book도 생성하고 제목을 넣는다.
add메서드로 book을 bookStore에 포함시킨다.
이후 book을 저장해준다.
- 예상
book, bookstore가 생성되고, book에는 BookStore id가 생성되어야함.
-결과
book에 BookStore id가 생성되지 않는다.
mappedBy
@Entity
@Getter
@Setter
public class BookStore {
@Id @GeneratedValue
private Integer id;
private String name;
@OneToMany(mappedBy = "bookStore")
private Set<Book> books = new HashSet<>;
void add(Book book) {
this.books.add(book);
}
}
양방향 관계 매핑에서는 mappedBy에 주목해야한다.
서로 @OneToMany, @ManyToOne이라는 어노테이션만 달았다고 서로 양방향으로 보고있는 것이 아니다.
mappedBy = "bookStore" 라고 지정을 해주었기 때문에 양방향 관계가 되는 것이다.
mappedBy는 JPA의 양방향 관계를 매핑할 때 사용되며, 관계의 주인을 설정하는데 사용된다.
BookStore엔티티가 Book엔티티와 매핑된다고 하면, mappedBy를 이용해 BookStore의 books라는 필드는 Book의 "bookStore"에 의해서 관리된다고 JPA에 알려준다. 이 때문에 Book이 관계의 주인이 된다.
즉, Book엔티티의 bookStore를 기준으로 매핑이 된다. 그러므로 add메서드를 통해서 BookStore에 Book을 추가하면, Book의 BookStore필드도 업데이트가 될 수 있다.
해결
@Entity
@Getter
@Setter
public class BookStore {
@Id @GeneratedValue
private Integer id;
private String name;
@OneToMany(mappedBy = "bookStore")
private Set<Book> books = new HashSet<>;
void add(Book book) {
book.setBookStore(this); // 추가
this.books.add(book);
}
}
해결 방법으로는 Book을 BookStore에 추가할 때, book에 BookStore자기 자신을 추가한다.
Book을 BookStore에 추가하는데, 관계의 주인은 Book인데 주인인 book에 대한 관계는 없고, books에 대한 관계만 설정이 되고 있었기 때문이다. 이렇게 관계의 주인에게 변화가 일어나야 DB에서 동작이 일어난다.
그러므로 books로 BookStore에서 Book엔티티를 관리하는, book에서 BookStore를 설정하기위해 설정하므로, 둘 다 있어야 양쪽의 엔티티를 관리할 수 있는 양방향 관계 매핑이 성립한다. 양쪽의 엔티티를 모두 관리해주어야 한다.
'Backend > Spring Boot' 카테고리의 다른 글
Spring boot - 트랜잭션(Transaction) (0) | 2023.12.07 |
---|---|
Spring Boot - 용어 (0) | 2023.08.11 |
Spring Boot - 연관관계 매핑 (0) | 2023.07.30 |
Spring Boot - MVC 모델 (0) | 2023.07.30 |
Spring Boot - Spring Data JPA 활용 (0) | 2023.07.29 |