728x90
1. Querydsl 설정
1.1. build.gradle
plugins {
id 'org.springframework.boot' version '2.6.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
// hibernate.version 버그로 인한 version downgrade 방법
ext["hibernate.version"] = "5.6.5.Final"
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//테스트에서 lombok 사용
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
// JdbcTemplate 추가
// mybatis나 jpa 사용시, 해당 패키지 내부에 jdbcTemplate이 들어가있어서 주석처리
// implementation 'org.springframework.boot:spring-boot-starter-jdbc'
// MyBatis 추가 - 김영한 강사님 file을 그대로 사용한 것이여서 2.2.0으로 작성해야한다.
// boot 3.0 이상 사용시 version을 3.0.1로 변경 해야한다.
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
// JPA 추가
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// H2 데이터베이스 추가
runtimeOnly 'com.h2database:h2'
//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"
}
tasks.named('test') {
useJUnitPlatform()
}
// Querydsl 추가, 자동 생성된 Q클래스를 gradle clean으로 제거
clean {
delete file('src/main/generated')
}
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으로 제거
clean {
delete file('src/main/generated')
}
1.2. Q 타입 생성 확인 방법
- Q type은 compile 시점에 자동 생성되는 파일이기 때문에 Git에 포함하지 않는 것을 권장한다.
- gradle build 하위에 생성되기 때문에 해당 패키지 자체를 git에 올라가지 않게 막으면 된다.
- 참고 - Querydsl 설정 부분은 공식메뉴얼에 소개 되어 있는 부분이 아니기 때문에 IntelliJ version이 변경되거나 Querydsl의 Gradle 설정 version이 변경이 될 경우 적용 방법이 조금씩 달라진다.
따라서 querydsl gradle로 검색하면 본인 환경에 맞는 대안을 찾아서 설정을 하길 권장한다.
1.2.1. gradle 이용 방법
- gradle 설정으로 gradle 선택
- 우측 배너에 아래 clean compile 2가지 단계 수행
- Gradle -> Task -> build -> clean
- Gradle -> Tasks -> other -> compileJava
- build -> generated -> sources -> annotationProcessor -> java/main 하위에 hello.itemservice.domain.QItem.class 가 생성되면 Q 타입이 생성이 된 것이다.
1.2.2. IntelliJ 방식
- intelliJ로 변경
- ItemServiceApplication 실행 * db 실행 중
- 동일 위치에 QItem 생성 확인 가능
- app 실행 대신 build -> Build Project로도 수행 가능 혹은 캐시 적용으로 generated package 생성 안될 경우 rebuild 하면 QItem 생성된다.
2. Querydsl 적용
- 공통
- Querydsl을 사용하려면 JPAQueryFactory가 필요
- JPAQueryFactory는 JPA 쿼리인 JPQL을 만들기 때문에 EntityManager가 필요
- 생성자 주입 방식은 JdbcTemplate 설정 방식과 유사
2.1. JpaItemRepositoryV3
- save, update(), findById()는 JPA 기능 사용
package hello.itemservice.repository.jpa;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import hello.itemservice.domain.Item;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
import static hello.itemservice.domain.QItem.*;
@Repository
@Transactional
public class JpaItemRepositoryV3 implements ItemRepository {
// jpa 사용하기 위한 bean 주입
private final EntityManager em;
private final JPAQueryFactory query;
public JpaItemRepositoryV3(EntityManager em) {
this.em = em;
this.query = new JPAQueryFactory(em);
}
@Override
public Item save(Item item) {
em.persist(item);
return item;
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
Item findItem = em.find(Item.class, itemId); // 1. 항상 select 수행
findItem.setItemName(updateParam.getItemName());
findItem.setPrice(updateParam.getPrice());
findItem.setQuantity(updateParam.getQuantity());
}
@Override
public Optional<Item> findById(Long id) {
Item item = em.find(Item.class, id); // find(entity.class, pk)
return Optional.ofNullable(item);
}
public List<Item> findAllOld(ItemSearchCond cond) {
String itemName = cond.getItemName();
Integer maxPrice = cond.getMaxPrice();
// QItem item = new QItem("i"); // params가 alias
// QItem item = QItem.item; // QItem.class가 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) // QItem static import로 뺌
.from(item)
.where(builder)
.fetch();
return result;
}
@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 likeItemName(String itemName){
if (StringUtils.hasText(itemName)){
return item.itemName.like("%" + itemName + "%");
}
return null;
}
private BooleanExpression maxPrice(Integer maxPrice){
if (maxPrice != null) {
return item.price.loe(maxPrice);
}
return null;
}
}
2.2. 동적 쿼리
- BooleanBuilder를 사용해서 where 조건을 넣어준다.
- 최종적으로 JPAQueryFactory에 쿼리를 넣어준다.
2.2.1. findAllOld
- method 내부에 다 넣는 방식
package hello.itemservice.repository.jpa;
import static hello.itemservice.domain.QItem.*;
@Repository
@Transactional
public class JpaItemRepositoryV3 implements ItemRepository {
// jpa 사용하기 위한 bean 주입
private final EntityManager em;
private final JPAQueryFactory query;
public JpaItemRepositoryV3(EntityManager em) {
this.em = em;
this.query = new JPAQueryFactory(em);
}
public List<Item> findAllOld(ItemSearchCond cond) {
String itemName = cond.getItemName();
Integer maxPrice = cond.getMaxPrice();
// QItem item = new QItem("i"); // params가 alias
// QItem item = QItem.item; // QItem.class가 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) // QItem static import로 뺌
.from(item)
.where(builder)
.fetch();
return result;
}
}
2.2.2. findAll
- private으로 복잡한 작업 빼준다. - 반환 타입이 BooleanExpression
- 핵심 로직이 더 간결해진다.
package hello.itemservice.repository.jpa;
import static hello.itemservice.domain.QItem.*;
@Repository
@Transactional
public class JpaItemRepositoryV3 implements ItemRepository {
// jpa 사용하기 위한 bean 주입
private final EntityManager em;
private final JPAQueryFactory query;
public JpaItemRepositoryV3(EntityManager em) {
this.em = em;
this.query = new JPAQueryFactory(em);
}
@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 likeItemName(String itemName){
if (StringUtils.hasText(itemName)){
return item.itemName.like("%" + itemName + "%");
}
return null;
}
private BooleanExpression maxPrice(Integer maxPrice){
if (maxPrice != null) {
return item.price.loe(maxPrice);
}
return null;
}
}