오늘 진행한 학습 요약
1. JPA 심화 5주차 ( SpringData JPA 기본 )
- 좀 더 멋지게 쿼리 생성하기 (QueryDSL)
- 테이블 객체 방명록 설정하기 (Auditing)
- 필요한 부분만 갱신하기 (Dynamic Insert/Update)
- 필요한 부분만 효율적으로 조회하기 (Projection)
2. JPA QueryDSL 사용하는 법
- QueryDSL 사용법
- QueryDSL의 장점
- QueryDSL의 단점
- QueryDSL을 사용할 때 적합한 상황
- 주요 메서드
- 실제 사용해보기
학습 정리
1. JPA 심화 5주차 ( SpringData JPA 기본 )
좀 더 멋지게 쿼리 생성하기 (QueryDSL)
- QueryDSL 사용법
- QueryDSL이란?
- 보통 데이터베이스에서 데이터를 가져올 때 SQL 같은 쿼리를 사용합니다. 하지만 SQL은 문자열로 작성되다 보니 오타가 나거나 실행 중에만 오류가 확인되는 문제가 있습니다.
- QueryDSL은 SQL 대신 자바 객체를 이용해 쿼리를 작성할 수 있도록 도와주는 기술입니다.
- Q클래스: 엔티티 정보를 이용해 쿼리를 쉽게 작성할 수 있도록 만든 전용 클래스.
- 왜 사용하나요?
- SQL처럼 복잡한 쿼리를 작성할 수 있으면서도, 문법 오류를 컴파일 시점에 잡을 수 있습니다.
- 읽기 쉽고 유지보수가 편리합니다.
- 적용 방법
- 설정 파일 작성:
- JPAQueryFactory를 Bean으로 등록하여 사용할 준비를 합니다.
-
@Configuration public class JPAConfiguration { @PersistenceContext private EntityManager entityManager; @Bean public JPAQueryFactory jpaQueryFactory() { return new JPAQueryFactory(entityManager); } }
- Q클래스 활용
- 생성된 Q클래스를 사용하여 데이터를 조회합니다.
-
JPAQueryFactory jqf = new JPAQueryFactory(em); QUser user = QUser.user; List<User> userList = jqf.selectFrom(user) .where(user.username.eq("name")) .fetch();
- 설정 파일 작성:
- 활용 예시
- Slack 멘션된 쓰레드 조회
-
- 특정 사용자가 멘션된 채팅 데이터를 가져오고 싶을 때.
- 가져올 데이터: 채널 이름, 쓰레드 작성자, 본문, 댓글, 이모지 등.
- 정렬 기준: 멘션된 시간 순서대로 정렬.
-
- Slack 멘션된 쓰레드 조회
- QueryDSL이란?
테이블 객체 방명록 설정하기 (Auditing)
- Auditing이란?
- 데이터를 다룰 때 언제, 누가 데이터를 만들었고 수정했는지를 자동으로 기록하는 기능입니다.
- 마치 방명록처럼 누가 다녀갔는지 기록을 남길 수 있습니다.
- 주요 기능
- 시간 기록: 데이터가 생성된 시점과 수정된 시점을 자동으로 기록.
- @CreatedDate: 생성 시간.
- @LastModifiedDate: 수정 시간.
- 사용자 기록: 데이터를 생성/수정한 사람을 자동으로 기록.
- @CreatedBy: 데이터를 만든 사람.
- @LastModifiedBy: 데이터를 수정한 사람.
- 시간 기록: 데이터가 생성된 시점과 수정된 시점을 자동으로 기록.
- 적용 방법
- 프로젝트에 @EnableJpaAuditing를 추가하여 Auditing 기능 활성화.
- 엔티티 클래스에 @EntityListeners(AuditingEntityListener.class) 추가.
- 인증된 사용자 정보를 반환하는 AuditorAware를 구현
-
@Service public class UserAuditorAware implements AuditorAware<User> { @Override public Optional<User> getCurrentAuditor() { return Optional.of(authenticatedUser); } }
-
필요한 부분만 갱신하기 (Dynamic Insert/Update)
- Dynamic Insert
- 보통 데이터를 데이터베이스에 추가할 때, 모든 필드를 포함해서 Insert 쿼리가 만들어집니다.
- 하지만 Dynamic Insert를 사용하면, null인 값은 제외하고 필요한 데이터만 추가할 수 있습니다.
- 효과: 불필요한 데이터 처리를 줄여 성능을 높입니다.
- 적용 방법: 엔티티 클래스에 @DynamicInsert 어노테이션 추가.
- Dynamic Update
- 데이터를 수정할 때도, 모든 필드를 업데이트하는 대신 변경된 데이터만 처리합니다.
- 효과: 수정이 필요한 데이터만 업데이트해 성능을 최적화.
- 적용 방법: 엔티티 클래스에 @DynamicUpdate 어노테이션 추가.
- 적용 전/후 비교
- 적용 전: 모든 필드가 포함된 쿼리가 실행됩니다.
- 적용 후: 변경된 필드만 포함된 쿼리가 실행됩니다.
-
-- 적용 전: UPDATE users SET username=?, password=? WHERE id=?; -- 적용 후: UPDATE users SET password=? WHERE id=?;
-
필요한 부분만 효율적으로 조회하기 (Projection)
- Projection이란?
- 데이터베이스에서 모든 데이터를 가져오는 것이 아니라, 필요한 데이터만 선택적으로 가져오는 기술.
- 예를 들어, 사용자 정보에서 이름과 이메일만 필요할 때, 해당 데이터만 가져와서 성능을 최적화할 수 있습니다.
- 유형
- 인터페이스 기반: 인터페이스를 통해 필요한 데이터를 가져옵니다.
- 클래스 기반: 특정 클래스를 생성해 필요한 데이터를 조회합니다.
2. JPA QueryDSL 사용하는 법
QueryDSL 사용법
- QueryDSL 사용하는 법
- QueryDSL은 Java에서 타입 세이프(Typed-Safe)하고 동적 쿼리를 작성하기 위한 프레임워크입니다. 주로 JPA와 함께 사용하며, JPQL이나 SQL 쿼리를 코드로 작성할 수 있습니다.
- 1. 설정 방법
- Gradle 의존성 추가
-
implementation 'com.querydsl:querydsl-jpa:5.x.x' annotationProcessor 'com.querydsl:querydsl-apt:5.x.x:jpa'
-
- Q클래스 생성
- @Entity로 선언된 엔티티 클래스의 Q클래스를 생성하려면 querydsl-apt를 사용해 컴파일 시 자동 생성
- IntelliJ: Preferences > Annotation Processors > Enable annotation processing 활성화
- 명령어로 생성
-
./gradlew clean compileQuerydsl
- Gradle 의존성 추가
- 2. 사용 방법
- 1) 기본적인 QueryDSL 사용
-
QUser user = QUser.user; List<User> users = queryFactory.selectFrom(user) .where(user.name.eq("John")) .fetch();
-
- 2) 동적 쿼리 작성
-
BooleanBuilder builder = new BooleanBuilder(); if (name != null) { builder.and(user.name.eq(name)); } if (age != null) { builder.and(user.age.goe(age)); } List<User> result = queryFactory.selectFrom(user) .where(builder) .fetch();
-
- 3) 조인
-
QOrder order = QOrder.order; QUser user = QUser.user; List<Tuple> results = queryFactory.select(order.id, user.name) .from(order) .join(order.user, user) .where(user.name.eq("John")) .fetch();
-
- 4) 페이징 및 정렬
-
List<User> users = queryFactory.selectFrom(user) .orderBy(user.age.desc()) .offset(0) // 시작 위치 .limit(10) // 최대 결과 수 .fetch();
-
- 5) 서브쿼리
-
QOrder order = QOrder.order; JPQLQuery<Double> subQuery = JPAExpressions.select(order.price.avg()) .from(order); List<User> result = queryFactory.selectFrom(user) .where(user.age.gt(subQuery)) .fetch();
-
- 1) 기본적인 QueryDSL 사용
QueryDSL의 장점
- 1. 타입 세이프 (Type-Safe)
- 컴파일 시점에 쿼리의 정확성을 보장합니다.
- JPQL에서 문자열로 작성한 쿼리는 런타임에 오류가 발생할 수 있으나, QueryDSL은 이를 방지합니다.
- 2. 가독성 향상
- 쿼리를 코드처럼 작성할 수 있어 가독성이 높습니다.
- 코드에 IDE의 자동 완성을 사용할 수 있습니다.
- 3. 동적 쿼리 작성 용이
- BooleanBuilder와 같은 기능을 사용해 동적 조건을 간편하게 추가할 수 있습니다.
- 4. JPA, SQL 모두 지원
- JPQL 뿐만 아니라 네이티브 SQL도 지원합니다.
- 5. 모듈 간 의존성 감소
- JPA 엔티티를 기반으로 쿼리를 작성하기 때문에 SQL 쿼리문이 변경되어도 코드의 수정이 최소화됩니다.
QueryDSL의 단점
- 1. 러닝 커브
- 초반 학습이 필요합니다. 특히, JPQL을 처음 사용하는 사람에게 QueryDSL의 문법은 복잡하게 느껴질 수 있습니다.
- 2. Q 클래스 의존
- QueryDSL은 자동 생성된 Q 클래스를 사용해야 하므로, 이를 잘못 관리하면 프로젝트 구조가 복잡해질 수 있습니다.
- 3. 코드 증가
- 간단한 쿼리도 코드가 길어질 수 있습니다. (e.g., 단순 조회 쿼리)
- 4. 추가적인 빌드 단계
- Q 클래스 생성을 위해 추가적인 빌드 단계가 필요합니다. (예: compileQuerydsl)
- 5. SQL 쿼리에 비해 성능 최적화가 어려움
- JPQL 기반으로 동작하기 때문에 복잡한 SQL을 최적화하기에는 한계가 있을 수 있습니다.
QueryDSL을 사용할 때 적합한 상황
- 동적 쿼리가 많을 때
- 조건이 다양하고 동적으로 변경되는 쿼리 작성이 필요한 경우 적합합니다.
- 타입 세이프가 중요한 프로젝트
- 컴파일 시점에 쿼리 오류를 발견해야 하는 경우.
- 가독성을 중시하는 팀
- 유지보수와 협업에서 쿼리의 가독성을 높이고 싶을 때.
주요 메서드
QueryDSL에서 사용하는 주요 메서드 | Notion
QueryDSL에서 사용하는 주요 메서드들은 크게 JPQL-like 메서드와 BooleanExpression 메서드로 나눌 수 있습니다. 각각의 메서드는 QueryDSL의 주요 기능을 수행하며, JPQL과 유사한 방식으로 조건을 생성하거
shinelee26.notion.site
실제 사용해보기
- ReservationRepository에 QueryDSL 사용을하기 위해 아래와 같이설정해야 한다.
- 1. JPAConfiguration 작성
- 2. ReservationRepositoryCustom -> Interface 생성
- 3. ReservationRepositoryCusomImpl -> 클래스를 생성 -> implements ReservationRepositoryCustom
- 4. ReservationRepository -> extends JpaRepository, ReservationRepositoryCustom
- 1. JPAConfiguration 코드 작성 내용
- 2. ReservationRepositoryCustom 코드 작성 내용
-
- 인터페이스로 커스텀 리포지토리 메서드를 정의한다.
- 이 인터페이스는 구현체(ReservationRepositoryCustomImpl)에 의해 구현된다!
- searchReservations: 특정 사용자(userId)와 아이템(itemId)을 기준으로 예약 정보를 검색
- searchReservationIdAndStatus: 예약 ID(reservationId)를 기준으로 예약 정보를 검색
-
- 3. ReservationRepositoryCusomImpl 코드 작성 내용
- QueryDSL을 사용하여 실제 구현을 제공한다.
- @RequiredArgsConstructor: final 필드인 jpaQueryFactory를 생성자 주입한다
- electFrom(reservation): Reservation 엔티티를 조회 대상으로 설정.
- leftJoin:
- 예약 엔티티(reservation)와 사용자(user), 아이템(item)을 LEFT JOIN
- fetchJoin: 연관된 엔티티를 한 번에 가져옴(지연 로딩 방지).
- fetchJoin으로 연관된 엔티티를 한번에 가져와서 N+1 문제를 방지하려고 했는데 다른 좋은 방법이 있는지 더 찾아봐야겠다..
- where: 조건을 지정.
- reservation.user.id.eq(userId): 사용자 ID가 userId와 일치하는 조건.
- reservation.item.id.eq(itemId): 아이템 ID가 itemId와 일치하는 조건.
- fetch(): 결과를 리스트로 반환.