흰 스타렉스에서 내가 내리지

[JPA] findById() 와 getReferenceById() 본문

Spring

[JPA] findById() 와 getReferenceById()

주씨. 2024. 2. 9. 15:08
728x90

 

# findById 를 사용했을 경우

Term termPS = termRepository.findById(requestDto.getTermId())
        .orElseThrow();
Member memberPS = memberRepository.findById(memberId)
        .orElseThrow();

// 북마크 테이블을 업데이트 합니다.
Optional<TermBookmark> termBookmarkOptional = termBookmarkRepository.findByTermAndMember(termPS, memberPS);

if (termBookmarkOptional.isEmpty()){
    termBookmarkRepository.save(TermBookmark.of(termPS, memberPS));
}else{
    termBookmarkOptional.get().addFolderCnt();
}

 

 

Hibernate: 
    select
        t1_0.term_id,
        t1_0.categories,
        t1_0.description,
        t1_0.name 
    from
        term t1_0 
    where
        t1_0.term_id=?
2024-02-08T18:48:51.095+09:00 TRACE 69831 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:BIGINT) <- [2]
Hibernate: 
    select
        m1_0.member_id,
        m1_0.categories,
        m1_0.created_date,
        m1_0.domain,
        m1_0.email,
        m1_0.folder_limit,
        m1_0.identifier,
        m1_0.introduction,
        m1_0.job,
        m1_0.modified_date,
        m1_0.name,
        m1_0.nickname,
        m1_0.point,
        m1_0.profile_img,
        m1_0.refresh_token,
        m1_0.role,
        m1_0.social_id,
        m1_0.social_type,
        m1_0.year_career 
    from
        member m1_0 
    where
        m1_0.member_id=?
2024-02-08T18:48:51.096+09:00 TRACE 69831 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:BIGINT) <- [1]
Hibernate: 
    select
        tb1_0.term_bookmark_id,
        tb1_0.folder_cnt,
        tb1_0.member_id,
        tb1_0.status,
        tb1_0.term_id 
    from
        term_bookmark tb1_0 
    where
        tb1_0.term_id=? 
        and tb1_0.member_id=?
2024-02-08T18:48:51.126+09:00 TRACE 69831 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:BIGINT) <- [2]
2024-02-08T18:48:51.126+09:00 TRACE 69831 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (2:BIGINT) <- [1]
Hibernate: 
    insert 
    into
        term_bookmark
        (folder_cnt, member_id, status, term_id, term_bookmark_id) 
    values
        (?, ?, ?, ?, default)

 

쿼리가 총 4회 발생한다.

1. Term 엔티티를 SELECT 한다.  -- findById()

2. Member 엔티티를 SELECT 한다. -- findById()

3. TermBookmark 엔티티를 SELECT 한다. 

4. TermBookmark 엔티티를 INSERT 한다.

 

이 때, 4번 쿼리를 날리기 위해서, 1번, 2번 쿼리를 날려 Member 엔티티와 Term 엔티티를 불러오고 있다. 

그런데, 코드 상에서 이미 memberId와 termId 를 알고 있고, 3번 쿼리를 날릴 때는 memberId와 termId 만 필요한데, 굳이 1번과 2번 쿼리를 날릴 필요가 있을까?

 

JPA에서 직접 연관 관계의 엔티티를 조회하지 않고 엔티티를 저장하는 방식은??

 

Term termPS = termRepository.getReferenceById(requestDto.getTermId());
Member memberPS = memberRepository.getReferenceById(memberId);

// 북마크 테이블을 업데이트 합니다.
Optional<TermBookmark> termBookmarkOptional = termBookmarkRepository.findByTermAndMember(termPS, memberPS);

if (termBookmarkOptional.isEmpty()){
    termBookmarkRepository.save(TermBookmark.of(termPS, memberPS));
}else{
    termBookmarkOptional.get().addFolderCnt();
}
Hibernate: 
    select
        tb1_0.term_bookmark_id,
        tb1_0.folder_cnt,
        tb1_0.member_id,
        tb1_0.status,
        tb1_0.term_id 
    from
        term_bookmark tb1_0 
    where
        tb1_0.term_id=? 
        and tb1_0.member_id=?
2024-02-08T18:58:37.517+09:00 TRACE 69940 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:BIGINT) <- [2]
2024-02-08T18:58:37.517+09:00 TRACE 69940 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter (2:BIGINT) <- [1]
Hibernate: 
    insert 
    into
        term_bookmark
        (folder_cnt, member_id, status, term_id, term_bookmark_id) 
    values
        (?, ?, ?, ?, default)

 

쿼리가 단 2회만 발생하고 있다. 

즉, 위에서 1번, 2번 쿼리가 발생하지 않았다.

 

XxxRepository.getReferenceById(id);

findById() 는 eager 방식의 조회기법이라면, getReferenceById() 는 lazy 방식으로 조회된다. 

getReferenceById() 는 주어진 식별자를 가지고 있는 엔티티의 참조를 반환한다.

 

만약, 식별자가 DB 에 존재하지 않는다면, EntityNotFoundException 이 발생한다. 
try-catch 문을 이용하여 적절히 예외처리를 해주어야 한다

 

어 근데 왜 예외가 안 잡히지..?

Term termPS;
try {
    termPS = termRepository.getReferenceById(requestDto.getTermId());
}catch (EntityNotFoundException e){
    throw new CustomApiException("용어가 존재하지 않습니다.");
}

이렇게 했는데 EntityNotFoundException을 못잡는다.....?

 

 

Referential integrity constraint violation: "FK55L2J14KR3176XLXRT5Y1QHRI: PUBLIC.TERM_BOOKMARK FOREIGN KEY(TERM_ID) REFERENCES PUBLIC.TERM(TERM_ID) (CAST(1223 AS BIGINT))"; SQL statement: insert into term_bookmark (folder_cnt,member_id,status,term_id,term_bookmark_id) values (?,?,?,?,default)

 

당연하지요.

Lazy 방식으로 조회하기 때문에, Exception 이 저 코드에서 발생하지 않는다.

저 코드를 지나갈때는 있건 없건 SQL 이 발생하지 않는다. 

그렇다면 언제 예외가 발생하느냐? 바로 termPS 를 비로소 사용할 때!

 

// 실제로 termPS 를 사용하여 예외가 발생할 부분!!!
termBookmarkRepository.save(TermBookmark.of(termPS, memberPS));

 

그렇다면 위 코드의 맨 아랫줄에 try-catch 문을 감싸주면 에러를 잘 처리해주지 않겠는가?

 

try {
    termBookmarkRepository.save(TermBookmark.of(termPS, memberPS));
}catch (DataIntegrityViolationException e){
    throw new CustomApiException("존재하지 않습니다.");
}
Body = {"status":-1,"message":"존재하지 않습니다.","data":null}

 

이제 예외처리를 잘 잡아준다. 

 

위 코드에서는 DataIntegrityVioldationException 이 발생한다. 

termPS 의 요소를 조회하여 본격적인 SELECT 문이 날아갈 때는 EntityNotFoundException 이 발생할 테지만, 지금은 FK 관련 에러이므로, DataIntegrityVioldationException 을 잡아줘야 한다. 

 

FK의 제약 조건을 다음과 같이 풀어주면, 식별자가 존재하지 않아도 DB 에 저장된다. 

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
private Term term;