일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- 외래키제약조건위반
- CPU스케줄링
- flyway
- 코테
- 임베디드타입
- 트리맵
- 프로젝트
- Spring JPA
- springboot
- 폰켓몬
- 파이널프로젝트
- SpringBatch
- 트리셋
- 스케일아웃
- 해시
- 2178
- fatch
- DB replication
- BFS
- CS
- 프로그래머스
- 산업은행청년인턴
- 구현
- 컴퓨터구조
- 운영체제
- findById
- 그래프탐색
- 백준
- JPA
- 산업은행it
- Today
- Total
나 JAVA 봐라
[Spring JPA] JPA의 findById와 EAGER, LAZY 전략 본문
프로젝트 진행 중, 예상치 못한 에러로 인해 시간이 지체 되었다... ㅠ
@Entity(name = "review")
public class Review extends AuditingField {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@ManyToOne(optional = false)
@JoinColumn(name = "gym_id", nullable = false)
private Gym gym;
...
Review Entity는 위와 같이 User, Gym과 연관 관계를 가지고 있다.
이 후, review의 id값으로 review를 조회하기 위해 서비스에서 아래와 같은 메소드를 작성했다.
@Transactional
public ReviewDto getReviewDtoById(Long id) {
Review review = reviewRepository.findById(id)
.orElseThrow(() -> new BusinessException(ReviewErrorCode.NOT_FOUND));
return ReviewDto.from(review);
}
근데 이렇게 했을 때
- Review Table에는 리뷰 id가 존재하지만,
- User Table이나 Gym Table에 user, gym id가 존재하지 않는 경우
리뷰가 존재하지 않는다고 Exception을 던져버리는 문제가 발생했다.
보통의 경우에는 review를 잘 반환하겠지만, 만약 gym table에서 gym이 삭제되거나 (ex. 폐업), user table에서 user가 삭제될 경우(ex. 회원 탈퇴) 에는 리뷰 자체는 존재 하더라도 불러오지 못하는 문제가 발생할 것이라고 생각했다.
찾아보니, findById를 통해 엔터티를 조회할 때, FetchType이 EAGER로 설정되어 있다면, user와 gym 필드도 ‘즉시’ 함께 로딩되기 때문에 user와 gym이 존재하지 않을 경우 조회하는 시점에서 예외가 발생한다고 한다.
쿼리를 확인해보니 아래와 같이 날아간다.
Hibernate: select r1_0.id,r1_0.angry_count,r1_0.content,r1_0.created_at,r1_0.gym_id,g1_0.id,g1_0.address,g1_0.brand,g1_0.close_time,g1_0.created_at,g1_0.images,g1_0.instagram_link,g1_0.latitude,g1_0.longitude,g1_0.name,g1_0.open_time,g1_0.phone_number,g1_0.road_address,g1_0.score_average,g1_0.setting_day,g1_0.updated_at,r1_0.help_count,r1_0.images,r1_0.interest_count,r1_0.like_count,r1_0.score,r1_0.thumb_count,r1_0.updated_at,r1_0.user_id,u1_0.id,u1_0.birthday,u1_0.created_at,u1_0.email,u1_0.gender,u1_0.nickname,u1_0.password,u1_0.profile_image,u1_0.sign_up_type,u1_0.updated_at from review r1_0 join gym g1_0 on g1_0.id=r1_0.gym_id join user u1_0 on u1_0.id=r1_0.user_id where r1_0.id=?
정리 해보면 ,
Review
엔티티 (r1_0
별칭 사용)- id, angry_count, content, created_at, help_count, images, interest_count, like_count, score, thumb_count, updated_at, user_id, gym_id
Gym
엔티티 (g1_0
별칭 사용)- id, address, brand, close_time, created_at, images, instagram_link, latitude, longitude, name, open_time, phone_number, road_address, score_average, setting_day, updated_at
User
엔티티 (u1_0
별칭 사용)- id, birthday, created_at, email, gender, nickname, password, profile_image, sign_up_type, updated_at
현재 코드에서는 따로 FetchType을 명시하지 않아 디폴트 값으로 EAGER로 되어 있는 상태여서, 해당 문제가 발생한 것 같다.
그렇다면 FetchType을 LAZY로 설정하면 해결이 될까?
@Entity(name = "review")
public class Review extends AuditingField {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "gym_id", nullable = false)
private Gym gym;
...
위와 같이 fetch = FetchType.LAZY 로 바꿔준 후, 리뷰를 조회해봤더니 아래와 같은 익셉션이 발생한다.
**jakarta.persistence.EntityNotFoundException: Unable to find org.fastcampus.orurydomain.gym.db.model.Gym with id 2**
EntityNotFoundException 에러가 떴다. 리뷰에서 참조한 gym은 존재하지 않는다는 뜻이다.
쿼리를 확인해보면
Hibernate: select r1_0.id,r1_0.angry_count,r1_0.content,r1_0.created_at,r1_0.gym_id,r1_0.help_count,r1_0.images,r1_0.interest_count,r1_0.like_count,r1_0.score,r1_0.thumb_count,r1_0.updated_at,r1_0.user_id from review r1_0 where r1_0.id=?
Hibernate: select u1_0.id,u1_0.birthday,u1_0.created_at,u1_0.email,u1_0.gender,u1_0.nickname,u1_0.password,u1_0.profile_image,u1_0.sign_up_type,u1_0.updated_at from user u1_0 where u1_0.id=?
Hibernate: select g1_0.id,g1_0.address,g1_0.brand,g1_0.close_time,g1_0.created_at,g1_0.images,g1_0.instagram_link,g1_0.latitude,g1_0.longitude,g1_0.name,g1_0.open_time,g1_0.phone_number,g1_0.road_address,g1_0.score_average,g1_0.setting_day,g1_0.updated_at from gym g1_0 where g1_0.id=?
이렇게 쿼리가 세 번 날아가고 있다.
💡 참고 )
Hibernate 쿼리 중 LAZY 로딩 쿼리는 연관된 엔티티(User와 Gym)를 지연 로딩으로 설정하여 실제로 필요한 시점에 추가로 조회한 것이다.
EAGER 쿼리는 연관된 엔티티를 즉시(EAGER) 로딩으로 설정하여, Review 엔티티를 조회할 때 바로 연관된 User와 Gym 엔티티를 함께 조회한다. 이로 인해 Review 엔티티를 조회할 때 바로 User와 Gym의 정보가 모두 로딩되어 반환된다.
지연 로딩을 사용하면, 실제로 연관된 엔티티의 정보가 필요한 시점에 데이터베이스에서 추가 쿼리를 수행하게 되므로, 쿼리의 성능에 일부 영향을 미칠 수 있다. 하지만 반면에 즉시 로딩을 사용하면 연관된 엔티티의 정보를 함께 가져오기 때문에 쿼리 횟수가 줄어들지만, 성능 저하의 가능성이 있다.
각각의 로딩 전략은 상황에 따라 선택되어야 하며, 실제로 필요한 데이터만 가져오기 위해서는 지연 로딩을 사용하는 것이 일반적이라고 한다.
결국 어떤 fatch 전략이든 삭제된 엔티티를 참조하고 있어 해결을 못하니까, 해당 부분에 대해서는 review의 id 값으로만 조회할 수 있도록 repository에서 메소드를 새로 생성해주면 어떨까 했는데, 해당 부분에 대해서는 JPA에서 제공하는 메소드가 없었고 팀원들과 논의했을 때에도 해결 방법이 따로 생각나지 않았다.
결론은 User, Gym 필드에 is_deleted를 추가해서 삭제 되었으면 true로 두기로 하고, User와 Gym에서는 탈퇴를 하더라도 DB 자체에서는 사라지지 않고 id 값은 유지되도록 하기로 했다.
'Spring > Spring JPA' 카테고리의 다른 글
[Spring JPA] Spring JPA 와 SQL의 연관관계 (0) | 2024.01.20 |
---|---|
@Transactional과 DataIntegrityViolationException (1) | 2024.01.10 |