주변에서 One To Many 단방향에 관해서 물어볼 때마다 저는 항상 이렇게 대답했습니다.
김영한 님의 인프런 강의에서 봤는데~
One To Many 단방향은 좋지 않다. 차라리 양방향을 해라.
이유는~때문이다..
이렇게 대답했습니다. 하지만 직접 문제를 겪어 본 적이 없어서 말에 설득력이 부족했습니다.
그래서 직접 한 번 실험해보고 문제점을 정리했습니다.
먼저 김영한님은 일대다 단방향 매핑은 이러한 단점이 있다고 하셨습니다.
- 엔티티가 관리하는 외래 키가 다른 테이블에 있음 (Many에 외래키 존재)
- 연관관계 관리를 위해 추가로 update sql 실행 (성능상 큰 차이는 없다)
- 개발을 하다 보면 B를 만졌는데 A도 update sql문이 나가니 헷갈린다.
- 그래서 필요하다면 일대다 보다는 양방향 관계로 한다. ( B는 A가 필요 없더라도, 객체 지향적으로 손해를 보는 거 같지만) - 트레이드 오프
@JoinTable을 사용한 단방향 @OneToMany
Article과 Image 엔티티
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private String content;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Image> images = new ArrayList<>();
public void addImage(final Image image) {
images.add(image);
}
}
@Entity
public class Image {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private String url;
}
@OneToMany 단방향에서 따로 조인 설정을 넣어주지 않으면 단방향 @JoinTable
이 적용됩니다.
Article과 Image를 저장하는 코드
Article article = new Article("foo");
article.addImage(new Image("foo 1"));
article.addImage(new Image("foo 2"));
article.addImage(new Image("foo 3"));
article.addImage(new Image("foo 4"));
articleRepository.saveAndFlush(article);
저장 sql
Hibernate: insert into article (id, content) values (null, ?)
Hibernate: insert into image (id, url) values (null, ?)
Hibernate: insert into image (id, url) values (null, ?)
Hibernate: insert into image (id, url) values (null, ?)
Hibernate: insert into image (id, url) values (null, ?)
Hibernate: insert into article_images (article_id, images_id) values (?, ?)
Hibernate: insert into article_images (article_id, images_id) values (?, ?)
Hibernate: insert into article_images (article_id, images_id) values (?, ?)
Hibernate: insert into article_images (article_id, images_id) values (?, ?)
OneToMany에서 JoinTable을 사용하면 Article과 Image를 저장한 후에 매핑테이블에 한 번 더 저장해줍니다. 하나의 외래키가 아닌 두 개의 외래키가 저장되는데요. 이 경우 1:N 관계 보다는 N:N 연관 처럼 보이며 매우 효율적이지 않습니다. 또 한 세 개의 테이블이 사용되므로 필요한 것보다 더 많은 공간을 사용하고 있습니다.
삭제
Image 엔티티를 삭제 하려면 어떻게 해야할까요?ImageRepository.delete()
를 통해서 삭제해보겠습니다.
코드
Image image = imageRepository.findById(1L).get();
imageRepository.delete(image);
삭제 결과
PUBLIC.ARTICLE_IMAGES FOREIGN KEY(IMAGES_ID) REFERENCES PUBLIC.IMAGE(ID) (1)"; SQL statement:
delete from image where id=? [23503-199]
이런 에러가 납니다. article_images 테이블에서 Image의 id를 외래키로 가지고 있기 때문에 제거가 불가능합니다.
Image를 삭제하는 방법은 Article의 Image List에서 remove 해줘야 합니다.
Image image = imageRepository.findById(1L).get();
article.getImages().remove(image);
testEntityManager.flush();
삭제 sql
Hibernate: delete from article_images where article_id=?
Hibernate: insert into article_images (article_id, images_id) values (?, ?)
Hibernate: insert into article_images (article_id, images_id) values (?, ?)
Hibernate: insert into article_images (article_id, images_id) values (?, ?)
Hibernate: delete from image where id=?
생각대로라면 article_images에서 1번, image에서 1번 이렇게 총 2번 삭제될 줄 알았는데 결과는 의외였습니다.
- article_images 테이블 에서
article_id
를 통해 모두 지운다. - 지우려는 image를 제외한 나머지 image들을 article_images에 다시 저장한다.
- 지우려는 image를 테이블에서 삭제한다.
자세한 이유는 JPA-일대다-단방향-매핑-잘못-사용하면-벌어지는-일
@JoinColumn을 사용한 단방향 @OneToMany
public class Article{
....
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name="article_id")
private List<Image> images = new ArrayList<>();
....
저장 sql
Hibernate: insert into article (id, content) values (null, ?)
Hibernate: insert into image (id, url) values (null, ?)
Hibernate: insert into image (id, url) values (null, ?)
Hibernate: insert into image (id, url) values (null, ?)
Hibernate: insert into image (id, url) values (null, ?)
Hibernate: update image set article_id=? where id=?
Hibernate: update image set article_id=? where id=?
Hibernate: update image set article_id=? where id=?
Hibernate: update image set article_id=? where id=?
@JoinColumn
을 사용하면 image를 DB에 저장할 때, article_id를 모르기 때문에 먼저 저장한 후에 update문을 통해서 article_id를 한 번 더 실행합니다.
삭제 sql
Hibernate: update image set article_id=null where article_id=? and id=?
Hibernate: delete from image where id=?
OneToMany 양방향
그렇다면 양방향은 어떨까요?
Article, Image
@Entity
public class Article{
@OneToMany(mappedBy = "article",cascade = CascadeType.ALL, orphanRemoval = true)
private List<Image> images = new ArrayList<>();
public void addImage(final Image image) {
images.add(image);
image.setArticle(this);
}
public void removeImage(final Image image){
images.remove(image);
image.setArticle(null);
}
}
@Entity
public class Image {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id")
private Article article;
...
}
저장 sql
Hibernate: insert into article (id, content) values (null, ?)
Hibernate: insert into image (id, article_id, url) values (null, ?, ?)
Hibernate: insert into image (id, article_id, url) values (null, ?, ?)
Hibernate: insert into image (id, article_id, url) values (null, ?, ?)
Hibernate: insert into image (id, article_id, url) values (null, ?, ?)
삭제 sql
Hibernate: delete from image where id=?
딱 한 번씩, 간단하게 실행됩니다.
양방향을 하면 이렇게 편하고 사용함에 있어서도 편한데 왜 양방향을 사용하지 않고 @OneToMany 단방향을 생각했을까요?
객체는 가급적이면 단방향으로 해주는 게 좋습니다. 양방향으로 하면 신경써줘야 할 부분이 많죠.
의존성? A가 변경될 때 B도 함께 변경될 수 있다.
즉, 양방향은 관리가 어렵고 논리적으로 서로가 계속 변경합니다.
(A 변경 -> B 변경 -> A 변경...)
이런 문제가 있다 보니 @OneToMany 단방향을 생각했는데, 어느 날 양방향에 대해서 질문을 했다가 좋은 대답을 들었습니다.
제가 양방향은 위험하지 않느냐? 사용을 자제하는 게 좋지 않을까요?
라고 물었습니다.'위험한 거'랑 '하면 안 되는 것'은 다르다. 자동차 타는 것은 위험하지 않느냐? 그러면 타면 안 되냐? 사용에 주의를 하라는 거다.
라는 좋은 대답을 들었습니다.
정리하자면,
저장할 때
삭제할 때
'JPA' 카테고리의 다른 글
JPA Entity를 JSON으로 변환할 때 발생할 수 있는 문제점과 해결방법 (0) | 2019.02.27 |
---|