[SpringBoot-JPA 활용편1] 자바 ORM 표준 JPA 프로그래밍 - 활용편2: 6. 스프링 data JPA 소개 및 QueryDSL 소개
실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 강의 - 인프런
스프링 부트와 JPA를 활용해서 API를 개발합니다. 그리고 JPA 극한의 성능 최적화 방법을 학습할 수 있습니다., 스프링 부트, 실무에서 잘 쓰고 싶다면? 복잡한 문제까지 해결하는 힘을 길러보세요
www.inflearn.com
해당 강의는 Inflearn에 등록된 김영한님의 Spring Boot 강의입니다.
이번 시간에는 스프링 data JPA와 QueryDSL에 대해서 공부하자.
1. 스프링 데이터 JPA 소개
스프링 데이터 JPA: https://spring.io/projects/spring-data-jpa
스프링 데이터 JPA의 예제를 위해 MemberRepository에 Old라고 refactoring하자.
이때 클래스 이름 변경시 다른 클래스에 선언되어 있는 MemberRepository 객체 들은 그대로 둔다.
(그 이름 그대로를 스프링 Data JPA가 대체할 예정)
MemberRepository
public interface MemberRepository extends JpaRepository<Member,Long> {
//findByX: select m from Member m.where m.x= :x
List<Member> findByName(String name);
}
MemberService
중에서
Spring Data JPA의 findById 메서드는 제네릭 타입의 ID를 입력으로 받으며, 이는 Long, Integer, UUID, String 등 다양한 타입의 ID를 처리할 수 있다는 것을 의미한다. 이러한 유연성은 Spring Data JPA가 제공하는 타입 안전성과 함께 강력한 기능 중 하나다.
findById 메서드는 Optional 타입을 반환한다. Optional 클래스는 자바 8에서 도입되었으며, 값이 있을 수도 있고 없을 수도 있는 객체를 안전하게 다룰 수 있도록 해준다. Optional.get() 메서드는 Optional 객체가 감싸고 있는 실제 값을 반환한다. 하지만, Optional 객체가 비어 있을 경우, 즉 값이 없다면 .get() 메서드 호출 시 NoSuchElementException 예외가 발생한다. 이는 Optional 객체의 사용에 있어 주의해야 할 중요한 사항이다.
더 안전하게 Optional 객체를 다루기 위한 방법으로는 orElse(), orElseThrow(), ifPresent() 같은 메서드들이 있다. 이 메서드들은 Optional 객체가 비어 있을 때 다양한 대응 방법을 제공한다. 특히, orElseThrow() 메서드는 Optional 객체가 비어 있을 때 특정 예외를 발생시키며, 사용자 정의 예외를 지정할 수 있어 에러 상황을 더 명확하게 전달할 수 있다.
예를 들어, 다음과 같이 orElseThrow()를 사용할 수 있다:
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new CustomNotFoundException("해당 사용자를 찾을 수 없습니다. ID: " + memberId));
spring data jpa의 JpaRepository를 상속만 해도 어마어마하게 많은 메서드를 쓸 수 있게 됐다.
Ex: findById(Long id)
나머지 특수한 컬럼에 대한 조회 및 crud는 따로 메서드를 만들면 된다.
회원 목록 및 주문목록에서 확인 (OSIV를 ON하지않으면 지연로딩 에러가 난다.)
findByName은 MemberRepository에 그냥 메서드로 선언만 하면 된다.
public interface MemberRepository extends JpaRepository<Member,Long> {
//findByX: select m from Member m.where m.x= :x
List<Member> findByName(String name);
}
MemberService에 Data Jpa의 메서드들을 사용.
2. QueryDSL 소개
http://querydsl.com/static/querydsl/latest/reference/html/ch02.html#jpa_integration
2. Tutorials
Querydsl defines a general statically typed syntax for querying on top of persisted domain model data. JDO and JPA are the primary integration technologies for Querydsl. This guide describes how to use Querydsl in combination with JPA. Querydsl for JPA is
querydsl.com
spring boot 3.X버전 QueryDSL 설정 방법
1.Build.gradle
아래와 같이 복붙
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'jpabook'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-devtools' //이것을 통해 코드 수정시 컴파일만하면 변경 업데이트 해줌
//쿼리 파라미터를 로그로 남기는 외부 라이브러리
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0'
// Hibernate5JakartaModule 등록
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5-jakarta'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//JUnit4 추가
testImplementation("org.junit.vintage:junit-vintage-engine") {
exclude group: "org.hamcrest", module: "hamcrest-core"
}
//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"
}
//junit
tasks.named('test') {
useJUnitPlatform()
}
//querydsl 추가
def querydslDir = 'src/main/generated'
clean {
delete file('src/main/generated')
}
tasks.withType(JavaCompile){
options.generatedSourceOutputDirectory=file(querydslDir)
}
2. CompileJava
더블클릭
3.src/main/generated 파일 생성 확인
이는 자동생성 파일이니 gitignore해줘야함.(git hub에 등록시 필수!!)
git ignore
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### QueryDSL관련 파일 ###
/src/main/generated/
실제 사용 예시
OrderRepository
에 x표시한거 지우고 코드추가
코드
import static jpabook.jpashop.domain.QMember.member;
import static jpabook.jpashop.domain.QOrder.order;
@Repository
public class OrderRepository {
private final EntityManager em;
private final JPAQueryFactory query;
public OrderRepository(EntityManager em) {
this.em = em;
this.query = new JPAQueryFactory(em);
}
/** Query DSL로 주문상태 에따라 조회하기
* 동적쿼리(조건이 있는 쿼리)
*/
public List<Order> findAllByQueryDsl(OrderSearch orderSearch){
//Q객체 생성
QOrder order= QOrder.order;
QMember member = QMember.member;
return query
.select(order)
.from(order)
.join(order.member,member)
.where(satusEq(orderSearch.getOrderStatus()), //AND 조건
nameLike(orderSearch.getMemberName()))
.limit(1000)
.fetch();
}
//주문 상태
private BooleanExpression satusEq(OrderStatus orderStatusCond) {
if (orderStatusCond==null)
return null;
return order.status.eq(orderStatusCond);
}
//이름 조회
private BooleanExpression nameLike(String nameCond) {
//이름이 없으면 NULL로 조회
if (!StringUtils.hasText(nameCond)) {
return null;
}
//이름이 있으면 해당 이름 조회
return member.name.like(nameCond);
}
코드 설명
이 코드는 Spring Data JPA와 Querydsl을 활용하여 OrderRepository 클래스 내에서 동적 쿼리를 구현하는 방법을 보여준다. OrderRepository는 주문(Order) 엔티티에 대한 데이터 접근을 담당하며, 주문 상태와 회원 이름을 기준으로 주문 목록을 조회하는 기능을 제공한다.
핵심 구성 요소 설명
- EntityManager는 JPA의 EntityManager를 통해 데이터베이스와의 통신을 관리한다. EntityManager는 엔티티의 조회, 추가, 수정, 삭제 등을 처리하는 핵심 인터페이스이다.
- JPAQueryFactory는 QueryFactory는 Querydsl 쿼리를 실행하는 데 필요한 컨텍스트를 제공한다.
- Q 클래스는 Querydsl이 제공하는 Q 클래스(예: QOrder, QMember)를 활용하여 쿼리를 타입 안전하게 구성한다. Q 클래스는 엔티티의 속성에 접근할 수 있는 메타모델을 제공한다.
메서드 설명
- findAllByQueryDsl 메서드는 주문 상태(OrderStatus)와 회원 이름(memberName)을 조건으로 받아, 해당 조건에 부합하는 주문 목록을 조회한다. 이 메서드는 Querydsl을 사용해 동적 쿼리를 구성하고 실행한다.
- .where 절에서는 statusEq와 nameLike 메서드를 활용해 주문 상태와 회원 이름에 대한 조건을 추가한다. 이 두 메서드는 BooleanExpression을 반환하며, Querydsl에서 조건문을 구성하는 데 사용된다.
- statusEq 메서드는 주문 상태 조건을 구성한다. 주문 상태가 null이 아니라면, 해당 상태를 가진 주문을 조회하는 조건을 반환한다.
- nameLike 메서드는 회원 이름 조건을 구성한다. 회원 이름이 주어졌다면, 해당 이름을 포함하는 회원의 주문을 조회하는 조건을 반환한다.
- limit(1000)은 조회 결과의 최대 개수를 1000개로 제한한다.
- .fetch()는 최종적으로 구성된 쿼리를 실행하고 결과를 리스트로 반환한다.
+a)order객체는 Qorder의 객체이다.
OrderService에 복붙
package jpabook.jpashop.service;
import jpabook.jpashop.domain.*;
import jpabook.jpashop.domain.item.Item;
import jpabook.jpashop.repository.ItemRepository;
import jpabook.jpashop.repository.MemberRepository;
import jpabook.jpashop.repository.OrderRepository;
import jpabook.jpashop.repository.OrderSearch;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final MemberRepository memberRepository;
private final ItemRepository itemRepository;
/**
* 주문 서비스의 구현할 기능
* 1. 상품 주문
* 2. 주문 내역 취소
* 3. 주문 내역 조회
*/
//1. 주문 (사용자 입력: 회원 아이디, 아이템 아이디, 주문 수량 입력)
@Transactional
public Long order(Long memberId, Long itemId, int count) {
//엔티티 조회
Member member = memberRepository.findById(memberId).get(); //회원 조회
Item item = itemRepository.findOne(itemId); //상품 조회
//배송 정보 생성
Delivery delivery = new Delivery();
delivery.setAddress(member.getAddress()); //배송 정보는 회원의 주소로 세팅
delivery.setStatus(DeliveryStatus.READY);
//주문 상품 생성
OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);
//주문 생성
Order order = Order.createOrder(member, delivery, orderItem); //회원,배송정보,주문 상품을 갖는 주문 생성
//주문 저장
orderRepository.save(order);
//저장한 주문을 객체로 반환
return order.getId();
}
//2. 주문 내역 취소
//취소시 주문 아이디로만 넘겨받아서 취소함
@Transactional
public void cancelOrder(Long orderId) {
//주문 엔티티 조회
Order order = orderRepository.findOne(orderId);
//주문 취소
order.cancel();
}
// 3. 주문 내역 조회....추후 강의 내용
public List<Order> findOrders(OrderSearch orderSearch)
{
//Criteria로 조회
//return orderRepository.findAllByCriteria(orderSearch);
//QueryDsl로 조회
return orderRepository.findAllByQueryDsl(orderSearch);
}
}
결과
Query DSL의 장점 5가지
3. Spring Data JPA와 QueryDSL: 각각 언제 사용하나?
개발 프로젝트에서 데이터를 다루는 것은 필수적인 작업 중 하나이다. Java 개발 환경에서 Spring Data JPA와 QueryDSL은 데이터 접근 및 쿼리 작성을 위한 강력한 도구들이다. 그러나 이 두 기술은 서로 다른 시나리오에서 그 진가를 발휘한다. 여기서는 Spring Data JPA와 QueryDSL을 언제 사용하는 것이 적합한지에 대해 알아보겠다.
Spring Data JPA: 단순 CRUD 작업의 해결사
Spring Data JPA는 Java Persistence API에 대한 Spring의 구현체로, 데이터 접근 계층을 쉽게 구축할 수 있게 해주는 프레임워크이다. 이는 주로 엔티티의 생성, 읽기, 업데이트, 삭제와 같은 기본적인 CRUD(Create, Read, Update, Delete) 작업을 수행할 때 사용된다. Spring Data JPA를 사용하면 복잡한 DAO(Data Access Object) 없이 리포지토리 인터페이스만으로 데이터 접근 코드를 간결하게 유지할 수 있다.
public interface UserRepository extends JpaRepository<User, Long> {
}
위 코드와 같이, 단 몇 줄의 코드로 기본적인 데이터 접근 기능을 갖춘 리포지토리를 구현할 수 있다. 이는 개발자가 데이터 접근 로직에 들이는 시간과 노력을 크게 줄여준다.
QueryDSL: 동적 쿼리의 유연한 대처
반면, QueryDSL은 복잡한 쿼리와 동적 쿼리를 작성할 때 그 강점을 발휘한다. 조건이 많고 복잡한 비즈니스 로직을 처리해야 할 때, QueryDSL은 타입 안전성을 제공하며 직관적인 API를 통해 쿼리를 구성할 수 있게 해준다. QueryDSL을 사용하면 조건에 따라 쿼리를 유연하게 변경할 수 있으며, 코드의 가독성도 높아진다.
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QUser user = QUser.user;
List<User> users = queryFactory.selectFrom(user)
.where(user.name.eq("Cho changuk")
.and(user.age.between(20, 30)))
.fetch();
- name이 "Cho changuk"과 정확히 일치하는 User 엔티티.
- age가 20 이상 30 이하인 User 엔티티.
위 예시처럼, QueryDSL은 복잡한 조건을 가진 쿼리를 명확하게 작성할 수 있게 해준다.
+a) QueryDSL에서 .fetch() 메서드는 쿼리를 실행하고, 그 결과를 가져오는 역할을 한다. 즉, 작성한 쿼리에 따라 데이터베이스에서 데이터를 조회하고, 이 데이터를 자바 객체의 리스트로 변환하여 반환한다.
정리하면 Spring Data는 단순 CRUD 작업시에 사용하고,
QueryDSL은 조건이 있는 동적쿼리 처리시에 사용하기 좋다.