Home Findbyid vs getreferencebyid
Post
Cancel

Findbyid vs getreferencebyid

findById() vs getReferenceById()

findById()EAGER방식의 조회기법이라면 getReferenceById(ID)LAZY방식으로 조회된다.

getReferenceById(ID) 는 실제 테이블을 조회하는 대신 프록시 객체만 가져온다.

프록시 객체만 있는 경우 ID 값을 제외한 나머지 값을 사용하기 전까지는 실제 DB 에 액세스 하지 않기 때문에
SELECT 쿼리가 날아가지 않는다.

findById()를 사용하면 DB 에 바로 액세스해서 데이터를 가져온다.

실제 DB 에 접근하냐 하지 않냐는 성능에 영향이 갈 수 있다.

단순히 특정 엔티티의 ID 값만 필요한 경우에는 모든 데이터를 가져올 필요가 없다.

연관 관계를 갖는 엔티티를 저장할 때, 연관된 엔티티 조회시 getReferenceById(ID)를 사용하는 것이 성능 개선에 도움이 된다.

*상황에 따라 적절한 메소드를 사용하면 된다.




test

Comment와 Registry로 test를 해봤다. (N:1)


Comment → Registry 가 LAZY 관계이면

Comment 테이블을 조회하는 시점에 registryId가 이미 FK로 Comment 테이블의 값에 포함되어 있다.

JPA는 이 값으로 Registry의 프록시 객체를 만들기 때문에 프록시 객체는 내부에 이미 registryId를 가지고 있다.

따라서 이 경우 registryId를 조회할 때는 프록시를 초기화 하지 않는다.



findById()

1
2
3
4
System.out.println(" = = = = = = = = = = == = = = = = = = = = = ");
System.out.println("findById");
System.out.println(registryRepository.findById(commentDto.getRegistryIdx()));
System.out.println();

image

findById()를 사용하니 select 쿼리가 떴다.



getReferenceById()

1
2
3
4
5
6
System.out.println(" = = = = = = = = = = == = = = = = = = = = = ");
System.out.println();
System.out.println("getReferenceById");
System.out.println(registryRepository.getReferenceById(commentDto.getRegistryIdx()));
System.out.println();
System.out.println("  == = = = = = = = == = = = = =  끝 = = = = = = = == = = = =  ");

image

getReferenceById()를 사용하니 실제로 SELECT 쿼리가 날아가지 않는다.



전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Comment setComment(CommentDto commentDto) {
    System.out.println("쀼뷰뷰뷰ㅠ븁");
    Registry registry = registryRepository.getReferenceById(commentDto.getRegistryIdx());
    System.out.println();
    
    
    System.out.println(" = = = = = = = = = = == = = = = = = = = = = ");
    System.out.println("findById");
    System.out.println(registryRepository.findById(commentDto.getRegistryIdx()));
    System.out.println();
    
    
    System.out.println(" = = = = = = = = = = == = = = = = = = = = = ");
    System.out.println();
    System.out.println("getReferenceById");
    System.out.println(registryRepository.getReferenceById(commentDto.getRegistryIdx()));
    System.out.println();
    System.out.println("  == = = = = = = = == = = = = =  끝 = = = = = = = == = = = =  ");
    
    Comment comment = commentDto.toEntity(registry);
    Comment save = commentRepository.save(comment);
    return save;
}

image




예외 처리

Negative Test를 작성하면서 getReferenceById()에 관한 예외 처리를 생각하게 되었다.


CommentService.java

1
2
3
4
5
6
7
8
@Override
public Comment postComment(CommentRequestDto commentDto) {
    Registry registry = registryRepository.getReferenceById(commentDto.getRegistryIdx());
    User user = userRepository.findByNickname(commentDto.getNickname()).orElseThrow(UserNotFoundException::new);
    Comment comment = commentDto.toEntity(registry, user);
    Comment save = commentRepository.save(comment);
    return save;
}

본 코드에서 findByNickname만 사용하는게 아니라 그 위 코드 getReferenceById로도 값을 찾는다.

userRepository.findByNickname()에 exception 처리가 되어있지만

registryRepository.getReferenceById()에는 exception 처리가 되어있지 않다.

그래서 문득 예외상황에 대한 test 코드를 작성하면서 registry의 idx값도 존재하지 않을 수도 있을텐데? 싶었다.



getReferenceById??

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public Comment postComment(CommentRequestDto commentDto) {
    System.out.println(" = = = = = = = = = = =  postComment 시작  = = = = = = = = = = =  ");

    Registry registry = registryRepository.getReferenceById(commentDto.getRegistryIdx());
    System.out.println("registry.getIdx() : " + registry.getIdx());

    System.out.println(" = = = = = = registry = = = = = = = = ");
    System.out.println("registry :  " + registry);

    System.out.println(" = = = = = = registryRepository.existsById = = = = = = = = = ");
    registryRepository.existsById(commentDto.getRegistryIdx());

    System.out.println(" = = = = = = = = = = = = = = = = = = = = = = ");
}

getReferenceById()를 사용하면 실제 테이블을 조회하는 대신 프록시 객체만 가져온다.

프록시 객체만 있는 경우 ID 값을 제외한 나머지 값을 사용하기 전까지는 실제 DB 에 액세스 하지 않기 때문에 SELECT 쿼리가 날아가지 않는다.

그렇기 때문에 registry.getIdx()에서는 쿼리문이 로그에 찍히지 않지만

registry 자체로 출력할 때는 로그에 select문이 찍히는 것을 볼 수 있다. (*existsById는 참고로 출력해봤다.)


image



그러므로 id 값만 사용할 것이라면 findById()보다는 getReferenceById(ID)를 쓰는게 쿼리가 덜 날라가기 때문에 좋을 것이다.

image




그런데 문제가 있다.

id값이 있는지 없는지를 따지는게 아니라 그냥 그 값을 가져온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Comment postComment(CommentRequestDto commentDto) {
    System.out.println(" = = = = = = = = = = =  postComment 시작  = = = = = = = = = = =  ");
    
    Registry registry = registryRepository.getReferenceById(commentDto.getRegistryIdx());
    System.out.println("registry.getIdx() :  " + registry.getIdx());
    
    if(registry.getIdx() == null) {
        System.out.println("hello");
    }
    
    User user = userRepository.findByNickname(commentDto.getNickname()).orElseThrow(UserNotFoundException::new);
    Comment comment = commentDto.toEntity(registry, user);
    Comment save = commentRepository.save(comment);

    return save;
}

registry의 없는 idx값을 test하였으나 registry.getIdx() : 40로 출력이 되고 아래와 같이 에러가 떴다.

image




return

findById()

image

매개변수로 전달된 ID에 해당하는 entity를 반환하거나, 해당하는 entity가 없을 경우 Optional.empty()를 반환한다.

즉, 탐색 결과가 없더라도 내부에서 예외를 발생시키지 않는다.

Throws를 살펴보면, ID가 null일 경우엔 IllegalArgumentException이 발생할 수 있다.



getReferenceById()

image

Optional<T> 가 아닌 T가 반환값이다.

즉, 매개변수로 전달된 ID에 해당하는 entity를 반환하되, 없을 경우 내부에서 예외를 발생시킨다.

특정 대상이 존재하지 않을 때의 커스텀 예외를 XXNotFoundException이라고 할 수 있다.



예외 처리 코드 작성

*Custom Exception 정리 글

1
2
3
4
5
6
7
8
9
@RestControllerAdvice
public class CustomExceptionHandler {
  
    @ExceptionHandler(SQLException.class)
    public ResponseEntity<ErrorResponseEntity> handleSQLException(){
        return ErrorResponseEntity.toResponseEntity(ErrorCode.DATABASE_ERROR);
    }

}

@ExceptionHandler를 이용해 Controller에 예외 처리 메소드를 추가했다.

@ExceptionHandler 에 설정한 예외가 발생하면 handler가 실행된다. → SQLException 예외를 처리하기 위해 작성

🐣 @Controller, @RestController가 아닌 @Service 나 @Repository 가 적용된 Bean에서는 사용할 수 없다.


@ControllerAdvice@Controller 어노테이션이 있는 모든 곳에서의 예외를 잡을 수 있도록 해준다.

@ControllerAdvice 안에 있는 @ExceptionHandler는 모든 컨트롤러에서 발생하는 예외상황을 잡을 수 있다.

🐣 @ControllerAdvice + @ResponseBody@RestControllerAdvice : @ControllerAdvice + 객체를 반환할 수 있다.



위와 같이 작성하면 없는 id값을 가지고 조회할 때 위처럼 에러가 뜨는 것이 아니라 예외처리가 되어 아래와 같이 출력된다.

image




LAZY 오류

LAZY를 사용하다보면 아래와 같은 오류를 볼 수 있다.

No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer


LAZY 옵션은 필요할때 조회를 해오는 옵션인데

위 에러는 필요가 없으면 조회를 안해서 비어있는 객체를 serializer 하려고 해서 발생되는 문제이다.


대표적으로 3가지 해결방법이 있지만 3번을 추천한다. 나머지는 근본적인 해결방법이 아니다.
(*자세한 내용은 Proxy에서 다룰 예정이다.)

  1. application 파일에 spring.jackson.serialization.fail-on-empty-beans=false 설정해주기

  2. 오류가 나는 엔티티의 LAZY 설정을 EAGER로 바꿔주기

  3. 오류가 나는 컬럼에 @JsonIgnore를 설정해주기




getReferenceById version

버전에 따라서 getReferenceById()로 나타나지 않을 수 있다.

spring boot version 2.5 미만의 경우 getOne()을 사용하고

spring boot version 2.7 미만의 경우 getOne(ID) is deprecated 되고 getById(ID)로 대체 되었다.

그리고 spring boot version 2.7 이상 부터는 getById() 대신 getReferenceById(ID)로 대체 되었다.


spring boot version 2.7 미만

image



spring boot version 2.7 이상

image




reference
find vs get (네이밍 컨벤션과 JPA에서의 내부 동작 차이)
ExceptionHandler 와 ControllerAdvice

This post is licensed under CC BY 4.0 by the author.