inblog logo
|
LifeLog, DevLog
    JPA

    QueryDSL 정리 - 2

    동적 쿼리, 페이징 처리, 락
    KYJTHEYJ's avatar
    KYJTHEYJ
    Feb 24, 2026
    QueryDSL 정리 - 2
    Contents
    동적 쿼리 (Dynamic Query)BooleanBuilder, BooleanExpressionBooleanBuilderBooleanExpressionQueryDSL 에서의 페이징 처리PageImpl 전체를 반환하여 생성한 내역을 그대로?QueryDSL 에서의 Lock 설정

    동적 쿼리 (Dynamic Query)

    입력한 값에 따라 유연하게 생성되는 쿼리

    • 이름만 있다면 이름으로 조건 조회

    • 이메일도 있으면 이름 + 이메일이 조건으로 조회 …

    BooleanBuilder, BooleanExpression

    BooleanBuilder

    여러 조건을 추가하여 하나씩 쌓아가며 만드는 동적 쿼리 패턴

    @Repository
    @RequiredArgsConstructor
    public class UserRepositoryImpl implements UserRepository {
        private final JPAQueryFactory queryFactory;
        
    		@Transactional(readOnly = true)
    		@Override
    		public List<UserSearchResponse> searchUserByMultiCondition(UserSearchRequest request) {
    		    BooleanBuilder builder = new BooleanBuilder();
    		
    		    if (request.getUsername() != null && !request.getUsername().isBlank()) {
    		        builder.and(user.username.contains(request.getUsername()));
    		    }
    		
    		    if (request.getEmail() != null && !request.getEmail().isBlank()) {
    		        builder.and(user.email.endsWith(request.getEmail()));
    		    }
    		
    		    if (request.getRole() != null) {
    		        builder.and(user.roleEnum.eq(request.getRole()));
    		    }
    		
    		    return queryFactory
    		            .select(new QUserSearchResponse(
    		                    user.username,
    		                    user.email,
    		                    user.roleEnum))
    		            .from(user)
    		            .where(builder)
    		            .orderBy(user.username.asc())
    		            .fetch();
    		}
    }
    

    BooleanExpression

    여러 조건을 명시하여 꺼내 사용하는 동적 쿼리 패턴

    @Repository
    @RequiredArgsConstructor
    public class UserRepositoryImpl implements UserRepository {
        private final JPAQueryFactory queryFactory;
    	
    	@Transactional(readOnly = true)
    	@Override
    	public List<UserSearchResponse> searchUserByMultiConditionV2(UserSearchRequest request) {
    	    return queryFactory
    	            .select(new QUserSearchResponse(
    	                    user.username
    	                    , user.email
    	                    , user.roleEnum
    	            )).from(user)
    	            .where(
    	                    userNameContains(request.getUsername())
    	                    , userEmailContains(request.getEmail())
    	                    , userRoleEq(request.getRole())
    	            ).orderBy(user.username.asc())
    	            .fetch();
    	}
    	
    	private BooleanExpression userNameContains(String username) {
    	    return username != null ? user.username.contains(username) : null;
    	}
    	
    	private BooleanExpression userEmailContains(String email) {
    	    return email != null ? user.email.contains(email) : null;
    	}
    	
    	private BooleanExpression userRoleEq(UserRoleEnum role) {
    	    return role != null ? user.roleEnum.eq(role) : null;
    	}
    }
    

    복잡한 조절이 필요하면 BooleanBuilder 를, 재사용이 잦고 간단한 동적 제어가 필요하면 BooleanExpression 을 사용한다

    QueryDSL 에서의 페이징 처리

    컨트롤러 메서드에서 Pageable 파라미터를 가져간 후

    PageImpl 로 구현하는 전체 큰 방안은 동일하다

    // Controller
    @GetMapping("/search/page")
    public ResponseEntity<Page<UserSearchResponse>> searchUserByUsernameWithPage(UserSearchRequest request, Pageable pageable) {
        return ResponseEntity.ok(userService.searchUserListPage(request, pageable));
    }
    
    // Service
    @Transactional
    public Page<UserSearchResponse> searchUserListPage(UserSearchRequest request, Pageable pageable) {
        return userRepository.searchUserByMultiConditionPage(request, pageable);
    }
    
    // Repository Interface
    public interface UserRepository {
        Page<UserSearchResponse> searchUserByMultiConditionPage(UserSearchRequest request, Pageable pageable);
    }
    
    // Repository Implement
    @Repository
    @RequiredArgsConstructor
    public class UserRepositoryImpl implements UserRepository {
        private final JPAQueryFactory queryFactory;
    
        private BooleanExpression userNameContains(String username) {
            return username != null ? user.username.contains(username) : null;
        }
    
        private BooleanExpression userEmailContains(String email) {
            return email != null ? user.email.contains(email) : null;
        }
    
        private BooleanExpression userRoleEq(UserRoleEnum role) {
            return role != null ? user.roleEnum.eq(role) : null;
        }
    
        @Override
        public Page<UserSearchResponse> searchUserByMultiConditionPage(UserSearchRequest request, Pageable pageable) {
            // 실 데이터 값
            List<UserSearchResponse> result = queryFactory
                    .select(new QUserSearchResponse(
                            user.username
                            , user.email
                            , user.roleEnum
                    )).from(user)
                    .where(
                            userNameContains(request.getUsername())
                            , userEmailContains(request.getEmail())
                            , userRoleEq(request.getRole())
                    )
                    .offset(pageable.getOffset())
                    .limit(pageable.getPageSize())
                    .orderBy(user.username.asc())
                    .fetch();
    
            // 전체 데이터 갯수
            Long total = queryFactory
                    .select(user.count())
                    .from(user)
                    .where(
                            userNameContains(request.getUsername())
                            , userEmailContains(request.getEmail())
                            , userRoleEq(request.getRole())
                    ).fetchOne();
    
            // 전체 갯수가 null 인 경우는 방지
            if(total == null)
                total = 0L;
    
            // Page 객체로 변환 = PageImpl 객체 반환
            return new PageImpl<>(result, pageable, total); // 실 데이터 출력 값, 페이저블, 전체 데이터 갯수가 필요함
        }
    }
    

    PageImpl 전체를 반환하여 생성한 내역을 그대로?

    그대로 사용하는 경우도 있겠지만,
    대부분이 필요한 값만 추출하여 사용한 것으로 보인다

    먼저 페이지 공통 응답 클래스를 만들어 감싸 사용하면 그만이다

    public record PageResponse<T>(
        List<T> content,
        int currentPage,
        int totalPages,
        long totalElements,
        int size,
        boolean isLast
    ) {
        public static <T> PageResponse<T> register(Page<T> page) {
            return new PageResponse<>(
                page.getContent(),
                page.getNumber(),
                page.getTotalPages(),
                page.getTotalElements(),
                page.getSize(),
                page.isLast()
            );
        }
    }
    // Controller
    @GetMapping("/search/page")
    public ResponseEntity<PageResponse<UserSearchResponse>> searchUserByUsernameWithPage(UserSearchRequest request, Pageable pageable) {
        return ResponseEntity.ok(PageResponse.register(userService.searchUserListPage(request, pageable)));
    }
    

    QueryDSL 에서의 Lock 설정

    @Repository
    @RequiredArgsConstructor
    public class EntityRepositoryImpl implements EntityRepositoryCustom {
        
        private final JPAQueryFactory queryFactory;
        
        public Optional<Entity> findByIdWithLock(Long id) {
            return Optional.ofNullable(
                queryFactory
                    .selectFrom(entity)
                    .where(entity.id.eq(id))
                    .setLockMode(LockModeType.PESSIMISTIC_WRITE)
                    .fetchOne()
            );
        }
    }
    Share article
    Contents
    동적 쿼리 (Dynamic Query)BooleanBuilder, BooleanExpressionBooleanBuilderBooleanExpressionQueryDSL 에서의 페이징 처리PageImpl 전체를 반환하여 생성한 내역을 그대로?QueryDSL 에서의 Lock 설정

    LifeLog, DevLog - https://github.com/KYJTHEYJ

    RSS·Powered by Inblog