환경설정
프로젝트생성
Intelli J가 코드 실행을 Gradle로 실행하도록 위임할 수 가 있음.
다시 Java로 실행하도록 위함
: 설정 > gradle 검색 > Build and run using 을 Gradle 변경
lombok 정상동작 세팅
: 설정 > lombok 검색 > Plugins > lombok 검색 후 설치 또는 start.spring.io에서 초기 설정
: annotation processors 검색 > Compiler > Annotation Processors 선택 > Enable annotation processing 체크
QueryDSL 설정과 거증
Gradle에 설정하고, 빌드 과정 속에서 Q타입이라는 파일을 뽑아낸다
> 초기 설정이 중요
build.gradle
스프링부트 2.x : querydsl 플러그인과 라이브러리 추가
스프링부트 3.x : 라이브러리 추가
Grandle의 Tasks > build에서 clean 후 build를 하면 디렉토리에서 build 밑에 Q타입이 생성된 것을 확인 가능
라이브러리 살펴보기
H2데이터베이스 설치
스프링 부트 설정 - JPA, DB
예제 도메인 모델
예제 도메인 모델과 동작 확인
@ToString(of = {"id", "username", "age"})
주의
> ManyToOne 관계에 있는 컬럼도 ToString으로 출력하면 무한 루프에 빠질 수 있음
기본 문법
시작 - JPQL vs Querydsl
public void startQuerydsl() {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QMember m = new QMember("m");
Member findMember = queryFactory
.select(m)
.from(m)
.where(m.username.eq("member1"))
.fetchOne();
JPQL처럼 where 조건에서 username을 따로 바인딩을 안해주어도 자동으로 파라미터 바인딩을 실행
JPQL에서는 SQL문을 문자로 만들기 때문에 문장 오류가 생길 경우 컴파일러 후 그 오류를 찾아내지만
Querydsl은 컴파일러 과정에서부터 오류를 잡아내기 때문에 매우 안정적으로 코딩이 가능
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
해당 내용을 Field로 빼서 사용 가능
기본 Q-Type 활용
기본 인스턴스를 static import를 사용하면 깔끔하게 코드 작성
Querydsl은 결국 JPQL로 빌드를 해준다
그렇다면 JPQL을 보려면?
> yml파일에 use_sql_comments: true 추가
기본 Alias가 아닌 사용자 정의로 설정할 때
> 테이블끼리 Join 할 때 같은 테이블명이 되지 않게 할 때 사용
검색 조건 쿼리
결과 조회
정렬
페이징
집합
집합함수
select(member.count(),
member.age.sum(),
member.age.avg(),
member.age.max(),
member.age.min())
해당 내용들은 Tuple로 출력(Querydsl에서 제공하는 Tuple)
데이터 타입이 여러개일 경우 사용
실무에서는 Tuple보다는 DTO로 많이 사용
GroupBy사용
조인 - 기본 조인
세타 조인
연관 관계가 없어도 조인 가능 = 세타 조인
> 관계없는 테이블을 가져와 모두 조인을 맺어버리는 것
외부 조인 불가능
> Left Join, Right Join이 불가능하다는 소리
> on절을 사용해야 외부 조인 가능
조인 - on절
조인 - 패치 조인
테스트 증명
@PersistenceUnit
EntityManagerFactory
: Entity Manager를 만드는 팩토리
getPersistenceUnitUtil().isLoaded(Entity)
불러온 Entity가 이미 로딩된 Entity인지 아직 로딩(초기화))이 안된 Entity인지 알려줌
서브 쿼리
Alias가 중복되면 안되기 때문에 QType 직접 생성(SQL문법과 유사)
from절의 서브쿼리 지원이 안됨
한번에 원하는 데이터를 가져오려고 하기 때문에 Query 줄이 길어지는 경우가 대부분.
DB는 데이터를 저장하고 가져오는 곳이다. 화면에 보여주기 위한 DTO를 출력하기 위해 간단한 Query를 복잡하게 작성하는 건 아닌지 생각할 필요가 있다.
(책. SQL Antipartten)
최소한의 Filtering과 Grouping을 해서 데이터를 줄이고, 실제 원하는 데이터 커스텀은 로직으로 해결
Case 문
최소한의 Filtering과 Grouping을 해서 데이터를 줄이고, 실제 원하는 데이터 커스텀은 로직으로 해결
상수, 문자 더하기
문자형과 숫자형을 연결하려면 둘다 문자형으로 변환 후 연결
ex)
//{username}_{age}
List<String> result = queryFactory
.select(member.username.concat("_").concat(member.age.stringValue()))
.from(member)
.fetch();
중급문법
프로젝션과 결과 반환 - 기본
쿼리 호출 후 결과가 리스트 형태로 String이나 Object 형태라면 List<>안에 그 결과 타입을 넣으면 되지만, 둘 이상의 결과 타입일 경우 Tuple이나 DTO로 설정 후 조회해야 한다.
> 한번에 여러개를 받을 수 있는 형태로 튜플 사용
튜플을 사용하는 조회는 Repository나 DAO 계층에서만 사용
> 하부 구현 기술을 QueryDSL에서 다른 걸 바꾸더라도 앞단 Controller나 서비스를 바꿀 필요가 없도록
Repository에서 정리한 후 바깥으로 데이터가 나갈때는 DTO로 변환해서 나가는 걸 추천
프로젝션과 결과 반환 - DTO 조회
방법 1프로퍼티 접근_setter
Projections.bean 사용
DTO Entity에서 선언한 @Data는 Getter, Setter가 자동으로 생성
Projections.bean를 사용하여 @Data로 만든 Setter를 이용하여 값이 들어감
방법 2 필드 직접 접근
Getter, Setter 무시하고 값이 바로 필드에 들어감
> Entity가 private여도 라이브러리를 통해 방법이 다 있음
뽑아내는 필드의 별칭이 다를 때
> 불러오는 대상에 연쇄체인으로 .as(“필드명”) 을 붙인다
서브쿼리 대상은 ExpressionUtile.as 사용
프로젝션과 결과 반환 - @QueryProjection
생성자에 @QueryProjection 어노테이션 입력 후 컴파일
> DTO가 Q타입으로 생성
잘못된 코드를 작성 할 경우 컴파일 전에 오류를 표시
Projections.constructor
와 차이: 출력할 수 있는 컬럼외의 다른 컬럼을 넣거나, 오류의 코드를 작성할 경우 컴파일 후 실제 사용자가 코드를 실행해야 문제가 있다는 것을 감지
@QueryProjection를 사용하는 방식은 QueryDSL에 의존적
아키텍처 문제로 QueryProjection를 뺄 경우 사용하는 모든 곳에서 오류가 발생
동적 쿼리 - BooleanBuilder 사용
인스턴스 생성 시 초기값 설정 가능
동적 쿼리 - Where 다중 파라미터 사용
분리한 메서드를 조합하여 재활용 가능
최종 쿼리의 가독성이 높아짐
수정, 삭제 벌크 연산
bulk 주의사항
JPA는 기본적으로 영속성 컨텍스트에 Entity가 올라가 있다
bulk 연산은 영속성 컨텍스트를 무시하고 바로 DB에 쿼리가 나가기 때문에 bulk 연산 후의 데이터와 영속성 컨텍스트에 있는 데이터 차이가 다르다.
조회를 할 때 영속성 컨텍스트에 Entity가 있으면 이곳에서 조회하기 때문에 bulk로 연산된 데이터를 볼 수 없다
그러므로, bulk 연산 후의 데이터를 가져오려면 flush()와 clear()를 습관화 해야한다(초기화)
SQL function 호출하기
등록된 Dialect를 보려면
: cmd + o > H2Dialect 검색 후 라이브러리 선택
실무 활용 - 순수 JPA와 Querydsl
순수 JPA 리포지토리와 Querydsl
동적 Query와성능 최적화 조회 - Builder 사용
한 번에 DTO로 조회하는 것
> @QueryProjection 을 사용하면 해당 DTO가 Querydsl을 의존하게 된다. 이런 의존이 싫으면, 해당
어노테이션을 제거하고, Projection.bean(), fields(), constructor() 을 사용
검색 조건에서 문자열이 null이나 “”가 들어올 수 있다
> 해당 조건을 if문에서는 StringUtils.hasText(condition.get조건)을 사용하여 간소화 한다.
동적 Query와성능 최적화 조회 - where절 파라미터 사용
where 절에 파라미터 방식을 사용하면 where절을 위해 만든 메서드 재사용 가능
조회 API 컨트롤러 개발
프로파일을 나누어 main과 test용으로 구분
.yml 파일 > profiles 하단 > active를 만들어 구분
Ex) local, 개발서버(dev), 운영서버(real)로 구분
test디렉토리의 yml파일 위치 : test>resources(새로 생성)>application.yml 파일 생성

초기화용 Data Class 파일에
@Profile(“local”) 어노테이션을 사용하여 지칭한 .yml파일을 연결
샘플 데이터 생성 메서드 생성 > final(static 형태로) 등록 > 메서드.init()을 실행할 메서드 생성 및 @PostConstruct 어노테이션 연결
> 샘플 데이터 생성 로직을 처음부터 @PostConstruct 메서드에 넣으면 안될까?
> 스프링 라이프 사이클에 의해 실행을 위한 @PostConstruct와 DB에 데이터를 넣는 @Transactional 어노테이션이 동시에 사용 불가
실무 활용 - Spring Data JPA와 Querydsl
스프링 데이터 JPA 리포지토리로 변경
JpaRepository의 기본 메서드를 사용하거나
간단한 기능의 경우 스프링 데이터 JPA가 메서드 이름으로 자동으로 jpql을 만드는 기능 사용
사용자 정의 리포지토리
사용자용 리포지토리 Interface 생성 및 Interface를 상속(implements)받을 Class 생성
> 상속받는 Class는 오리지널RepositoryImpl 형태의 이름으로 생성(규칙)
> 스프링 데이터 JPA 특징을 살린 설계

스프링 데이터 페이징 활용 1 - Querydsl 페이징 연동
일반 페이징 로직
public Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable) {
QueryResults<MemberTeamDto> results = queryFactory
.select(new QMemberTeamDto(
member.id.as("memberId"),
member.username,
member.age,
team.id.as("teamId"),
team.name.as("teamName")))
.from(member)
.leftJoin(member.team, team)
.where(
usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetchResults();
List<MemberTeamDto> content = results.getResults();
long total = results.getTotal();
return new PageImpl<>(content, pageable, total);
}
데이터의 내용과 전체 카운트를 별도로 요청하는 쿼리
> content쿼리와 전체 카운트 쿼리를 분리
컨텐츠 쿼리는 복잡하지만 전체 카운트 쿼리는 쉬운 경우가 있다.
컨텐츠 내용 + 전체 카운트 등을 해주는 leftJoin()을 한 후 fetchResults()를 할 경우 최적화가 안된다.
복잡한 쿼리를 사용하지 않는 CountQuery의 경우 별도로 분리하여 최적화를 시키는 것이 가능하다.
스프링 데이터 페이징 활용 2 - Count Query 최적화
return PageableExecutionUtils.getPage(content, pageable, () -> countQuery.fetchCount());
getPage()에서 컨텐츠와 pageable의 total size를 보고
> 시작페이지 이며 컨텐츠 사이즈가 페이지의 사이즈보다 적거나
> 마지막 페이지 일 경우
= .fetchCount() 메서드 호출을 안함
스프링 데이터 페이징 활용 3 - 컨트롤러 개발
스프링 데이터 JPA가 제공하는 Querydsl 기능
제약이 너무 커서 복잡한 실무 환경에서는 사용하기 부족한 기능들이지만
간단한 기능 파악과 왜 부족한지 확인
> 테이블이 다수이고 Join 기능이 들어갈 경우 작동이 안됨
인터페이스 지원 - QuerydslPredicateExecutor
인터페이스에 QuerydslPredicateExecutor<Entity> 추가하여 제공하는 기능 사용 가능
한계
RDB형태의 데이터베이스를 사용하는 이상 기본적으로 Left Join이 안되는 경우 사용하기가 어렵다.
클라이언트 코드가 Querydsl에 의존하게 된다
Querydsl Web 지원
단순한 조건만 가능
조건을 커스텀하는 기능이 복잡하고 명시적이지 않음
컨트롤러가 Querydsl에 의존
복잡한 실무환경에서 사용하기에는 한계가 명확
리포지토리 지원 - QuerydslRepositorySupport
Querydsl 지원 클래스 직접 만들기
위 단점들을 극복하기 위한 지원 클래스 만들기
'끄적끄적 > Spring Boot' 카테고리의 다른 글
| 개인프로젝트_서버 구상 (0) | 2024.08.11 |
|---|---|
| Spring Boot Data JPA (0) | 2024.07.13 |
| Spring Boot 프로젝트 필기 (0) | 2024.06.29 |
| Spring Boot 강의 필기 (0) | 2024.06.14 |