velog에서 이전한 글 입니다.
2023.07.13 - [spring] - 스프링) JPA Entity 연관관계 - 외래 키 주인 (23-07-11)
지난 포스팅에 이어
JPA Entity Option
FetchType EAGER/LAZY (지연 로딩)
public @interface ManyToOne{
...
FetchType fetch() default FetchType.EAGER;
...
}
public @interface OneToMany{
...
FetchType fetch() default FetchType.LAZY;
...
}
EAGER는 첫 select와 동시에 join으로 참조 항목을 가져온다.
LAZY는 첫 select에서 join을 하지않고 참조가 필요할 때 추가로 select를 요청하고 가져온다.
ManyToOne의 default FetchType은 EAGER
OneToMany의 default FetchType은 LAZY 이다.
몇 개일지 모를 다수를 기본적으로 join하는 건 성능에 방해될 수 있어서 LAZY일 것이다.
public class Post extends Timestamped {
...
@OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE)
private List<Comment> commentList = new ArrayList<>();
...
}
public class Comment extends Timestamped {
....
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;
@ManyToOne()
@JoinColumn(name = "user_id")
private User user;
}
위와 같은 entity 관계에 Post를 findAll
할 경우 sql은 아래와 같다.
Hibernate:
/* <criteria> */ select
...
p1_0.subject,
p1_0.username
from
post p1_0
Hibernate:
select
...
c1_0.created_at,
c1_0.modified_at,
u1_0.id,
u1_0.password,
u1_0.username
from
comment c1_0
left join
users u1_0
on u1_0.id=c1_0.user_id
where
c1_0.post_id=?
post에서 commentList는 default가 LAZY 이다. post만 select한 후 commentList를 사용할 때 추가로 comment를 select하는 것을 볼 수 있다.
여기서 comment는 user와 post 필드가 있다. default는 EAGER 이고 post는 LAZY로 설정했다.
comment는 user를 바로 join하는 것을 볼 수 있고 post는 사용하지 않아 추가 select query가 실행되지 않은 것을 볼 수 있다.
CascadeType PERSIST/REMOVE (영속성 전이)
영속성 전이 : 영속 상태의 Entity에서 수행되는 작업이 연관된 Entity까지 전파되는 상황
public class User {
...
(1)
@OneToMany(mappedBy = "user")
(2)
@OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Food> foodList = new ArrayList<>();
public void addFoodList(Food food) {
this.foodList.add(food);
food.setUser(this);// 외래 키(연관 관계) 설정
}
}
User user = new User();
user.setName("Robbie");
...
user.addFoodList(food1);
user.addFoodList(food2);
userRepository.save(user);
(1)의 설정은 userRepo의 save가 foodRepo의 save까지 이어지진 않는다.
(2)의 설정은 CascadeType.PERSIST
로 등록(insert)에 대해 영속성 전이가 적용되었고 userRepo save로 foodRepo에 food1, food2를 저장한 것을 볼 수 있다.
User user = userRepository.findByName("Robbie");
userRepository.delete(user);
(1)의 설정은 userRepo의 delete가 foodRepo의 delete까지 이어지진 않는다.
(2)의 설정은 CascadeType.REMOVE
로 삭제(delete)에 대해 영속성 전이가 적용되었고 userRepo delete로 food1, food2가 삭제된 것을 볼 수 있다.
orphanRemoval (고아 Entity 삭제)
(@Transactional)
User user = userRepository.findByName("Robbie");
Food chicken = null;
for (Food food : user.getFoodList()) {
if(food.getName().equals("후라이드 치킨")) {
chicken = food;
}
}
if(chicken != null) {
user.getFoodList().remove(chicken);
}
위와 같이 연관 관계 데이터를 삭제할 때CascadeType.REMOVE
는 Delete 쿼리를 수행하지 않는다.
public class User {
...
(1)
@OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<Food> foodList = new ArrayList<>();
...
}
여기서 orphanRemoval
설정은 연관 관계 데이터의 부분 삭제를 쿼리로 이어지게 해준다.
cascade remove는 본인 entity 삭제시 연관관계 데이터를 모두 삭제해주었고orphanRemoval
는 cascade remove의 기능을 포함하고 추가로 연관관계 데이터 부분 삭제시 삭제 쿼리를 날려준다.
orphanRemoval
는 ManyToOne에는 없는 옵션이다.
반대쪽 연관관계 데이터를 삭제하는 기능인데 외래키를 가지고 있는 Many에서 One을 죽이는 건 말도 안되고 반대가 One이므로 부분 삭제 기능이 필요없다.
주의
orphanRemoval
나 CascadeType.REMOVE
는 신중히 사용할 필요가 있다.
option을 걸기전에 '나'뿐만 아니라 다른 누군가도 '나'의 연관 Entity를 참조하고 있는지 잘 확인해야 한다.
'Spring > JPA' 카테고리의 다른 글
JDBC, JPA, Hibernate, Spring Data JPA (0) | 2023.09.28 |
---|---|
spring data JPA - slice, page (무한 스크롤, 페이지네이션) (0) | 2023.07.27 |
스프링) JPA Entity 연관관계 - 외래 키 주인 (23-07-11) (1) | 2023.07.14 |
스프링) Spring Boot의 JPA @Transactional/SimpleJpaRepository (23-07-09) (0) | 2023.07.14 |
스프링) JPA persistence/트랜잭션/Entity상태 (23-07-08) (0) | 2023.07.14 |