You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
plugins {
id 'org.springframework.boot' version '3.6.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
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'
//MyBatis 추가
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
//JPA, 스프링 데이터 JPA 추가
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
//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"
//H2 데이터베이스 추가
runtimeOnly 'com.h2database:h2'
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'
}
tasks.named('test') {
useJUnitPlatform()
}
//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
clean {
delete file('src/main/generated')
}
검증 - Q 타입 생성 확인 방법
###QueryDSL 적용
JpaItemRepositoryV3
packagehello.itemservice.repository.jpa;
importcom.querydsl.core.BooleanBuilder;
importcom.querydsl.core.types.Predicate;
importcom.querydsl.core.types.dsl.BooleanExpression;
importcom.querydsl.jpa.impl.JPAQueryFactory;
importhello.itemservice.domain.Item;
importhello.itemservice.domain.QItem;
importhello.itemservice.repository.ItemRepository;
importhello.itemservice.repository.ItemSearchCond;
importhello.itemservice.repository.ItemUpdateDto;
importorg.springframework.stereotype.Repository;
importorg.springframework.transaction.annotation.Transactional;
importorg.springframework.util.StringUtils;
importjavax.persistence.EntityManager;
importjava.util.List;
importjava.util.Optional;
importstatichello.itemservice.domain.QItem.*;
/** * @Repository * 데이터베이스와 연결되는 클래스에 붙이는 어노테이션 * Spring이 자동으로 관리, @Component처럼 빈으로 등록 * 원래 JPA는 PersistenceException 같은 예외를 던짐 * 붙이면 Spring이 자동으로 DataAccessException으로 변환 * * Spring이 자동으로 관리, 예외 처리 쉽게 해줌 * */@Repository/** * @Transactional * 트랜잭션을 자동으로 관리 * 디비에 작업을 하나의 단위로 묶는 것 * Spring이 자동으로 트랜잭션을 시작하고, 끝나면 commit함 * * 데이터 변경을 안전하게, 오류 발생 시 rollback 해줌 * */@TransactionalpublicclassJpaItemRepositoryV3implementsItemRepository {
//JPA의 EntityManager를 사용하여 데이터베이스와 상호 작용privatefinalEntityManagerem;
//JPAQueryFactory query : QueryDSL을 활용한 동적 쿼리 생성을 위해 JPAQueryFactory 사용privatefinalJPAQueryFactoryquery;
//생성자를 통해 EntityManager를 주입받고, JPAQueryFactory 초기화publicJpaItemRepositoryV3(EntityManagerem) {
this.em = em;
this.query = newJPAQueryFactory(em);
}
@Override/** * 아이템 저장(save) * em.persist(item)를 통해 새로운 Item 엔티티를 영속화(디비에 저장) * 저장된 엔티티를 그대로 반환 * */publicItemsave(Itemitem) {
em.persist(item);
returnitem;
}
@Override/** * 아이템 수정(uodate) * ID로 기존 아이템을 찾고, 전달받은 updateParam 반영하여 수정 * findById(itemId).orElseThrow() - 해당 ID의 아이템이 없으면 예외 발생 * JPA는 트랜잭션 범위 내에서 엔티티를 변경하면 자동으로 변경 사항을 감지하여 디비에 반영 * */publicvoidupdate(LongitemId, ItemUpdateDtoupdateParam) {
ItemfindItem = em.find(Item.class, itemId);
findItem.setItemName(updateParam.getItemName());
findItem.setPrice(updateParam.getPrice());
findItem.setQuantity(updateParam.getQuantity());
}
@Override/** * 아이템 조회(findById) * em.find(Item.class, id) - JPA의 기본 메서드 사용, ID로 Item 조회 * Optional.ofNullable(item) - null일 경우 대비, Optional로 감싸 반환 * */publicOptional<Item> findById(Longid) {
Itemitem = em.find(Item.class, id);
returnOptional.ofNullable(item);
}
/** * 동적 검색 - 기존 방식(findAllOld) * QueryDSL의 BooleanBuilder를 사용, 동적 검색 조건을 적용 * itemName이 있으면 item.itemName.like("%" + itemName + "%")로 필터링 * maxPrice가 있으면 item.price.loe(maxPrice)로 가격 제한 적용 * 최종적으로 QueryDSL의 fetch()를 사용하여 결과 리스트 반환 * */publicList<Item> findAllOld(ItemSearchCondcond) {
StringitemName = cond.getItemName();
IntegermaxPrice = cond.getMaxPrice();
QItemitem = QItem.item;
BooleanBuilderbuilder = newBooleanBuilder();
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();
returnresult;
}
@Override/** * 동적 검색 - 개선된 방식(findAll) * 기존 BooleanBuilder 대신 개별적인 BooleanExpression 메서드를 활용, 코드가 더 깔끔해짐 * */publicList<Item> findAll(ItemSearchCondcond) {
StringitemName = cond.getItemName();
IntegermaxPrice = cond.getMaxPrice();
returnquery
.select(item)
.from(item)
.where(likeItemName(itemName), maxPrice(maxPrice))
.fetch();
}
/** * itemName이 있으면 LIKE 조건을 적용 * QueryDSL에서는 null 반환, 해당 조건을 무시, BooleanBuilder 없이도, 동적 검색 가능 * */privateBooleanExpressionlikeItemName(StringitemName) {
if (StringUtils.hasText(itemName)) {
returnitem.itemName.like("%" + itemName + "%");
}
returnnull;
}
/** * maxPrice가 설정되어 있으면 가격이 maxPrice 이하인 조건을 적용 * */privateBooleanExpressionmaxPrice(IntegermaxPrice) {
if (maxPrice != null) {
returnitem.price.loe(maxPrice);
}
returnnull;
}
}
QuerydslConfig
packagehello.itemservice.config;
importhello.itemservice.repository.ItemRepository;
importhello.itemservice.repository.jpa.JpaItemRepositoryV3;
importhello.itemservice.service.ItemService;
importhello.itemservice.service.ItemServiceV1;
importlombok.RequiredArgsConstructor;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importjavax.persistence.EntityManager;
@Configuration// 이 클래스는 스프링 설정 클래스/** * @RequiredArgsConstructor * 자동으로 생성자 주입 * EntityManager는 JPA에서 DB와 연결을 관리하는 중요한 객체 * 덕분에 EntityManager를 자동으로 생성자 주입 * 즉, new QuerydslConfig(em)처럼 자동으로 주입 * */@RequiredArgsConstructor// final 필드(EntityManager)를 자동으로 주입publicclassQuerydslConfig {
privatefinalEntityManagerem;//JPA에서 DB와 연결을 도와주는 객체@Bean//스프링이 이 메서드를 실행하고, 반환 값을 관리(Bean) 등록publicItemServiceitemService() {
returnnewItemServiceV1(itemRepository());
}
@BeanpublicItemRepositoryitemRepository() {
returnnewJpaItemRepositoryV3(em);
}
}
QueryDSL 장점
List<Item> result = query
.select(item)
.from(item)
.where(likeItemName(itemName), maxPrice(maxPrice))
.fetch();
쿼리 문장에 오타가 있어도 컴파일 시점 오류 막을 수 있음
메서드 추출을 통해 코드 재사용 가능, ex) likeItemName(itemName), maxPrice(maxPrice) 메서드를 다른 쿼리에 함께 사용
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
QueryDSL 설정
build.gradle
검증 - Q 타입 생성 확인 방법
###QueryDSL 적용
JpaItemRepositoryV3
QuerydslConfig
QueryDSL 장점
All reactions