
동적 쿼리 (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