본문 바로가기

Java/JPA

[JPA] JPA_existBy~ / Querydsl 성능비교 (+ fetchOne(), fetchFirst())

 

* 아이디 중복확인 체크로직 

두가지 방법으로 실행속도 비교

1. JPA Repository 메소드 사용

2. QueryDSL 사용

 


 

1. JPA  Repository _ existBy~ 메소드

 

* UserRepository

public interface UserRepository extends JpaRepository<User> {
	    
        boolean existsByUserIdAndCode(String userId, String code);

}

 

* UserService

// 아이디 중복확인 체크 로직
// param: String userId, String code

if(UserRepository.existsByUserIdAndCode(userId, code)){
        throw new CustomException(CustomErrorCode.ID_DUPLICATE);
}

=> existsByUserIdAndCode 메소드 : parameter로 받은 userId와 code가 db에 있는지 존재여부를 boolean으로 반환.

     true  ===>   "ID_DUPLICATE" 에러

 

대부분 4~5 msec 정도.


 

2. QueryDSL  _ selectOne(), fetchFirst()

 

* UserService

private final EntityManager em;

// ... 생략
// 아이디 중복확인 체크 로직
// param: String userId, String code

JPAQueryFactory queryFactory = new JPAQueryFactory(em);

QUser userInfo = QUser.userInfo;
Integer isExist = queryFactory.selectOne()
        .from(userInfo)
        .where(userInfo.userId.eq(userId)
                .and(userInfo.code.eq(code)))
        .fetchFirst();

if(isExist != null) {
	throw new CustomException(CustomErrorCode.ID_DUPLICATE);
}

=> selectOne 쿼리 : parameter로 받은 userId와 code가 db에 있는지 존재여부를 boolean으로 반환. (isExist)

     true  ===>   "ID_DUPLICATE" 에러

 

대부분 4~5 msec 정도이나 5 msec 이상으로 넘어가는 경우도 있었다.

 


 

두 경우 모두 select limit1 쿼리를 실행하지만 내부적으로 차이가 있다고 한다.

 

1번의 existsBy... 메소드는 데이터의 존재여부만 확인 후 존재하면 즉시 1을 반환 (없는경우 반환X)

2번의 fetchFirst() 는 조건을 만족하는 결과 중 첫번째 항목을 반환. (없는경우 null 반환.)

' limit 1 ' 쿼리를 수행해서 제한을 하지만 db에서 결과를 가져와 해당 데이터 타입으로 매핑하는 작업이 포함되어 있음!!

 

테스트 결과 db에 데이터가 많지 않아서 그런지 큰 차이는 없었지만

단순히 존재여부만을 확인하고자 한다면 1번방법이 더 효율적인듯 하다.

 


 

* QueryDSL  관련 추가

결과로 반환되는 데이터(매핑된 객체)를 활용할 필요가 없다면

.fetchFirst() != null 로 처리해 결과를 boolean으로 받아도 된다. (하지만 객체를 활용할 이유로 fetchFirst()를 쓰지 않을까)

private final EntityManager em;

// ... 생략
// 아이디 중복확인 체크 로직
// param: String userId, String code

JPAQueryFactory queryFactory = new JPAQueryFactory(em);

QUser userInfo = QUser.userInfo;
boolean isExist = queryFactory.selectOne()
        .from(userInfo)
        .where(userInfo.userId.eq(userId)
                .and(userInfo.code.eq(code)))
        .fetchFirst() != null;

if(isExist) {
	throw new CustomException(CustomErrorCode.ID_DUPLICATE);
}

=> 조건에 만족하는 반환데이터의 null여부를 쿼리에서 체크하고 boolean으로 반환. (isExist)

     true  ===>   "ID_DUPLICATE" 에러

 


 

* QueryDSL  _  count(), fetchOne()

 

결과로 반환되는 데이터의 count값이 필요하다면

private final EntityManager em;

// ... 생략
// 아이디 중복확인 체크 로직
// param: String userId, String code

JPAQueryFactory queryFactory = new JPAQueryFactory(em);

QUser userInfo = QUser.userInfo;
long count = queryFactory.select(userInfo.count())
        .from(userInfo)
        .where(userInfo.userId.eq(userId)
                .and(userInfo.code.eq(code)))
        .fetchOne();

if (count > 0) {
    throw new CustomException(CustomErrorCode.ID_DUPLICATE);
}

=> 조건에 만족하는 테이블의 갯수를 long타입으로 반환. (count)

     count > 0  ===>   "ID_DUPLICATE" 에러

 


 

* count  vs  exist * 

 count는 db에서 결과데이터의 갯수를 가져오기 때문에 데이터를 객체로 매핑하는 작업이 없지만,

테이블 전체를 탐색하기 때문에 exist보다는 시간이 오래걸릴 수 있음! 

필요한 정보가 결과데이터인지 결과데이터 존재여부인지에 따라 적절한 메소드 선택하기!!

 

 


 

* fetchOne(),  fetchFirst() :  QueryDSL 에서 데이터를 조회할 때 쿼리의 결과를 반환하는 메소드.

 

- fetchOne() 

단 하나의 결과만을 반환 (결과가 하나 이상이면 NonUniqueResultException, 없으면 null)

=> 결과가 없거나 정확이 하나임이 보장될 때.

 

- fetchFirst()

첫 번째 결과만을 반환 (추가적인 결과는 무시, 결과가 없으면 null)

> 내부적으로  limit(1).fetchOne() 을 수행. (결과를 단건으로 치환 후 반환하는 방식) 

=> 정렬된 데이터 중 조건에 만족하는 최상위 데이터가 필요할 때.