Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 자바의 정석 기초편 ch4
- 스프링 mvc2 - 타임리프
- 스프링 mvc2 - 로그인 처리
- jpa - 객체지향 쿼리 언어
- 스프링 mvc1 - 서블릿
- 자바의 정석 기초편 ch7
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch13
- 2024 정보처리기사 수제비 실기
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch3
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch9
- jpa 활용2 - api 개발 고급
- 스프링 mvc1 - 스프링 mvc
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch1
- 스프링 입문(무료)
- @Aspect
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch6
- 스프링 db1 - 스프링과 문제 해결
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch12
- 게시글 목록 api
- 자바 기본편 - 다형성
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch8
Archives
- Today
- Total
나구리의 개발공부기록
데이터 접근 기술 - Querydsl, Querydsl 소개(기존 방식의 문제점 / 해결), Querydsl 설정, Querydsl 적용 본문
인프런 - 스프링 완전정복 코스 로드맵/스프링 DB 2편 - 데이터 접근 활용
데이터 접근 기술 - Querydsl, Querydsl 소개(기존 방식의 문제점 / 해결), Querydsl 설정, Querydsl 적용
소소한나구리 2024. 9. 23. 09:51 출처 : 인프런 - 스프링 DB 2편 데이터 접근 핵심 원리 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2
1. Querydsl 소개 - 기존 방식의 문제점
1) Query의 문제점
- 아래의 코드는 문자를 합치게 되면 "select * from memberwhere name like ?and age between ? and ?" 처럼 문자가 합쳐지는 버그가 발생함 -> 과거에 버그를 정말 많이 내었던 부분임
- 쿼리는 문자이므로 Type-check가 불가능하고 실행하기 전까지 작동 여부를 확인할 수가 없음
String sql =
"select * from member" +
"where name like ?" +
"and age between ? and ?"
- 컴파일 에러 : 좋은 에러 -> IDE에서 컴파일 시점에 그 즉시 확인할 수 있는 에러(타입 에러등), 문제가 발생 되기 전에 에러를 알 수 있음
- 런타임 에러 : 나쁜 에러 -> 애플리케이션을 띄울때 에러가 나는 경우는 그나마 낫지만, 정상 작동은 되는데 고객이 사용할 때 발생되는 에러는 가장 안좋음 (문법오류 등..)
2) QueryDSL
- 쿼리를 Java로 type-safe하게 개발할 수 있게 지원하는 프레임워크 -> 쿼리를 Java 코드로 작성할 수 있게 도와줌
- 주로 JPA쿼리(JPQL)에 사용함
- SQL도 지원하긴 하는데 복잡해서 사용하진 않음
(1) Type-safe - 프로그래밍 언어나 시스템에서 타입 관련 오류를 방지하고 데이터의 일관성을 보장
- 컴파일시 에러 체크 가능
- 제네릭 프로그래밍 가능
- IDE 에서 Code-assistant, 컨트롤 + 스페이스+ .(dot) 등의 기능을 활용할 수 있음
3) JPA에서 Query 방법은 크게 3가지
(1) JPQL(HQL)
- SQL과 비슷해서 금방 익숙해지지만 type-safe가 아니며 동적 쿼리 생성이 어려움
@Test
public void jpql() {
String query =
"select m from Member m " +
"where m.age between 20 and 40 " +
" and m.name like '김%' " +
"order by m.age desc";
List<Member> resultList =
entityManager.createQuery(query, Member.class)
.setMaxResults(3).getResultList();
}
(2) Criteria API
- 장점이 동적 쿼리를 편리하게 작성할 수 있다고 하는데 아님.. 실제로해보면 그렇지 않음.. 눈에 안들어옴
- type-safe도 아니고 복잡하고 알아야 할게 많음
@Test
public void jpaCriteriaQuery() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> root = cq.from(Member.class);
Path<Integer> age = root.get("age");
Predicate between = cb.between(age, 20,40);
Path<String> path = root.get("name");
Predicate like = cb.like(path, "김%");
CriteriaQuery<Member> query = cq.where( cb.and(between, like) );
query.orderBy(cb.desc(age));
List<Member> resultList =
entityManager.createQuery(query).setMaxResults(3).getResultList();
}
(3) MetaModel Criteria API(type-safe)
- Criteria API + MetaModel 이며, Criteria API 와 거의 동일하지만 type-safe 지원함
- 하지만 복잡한건 매한가지임
@Test
public void jpaCriteriaQueryWithMetamodel() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> member = cq.from(Member.class);
// Metamodel 사용
EntityType<Member> Member_ = member.getModel();
Path<Integer> age = member.get(Member_.age);
Path<String> name = member.get(Member_.name);
Predicate agePredicate = cb.between(age, 20, 40);
Predicate namePredicate = cb.like(name, "김%");
cq.where(cb.and(agePredicate, namePredicate));
cq.orderBy(cb.desc(age));
TypedQuery<Member> query = entityManager.createQuery(cq);
List<Member> resultList = query.setMaxResults(3).getResultList();
}
** timowest라는 핀란드 개발자가 이런 복잡한 동적쿼리 문제를 해결하기 위해 QueryDSL을 개발함
2. Querydsl 소개 - 해결
1) QueryDSL 분석
- DSL : 도메인 + 특화 + 언어, 특정한 도메인에 초점을 맞춘 제한적인 표현력을 가진 컴퓨터 프로그래밍 언어
- Domain : 도메인
- Specific : 특화
- Language : 언어
- 쿼리 + DSL : 쿼리에 특화된 프로그래밍 언어
- 단순하고 간결하며 유창함
- 다양한 저장소 쿼리 기능을 통합
- JPA, MongoDB, SQL 같은 기술들을 위해 type-safe SQL을 만드는 프레임 워크
2) QueryDSL - JPA
- 많은 DB기술에 접근하기 위한 통합 문법을 제공하지만 JPA 쿼리(JPQL)을 typesafe하게 작성하는데 많이 사용됨
- QueryDSL을 사용할 때는 APT(Annotation Processing Tool)를 통해 엔터티 클래스에서 Q 클래스를 생성함
- JPA의 경우에는 @Entity가 붙은 코드를 읽어서 Q엔터티가 생성됨
- QuesryDSL이 JPQL을 생성하고 그후 SQL도 생성해서 실행
- @Entity가 붙은 @Member를 읽어서 APT가 QMember를 생성
- JPQL을 먼저 생성하고 그후 SQL로 번역해서 실행함
- QueryDSL - JPA 버전은 생성된 Q클래스를 JPQL을 만들고 실행해주는 빌더라고 이해하면 되는데, 결국 JPA 자체를 잘 알아야하고 JPA가 제공하는 JPQL을 잘 알아야 실무에서 사용할 수 있음
3) 장단점 및 기능
(1) 장점
- type-safe 제공
- 단순하고
- 쉬움
(2) 단점
- Q코드 생성을 위한 APT 설정이 좀 복잡한데, 한번 세팅하고 쓰면 편리함
(3) 구성 및 기능
- Query 관련 기능
- fetch() : 목록 조회
- fetchOne() : 단건 조회
- from : 데이터 소스 지정
- innerJoin, join, leftJoin, fullJoin : 조인 처리
- on : 조인 조건 설정
- where (and, or, allOf, anyOf) : 조건절
- groupBy : 그룹핑
- having : 그룹 조건
- orderBy (desc, asc) : 정렬
- limit, offset, restrict(limit + offset) : 페이징 처리
- Path, 경로
- QMember
- QMember.name
- Expression, 표현식
- member.age.eq(30): equals, age = 30
- member.name.contains("John"): 포함, name Like '%John%'
- name.gt(30): greater than, name > 30
- 단순 쿼리, 동적 쿼리, 조인쿼리, 페이징, 정렬 지원
4) SpringDataJPA + Querydsl
- SpringData 프로젝트의 약점은 조회이지만 Querydsl로 조회 기능을 보완하여 복잡한 쿼리 및 동적 쿼리가 가능해짐
- 단순한 경우에는 SpringDataJPA를 사용하고 복잡한 경우 Querydsl을 직접 사용하면됨
- type-safe가 지원되어 컴파일 오류로 문제의 원인을 직관적으로 볼수 있고 IDE 지원을 통해 코드 및 쿼리 작성이 매우 수월해지기 때문에 한번 써보면 안쓰기가 어려움.. (자바 코드로 IDE의 지원을 받으며 짜기 때문에 작성이 수월하고 재밌음)
- JPQL로 해결하기 어려운 영역이 존재하는데 이부분은 JPA 기본편 강의에서 JPQL을 설명하면서 어떤 한계가 있는지 설명하고, 이런 부분에 부딪힐때는 JdbcTemplate이나 MyBatis를 사용해서 네이티브 SQL쿼리를 사용해서 해결하면 됨
3. Querydsl 설정
- IDE, 그레이들 버전이나 환경에 따라 설정 방법이 조금씩 달라질 수 있으므로 동작이 안된다면 인터넷 검색을 활용해야 함(메뉴얼에는 잘 안나와있음)
(1) 스프링 부트 3.x 설정
- build.gradle에 의존성 및 clean으로 Q클래스 제거하는 설정 추가
** 참고
- 20241121 스프링 부트 수업 들으며 확인한 결과 스프링 부트 3.0.2 기준 querydsl도 스프링 부트에서 자동으로 버전관리를 해주기 때문에 버전명 없이 implementation 'com.querydsl:querydsl-jpa'로만 추가해줘도 됨
dependencies {
//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}
//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
//IntelliJ 설정시에만 필요하고 Gradle 설정시에는 해당 코드없어도 gradle clean 으로 제거 됨
clean {
delete file('src/main/generated')
}
(2) 스프링 부트 2.x 설정
dependencies {
//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}
//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
//IntelliJ 설정시에만 필요하고 Gradle 설정시에는 해당 코드없어도 gradle clean 으로 제거 됨
clean {
delete file('src/main/generated')
}
2) Q 타입 생성 확인 방법
- setting -> Build Tools -> Gradle에서 Build and run using과 Run tests using에 Gradle인지, IntelliJ 인지에 따라 설정이 다름
(1) Gradle로 설정 시
- InteliJ에서 우측 상단 코끼리모양(Gradle) 클릭 후 clean 후 compileJava 실행
- Gradle -> Tasks -> build -> clean
- Gradle -> Tasks -> other -> compileJava
- 콘솔에서 사용시 gradle clean compileJava라고 입력하면 됨
- build -> generated -> sources -> annotationProcessor -> java/main 하위에 hello.itemservice.domain.QItem이 생성됨
- clean을 수행하면 build 폴더 자체가 삭제되므로 별도의 설정은 없어도 됨
(2) IntelliJ 설정시
- 스프링 부트 3.2.x 버전부터 매개변수 이름을 파싱하는 방식이 변경되어서 가급적 gradle로 설정하는 것이 편리함
- main() 애플리케이션 실행 혹은 상단의 Build -> Rebuild 혹은 Build -> Build Project를 실행해주면됨
- src/main/generated 하위에 hello.itemservice.domain.QItem이 생성됨
- IntelliJ 설정시에는 Q파일을 직접 삭제 해야하는데, build.gradle에 추가한 clean { ... } 설정으로 gradle clean 명령어 실행으로 Q파일을 삭제 할 수 있음
** 참고
- Q타입은 컴파일 시점에 자동 생성되므로 버전관리(GIT)에 포함하지 않는 것이 좋음
- gradle 옵션을 선택하면 Q 타입은 gradle build폴더 아래에 생성되는데 대부분 gradle build 폴더를 git에 포함하지 않기 때문에 이부분은 자연스럽게 해결됨
- InteliJ IDEA 옵션을 선택하면 src/main/generated 폴더 아래에 생성되기 때문에 해당 경로를 포함하지 않도록 gitignore로 설정 해줘야함
- Querydsl은 이렇게 설정하는 부분이 사용하면서 조금 귀찮은데, IDE나 Querydsl의 Gradle 설정이 버전업하면서 적용 방법이 조금 달라지거나 본인의 작업 환경에 따라서 잘 동작하지 않을 수 있지만 querydsl gradle로 검색하면 대안을 금방 찾아서 적용할 수 있음(메뉴얼에는 안나와있음)
4. Querydsl 적용
1) JpaItemRepositoryV3
- Querydsl을 사용하려면 JPAQueryFactory가 필요한데, JPAQueryFactory는 JPA 쿼리인 JPQL을 만들기 때문에 EntityManager가 필요함 -> JdbcTemplate 설정하는 것과 유사함
- JPAQueryFactory를 스프링 빈으로 등록해서 사용해도 됨
(1) findAllOld()
- Querydsl을 사용하는 기본적인 방법으로 동적 쿼리 문제를 해결
- BooleanBuilder를 사용해서 if문을 사용해서 조건들을 설정하고 where에 설정한 조건을 넣어주면 됨
- 자바 코드로 작성하기 때문에 동적 쿼리를 매우 편리하게 작성할 수 있음
package hello.itemservice.repository.jpa;
// static Import - QItem
import static hello.itemservice.domain.QItem.*;
@Repository
@Transactional
public class JpaItemRepositoryV3 implements ItemRepository {
// JPA 사용
private final EntityManager em;
private final JPAQueryFactory query;
// 주입: JPAQueryFactory에 EntityManager를 입력하여 생성
public JpaItemRepositoryV3(EntityManager em) {
this.em = em;
this.query = new JPAQueryFactory(em);
}
// ... JPA 사용 코드와 동일
// Querydsl 사용
// @Override
public List<Item> findAllOld(ItemSearchCond cond) {
String itemName = cond.getItemName();
Integer maxPrice = cond.getMaxPrice();
// static Import 하면 해당 코드를 생략해도 됨
// Item item = QItem.item;
BooleanBuilder builder = new BooleanBuilder();
if (StringUtils.hasText(itemName)) {
builder.and(item.itemName.like("%" + itemName + "%"));
}
if (maxPrice != null) {
builder.and(item.price.loe(maxPrice));
}
List<Item> result = query
.select(item)
.from(item)
.where(builder)
.fetch();
return result;
}
}
2) 설정 및 실행
(1) QuerydslConfig 생성
- JPA를 사용했으므로 리포지토리 주입을 EntityManager를 사용하여 JpaItemRepositoryV3로 생성
package hello.itemservice.config;
@Configuration
@RequiredArgsConstructor
public class QuerydslConfig {
private final EntityManager entityManager;
@Bean
public ItemService itemService() {
return new ItemServiceV1(itemRepository());
}
@Bean
public ItemRepository itemRepository() {
return new JpaItemRepositoryV3(entityManager);
}
}
(2) ItemServiceApplication에 Import 설정 변경
- 생성한 QuerdydslConfig로 @Import 변경
@Import(QuerydslConfig.class)
(3) 실행
- findItems 테스트가 정상 실행 되며 애플리케이션도 모두 잘 동작함
(4) 예외 변환
- Querydsl은 별도의 스프링 예외 추상화를 지원하지 않지만 대신 @Repository가 적용해서 스프링 예외 추상화를 적용
3) JpaItemRepositoryV3 - findAll() 리펙토링
- findAllOld의 코드에서 동적쿼리 조건들을 메서드 추출로 리펙토링하여 훨씬 깔끔하고 이해하기 쉽게 변경
- Querydsl에서 where(A,B)에 다양한 조건들을 직접 넣게되면 AND 조건으로 처리하게 되고 where()에 null이 반환되면 해당 조건은 무시하게 됨
- 해당 코드의 장점은 동적 쿼리 조건을 메서드 추출로 likeItemName(), maxPrice() 처럼 메서드화 했기 때문에 다른 쿼리를 작성할 때 재사용 할 수 있음 -> 자바 코드로 개발했기 때문에 쿼리 조건을 부분적으로 모듈화 할 수 있는 큰 장점
// findAll - 리펙토링
@Override
public List<Item> findAll(ItemSearchCond cond) {
String itemName = cond.getItemName();
Integer maxPrice = cond.getMaxPrice();
// 메서드 추출을 활용해서 간결해진 쿼리 코드
return query
.select(item)
.from(item)
.where(likeItemName(itemName), maxPrice(maxPrice))
.fetch();
}
// 조건들을 메서드추출로 따로 빼서 관리
private BooleanExpression maxPrice(Integer maxPrice) {
if (maxPrice != null) {
return item.price.loe(maxPrice);
}
return null;
}
private BooleanExpression likeItemName(String itemName) {
if (StringUtils.hasText(itemName)) {
return item.itemName.like("%" + itemName + "%");
}
return null;
}
- 리펙토링 코드도 테스트 및 애플리케이션 실행해서 테스트해보면 정상 동작함
4) 정리
- Querydsl 덕분에 동적 쿼리를 매우 깔끔하게 이해할 수 있게 사용할 수 있게 됨
- 쿼리 문장에 오타가 있어도 컴파일 시점에 오류를 막을 수 있게됨
- 메서드 추출을 통해서 코드를 재사용 할 수 있게됨(동적 쿼리 조건을 모듈화 하여 다른 비슷한 쿼리가 필요할 때 함께 사용이 가능함)
- Querydsl을 사용해서 자바 코드로 쿼리를 작성하성하면 위 장점 외에도 최적의 쿼리결과를 만들기 위해 DTO로 편리하게 조회하는 기능(Q엔터티 생성)등의 수많은 편리한 기능을 제공함 - DTO로 조회하는 기능은 실무에서 자주 사용하는 기능
- JPA를 사용한다면 스프링 데이터 JPA와 Querydsl은 실무의 다양한 문제를 편리하게 해결하기 위해 기본으로 선택해야 하는 기술
- Querydsl을 잘 사용하려면 JPQL 문법을 잘 알아야 함
- Querydsl의 자세한 강의는 실전! Qeurydsl, JPA의 자세한 강의는 JPA 기본편 강의에서 다룸