-
[백견불야일타] 2장 Spring Data JPASpringBoot/쇼핑몰 프로젝트 with JPA 2022. 8. 19. 17:37
JPA
- 자바 ORM기술에 대한 API 표준 - ORM(Object Relational Mapping)이란 객체와 관계형 데이터베이스를 매핑해주는 것을 말함
- 특정 데이터베이스에 종속되지 않음 (Oracle -> MaraDB)
- DB중심 설계의 패러다임에서 객체지향적으로 설계 가능
- 테이블에 새로운 컬럼이 추가되었을 경우, 해당 테이블의 컬럼을 사용하는 DTO 클래스의 필드도 모두 변경해야 한다. JPA에서는 테이블과 매핑된 클래스에 필드만 추가한다면 쉽게 관리 가능하다. 또한 SQL문을 직접 작성하지 않고 객체를 사용하여 동작하기 때문에 유지보수와 재사용성도 증가
- 단점으로는 복잡한 쿼리 처리(통계 처리 같은 복잡한 쿼리 처리는 SQL문 사용이 좋음), 성능 저하 위험(객체 간의 매핑 설계를 잘못했을 경우 성능 저하 발생, 자동으로 생성되는 쿼리가 많기 때문에 의도와 다르게 갈 수 있음), 학습 시간(관계형 DB를 충분히 공부 한 후 사용해야함)
엔티티(Entity)
- 데이터베이스의 테이블에 대응하는 클래스
- @Entity 가 붙은 클래스는 JPA에서 관리하며 엔티티라고 함
엔티티 매니저 팩토리
- 엔티티 매니저 인스턴스를 관리하는 주체
- 애플리케이션 실행 시 한 개만 만들어지며 사용자로부터 요청이 오면 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성
엔티티 매니저
- 영속성 컨텍스트에 접근하여 엔티티에 대한 데이터베이스 작업을 제공
- 내부적으로 데이터베이스 커넥션을 사용해서 데이터베이스에 접근
엔티티 매니저의 몇 가지 메소드
- find() : 영속성 컨텍스트에서 엔티티를 검색하고 영속성 컨텍스트에 없을 경우 데이터베이스에서 데이터를 찾아 영속성 컨텍스트에 저장
- persist() : 엔티티를 영속성 컨텍스트에 저장
- remove() : 엔티티 클래스를 영속성 컨텍스트에서 삭제
- flush() : 영속성 컨텍스트에 저장된 내용을 데이터베이스에 반영
영속성 컨텍스트
- 엔티티를 영구 저장하는 환경
SHOP PROJECT 생성하기
의존성 추가
- Thymeleaf : 서버에서 가공한 데이터를 뷰에 보여주기 위한 템플릿 엔진
- Spring Data JPA : JPA를 쉽게 구현할 수 있도록 도와주는 모듈
- MySQL Driver : MySQL 사용
- H2 Database : 자바 기반의 관계형 데이터베이스로 매우 가볍고 빠른 데이터베이스, 데이터를 영구적으로 저장하는 데 권장되는 데이터베이스는 아니지만 테스트용 데이터베이스로 많이 사용
application.properties
server.port = 80 #MySQL 연결 설정 # 데이터베이스에 연결하기 위해 mysql jdbc driver를 설정 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 연결할 데이터베이스의 url, 포트번호, 데이터베이스의 이름을 입력 spirng.datasource.url=jdbc:mysql://localhost:3307/shop?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=1577 #실행되는 쿼리 콘솔 출력 spring.jpa.properties.hibernate.show_sql=true #콘솔창에 출력되는 쿼리를 가독성이 좋게 포맷팅 spring.jpa.properties.hibernate.format_sql=true #쿼리에 물음표로 출력되는 바인드 파라미터 출력 logging.level.org.hibernate.type.descriptor.sql = trace spring.jpa.hibernate.ddl-auto=create spring.jpa.database-platform = org.hibernate.dialect.MySQL8Dialect
DDL AUTO 옵션
- 애플리케이션 구동 시 JPA의 데이터베이스 초기화 전략을 설정할 수 있다.
- none : 사용하지 않음
- create : 기존 테이블 삭제 후 테이블 생성
- create-drop : 기존 테이블 삭제 후 테이블 생성, 종료 시점에 테이블 삭제
- update : 변경된 스키마 적용
- validate : 엔티티와 테이블 정상 매핑 확인
- 개발 초기에는 create 또는 update 옵션을 이용해서 익숙해지는 데 집중하고 추후에 validate 옵션을 설정 해 주는 것이 좋다.
- 스테이징, 운영 환경에서는 절대로 create, -drop, update 사용 x -> 스테이징과 운영 서버에서는 테이블 생성 및 컬럼 추가, 삭제, 변경은 데이터베이스에서 직접하며, none을 사용하거나 validate를 이용하여 정상적인 매핑 관계만 확인
- 스테이징 환경 - 운영환경과 거의 동일한 환경으로 구성하여, 운영환경에 배포하기 전 여러 가지 기능을 검증하는 환경
- 운영 환경 - 실제 서비스를 운영하는 환경
상품 엔티티 설계
com.shop.constant.ItemSellStatus.java
package com.shop.constant; public class ItemSellStatus { SELL, SOLD_OUT }
상품이 현재 판매 중인지 품절 상태인지를 나타내는 enum 타입의 클래스
com.shop.entity.Item.java
package com.shop.entity; import com.shop.constant.ItemSellStatus; import lombok.Getter; import lombok.Setter; import lombok.ToString; import java.time.LocalDateTime; @Getter @Setter @ToString public class Item { private Long id; // 상품코드 private String itemNm; // 상품명 private int price; // 가격 private int stockNumber; // 재고수량 private String itemDetail; // 상품 상세 설명 private ItemSellStatus itemSellStatus; // 상품 판매 상태 private LocalDateTime regTime; // 등록 시간 private LocalDateTime updateTime; // 수정 시간 }
@Column
- 문자열을 저장하는 VARCHAR 타입은 길이를 설정할 수 있고, 테이블에 데이터를 넣을 때 데이터가 항상 존재해야 하는 Not Null 조건 등이 있다. 이 어노테이션의 속성을 이용하면 테이블에 매핑되는 컬럼의 이름, 문자열의 최대 저장 길이 등 다양한 제약 조건 등을 추가 할 수 있다.
@Entity
- 클래스의 상단에 입력하면 JPA에 엔티티 클래스라는 것을 알려줌
- 반드시 기본키를 가져야 함
- @Id 어노테이션을 이용하여 id 멤버 변수를 상품 테이블의 기본키로 설정
- @GeneratedValue 어노테이션을 통한 기본키를 생성하는 전략은 총 4가지가 있다
- GenerationType.AUTO - JPA 구현체가 자동으로 생성 전략 결정
- GenerationType.IDENTITY - 기본키 생성을 데이터베이스에 위임 (MySQL -> AUTO_INCREMENT 사용)
- GenerationType.SEQUENCE - 데이터베이스 시퀀스 오브젝트를 이용한 기본키 생성. @SequenceGenerator를 사용하여 시퀀스 등록 필요
- GenerationType.TABLE - 키 생서용 테이블 사용. @TableGenerator 필요
com.shop.entity.Item.java
package com.shop.entity; import com.shop.constant.ItemSellStatus; import lombok.Getter; import lombok.Setter; import lombok.ToString; import javax.persistence.*; import java.time.LocalDateTime; @Entity @Table(name="item") @Getter @Setter @ToString public class Item { @Id @Column(name="item_id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; // 상품코드 @Column(nullable = false, length = 50) private String itemNm; // 상품명 @Column(name = "price", nullable = false) private int price; // 가격 @Column(nullable = false) private int stockNumber; // 재고수량 @Lob @Column(nullable = false) private String itemDetail; // 상품 상세 설명 @Enumerated(EnumType.STRING) private ItemSellStatus itemSellStatus; // 상품 판매 상태 private LocalDateTime regTime; // 등록 시간 private LocalDateTime updateTime; // 수정 시간 }
Repository 설계하기
com.shop.repository.ItemRepository.java
package com.shop.repository; import com.shop.entity.Item; import org.springframework.data.jpa.repository.JpaRepository; public interface ItemRepository extends JpaRepository<Item, Long> { }
JpaRepository는 2개의 제네릭 타입을 사용하는데 첫 번째는 엔티티 타입 클래스, 두 번째는 기본키 타입을 넣어줌
지원 메소드 예시
- <S extends T> save(S entity) - 엔티티 저장 및 수정
- void delete(T entity) - 엔티티 삭제
- count() - 엔티티 총 개수 반환
- Iterable<T> findAll() - 모든 엔티티 조회
application-test.properties
# Datasource 설정 spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:test spring.datasource.username=sa spring.datasource.password= # H2 데이터베이스 방언 설정 spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
테스트 환경의 H2 데이터베이스를 사용하기 위한 파일 작성. 테스트 환경을 위한 별도의 properties
com.shop.repository.ItemRepositoryTest.java
package com.shop.repository; import com.shop.constant.ItemSellStatus; import com.shop.entity.Item; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import java.time.LocalDateTime; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @TestPropertySource(locations = "classpath:application-test.properties") class ItemRepositoryTest { @Autowired ItemRepository itemRepository; @Test @DisplayName("상품 저장 테스트") public void createItemTest(){ Item item = new Item(); item.setItemNm("테스트 상품"); item.setPrice(10000); item.setItemDetail("테스트 상품 상세 설명"); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); Item savedItem = itemRepository.save(item); System.out.println(savedItem.toString()); } }
ItemRepository를 테스트하는 코드
쿼리 메소드
대표적으로 find가 있다.
find + (엔티티 이름. 생략 가능) + By + 변수이름
com.shop.repository.ItemRepository.java
package com.shop.repository; import com.shop.entity.Item; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface ItemRepository extends JpaRepository<Item, Long> { //상품명으로 데이터 조회 find + By + 변수이름 List<Item> findByItemNm(String itemNm); //상품명과 상품 상세 설명을 OR 조건을 이용하여 조회 List<Item> findByItemNmOrItemDetail(String itemNm, String itemDetail); //파라미터로 넘어온 price 변수보다 값이 작은 상품 데이터를 조회 List<Item> findByPriceLessThan(Integer price); //OrderBy로 정렬처리하기 - 내림차순 - OrderBy + 속성명 + Desc List<Item> findByPriceLessThanOrderByPriceDesc(Integer price); }
com.shop.repository.ItemRepositoryTest.java
package com.shop.repository; import com.shop.constant.ItemSellStatus; import com.shop.entity.Item; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import sun.management.counter.StringCounter; import java.time.LocalDateTime; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @TestPropertySource(locations = "classpath:application-test.properties") class ItemRepositoryTest { @Autowired ItemRepository itemRepository; @Test @DisplayName("상품 저장 테스트") public void createItemTest(){ Item item = new Item(); item.setItemNm("테스트 상품"); item.setPrice(10000); item.setItemDetail("테스트 상품 상세 설명"); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); Item savedItem = itemRepository.save(item); System.out.println(savedItem.toString()); } public void createItemList(){ for(int i=1; i<=10; i++){ Item item = new Item(); item.setItemNm("테스트 상품" + i); item.setPrice(10000 + i); item.setItemDetail("테스트 상품 상세 설명"+ i); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); Item savedItem = itemRepository.save(item); } } @Test @DisplayName("상품명 조회 테스트") public void findByItemNmTest(){ this.createItemList(); List<Item> itemList = itemRepository.findByItemNm("테스트 상품1"); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("상품명, 상품상세설명 or 테스트") public void findByItemNmOrItemDetailTest(){ this.createItemList(); List<Item> itemList = itemRepository.findByItemNmOrItemDetail("테스트 상품1", "테스트 상품 상세 설명5"); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("가격 LessThan 테스트") public void findByPriceLessThanTest(){ this.createItemList(); List<Item> itemList = itemRepository.findByPriceLessThan(10005); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("가격 내림차순 조회 테스트") public void findByPriceLessThanOrderByPriceDesc(){ this.createItemList(); List<Item> itemList = itemRepository.findByPriceLessThanOrderByPriceDesc(10005); for(Item item : itemList){ System.out.println(item.toString()); } } }
@Query
- SQL과 유사한 JPQL이라는 객체지향 쿼리 언어를 통해 복잡한 쿼리도 처리 가능
- SQL의 경우 데이터베이스의 테이블을 대상으로 쿼리를 수행하고, JPQL은 엔티티 객체를 대상으로 쿼리 수행
- SQL을 추상화해서 사용하기 때문에 특정 DB SQL에 의존하지 않기 때문에 데이터베이스가 변경되어도 애플리케이션이 영향을 받지 않음
com.shop.repository.ItemRepository.java
package com.shop.repository; import com.shop.entity.Item; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; public interface ItemRepository extends JpaRepository<Item, Long> { //상품명으로 데이터 조회 find + By + 변수이름 List<Item> findByItemNm(String itemNm); //상품명과 상품 상세 설명을 OR 조건을 이용하여 조회 List<Item> findByItemNmOrItemDetail(String itemNm, String itemDetail); //파라미터로 넘어온 price 변수보다 값이 작은 상품 데이터를 조회 List<Item> findByPriceLessThan(Integer price); //OrderBy로 정렬처리하기 - 내림차순 - OrderBy + 속성명 + Desc List<Item> findByPriceLessThanOrderByPriceDesc(Integer price); // JPQL 로 작성한 쿼리문 // @Param을 이용하여 파라미터로 넘어온 값을 JPQL에 들어갈 변수로 지정해줄 수 있음 // 현재는 itemDetail 변수를 "like % %" 사이에 ":itemDetail"로 값이 들어가도록 작성 @Query("select i from Item i where i.itemDetail like %:itemDetail% order by i.price desc") List<Item> findByItemDetail(@Param("itemDetail") String itemDetail); @Query(value = "select * from item i where i.item_detail like %:itemDetail% order by i.price desc", nativeQuery = true) List<Item> findByItemDetailByNative(@Param("itemDetail") String itemDetail); }
- 기존의 데이터베이스에서 사용하던 쿼리를 그대로 사용해야 할 때 nativeQuery 사용가능
- 하지만 특정 데베에 종속되는 쿼리문을 사용하기 때문에 데이터베이스에 대해 독립적이라는 장점을 잃어버림
- 기존에 작성한 통계용 쿼리처럼 복잡한 쿼리를 그대로 사용해야 하는 경우 활용
com.shop.repository.ItemRepositoryTest.java
package com.shop.repository; import com.shop.constant.ItemSellStatus; import com.shop.entity.Item; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import sun.management.counter.StringCounter; import java.time.LocalDateTime; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @TestPropertySource(locations = "classpath:application-test.properties") class ItemRepositoryTest { @Autowired ItemRepository itemRepository; @Test @DisplayName("상품 저장 테스트") public void createItemTest(){ Item item = new Item(); item.setItemNm("테스트 상품"); item.setPrice(10000); item.setItemDetail("테스트 상품 상세 설명"); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); Item savedItem = itemRepository.save(item); System.out.println(savedItem.toString()); } public void createItemList(){ for(int i=1; i<=10; i++){ Item item = new Item(); item.setItemNm("테스트 상품" + i); item.setPrice(10000 + i); item.setItemDetail("테스트 상품 상세 설명"+ i); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); Item savedItem = itemRepository.save(item); } } @Test @DisplayName("상품명 조회 테스트") public void findByItemNmTest(){ this.createItemList(); List<Item> itemList = itemRepository.findByItemNm("테스트 상품1"); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("상품명, 상품상세설명 or 테스트") public void findByItemNmOrItemDetailTest(){ this.createItemList(); List<Item> itemList = itemRepository.findByItemNmOrItemDetail("테스트 상품1", "테스트 상품 상세 설명5"); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("가격 LessThan 테스트") public void findByPriceLessThanTest(){ this.createItemList(); List<Item> itemList = itemRepository.findByPriceLessThan(10005); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("가격 내림차순 조회 테스트") public void findByPriceLessThanOrderByPriceDesc(){ this.createItemList(); List<Item> itemList = itemRepository.findByPriceLessThanOrderByPriceDesc(10005); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("@Query를 이용한 상품 조회 테스트") public void findByItemDetailTest(){ this.createItemList(); List<Item> itemList = itemRepository.findByItemDetail("테스트 상품 상세 설명"); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("nativeQuery를 이용한 상품 조회 테스트") public void findByItemDetailByNative(){ this.createItemList(); List<Item> itemList = itemRepository.findByItemDetailByNative("테스트 상품 상세 설명"); for(Item item : itemList){ System.out.println(item.toString()); } } }
Querydsl
- 고정된 SQL 문이 아닌 조건에 맞게 동적으로 쿼리 생성
- 비슷한 쿼리를 재사용할 수 있으며 제약 조건 조립 및 가독성 향상
- 문자열이 아닌 자바 소스코드로 작성하기 때문에 컴파일 시점에 오류 발견
- IDE의 도움을 받아서 자동 완성 기능을 이용할 수 있기 때문에 생산성 향상
pom.xml
<dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> <version>5.0.0</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <version>5.0.0</version> </dependency>
Querydsl을 사용하기 위해 의존성 추가
Reload 하여 라이브러리 다운받기 전까지 빨간글씨로 뜨니 당황하지 않는다.
<plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.1.3</version> <executions> <execution> <goals> <goal>process</goal> </goals> <configuration> <outputDirectory>target/generated-sources/java </outputDirectory> <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor </processor> </configuration> </execution> </executions> </plugin>
Qdomain이라는 자바 코드를 생성하는 플러그인 추가
Reload 하여 라이브러리 다운받기 전까지 빨간글씨로 뜨니 당황하지 않는다.
com.shop.repository.ItemRepositoryTest.java
class ItemRepositoryTest { // 영속성 컨테스트를 사용하기 위해 EntitiyManager 빈 주입 @PersistenceContext EntityManager em; public void createItemList(){ for(int i=1; i<=10; i++){ Item item = new Item(); item.setItemNm("테스트 상품" + i); item.setPrice(10000 + i); item.setItemDetail("테스트 상품 상세 설명"+ i); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); Item savedItem = itemRepository.save(item); } } @Test @DisplayName("Querydsl 조회 테스트") public void QuerydslTest(){ this.createItemList(); // JPAQueryFactory를 이용하여 쿼리 동적 생성, 파라미터로는 엔티티매니저의 객체 JPAQueryFactory queryFactory = new JPAQueryFactory(em); // Querydsl을 통해 쿼리를 생성하기 위해 자동으로 생성된 Qitem 객체 이용 QItem qItem = QItem.item; // JAVA 소스코드지만 sql문과 비슷하게 작성 JPAQuery<Item> query = queryFactory.selectFrom(qItem) .where(qItem.itemSellStatus.eq(ItemSellStatus.SELL)) .where(qItem.itemDetail.like("%" + "테스트 상품 상세 설명" + "%")) .orderBy(qItem.price.desc()); // JPAQuery 메소드중 하나인 fetch를 이용해서 쿼리 결과를 리스트로 반환 List<Item> itemList = query.fetch(); for(Item item : itemList){ System.out.println(item.toString()); } } }
com.shop.repository.ItemRepository.java
public interface ItemRepository extends JpaRepository<Item, Long>, QuerydslPredicateExecutor<Item>
Repository에 Predicate를 파라미터로 전달하기 위해 QuerydslPredicateExecutor 인터페이스 상속 추가
Predicate란 '이 조건이 맞다' 고 판단하는 근거를 함수로 제공하는 것
QuerydslPredicateExecutor 인터페이스 정의 메소드
- long count(Predicate) - 조건에 맞는 데이터의 총 개수 반환
- boolean exists(Predicate) - 조건에 맞는 데이터 존재 여부 반환
- Iterable findAll(Predicate) - 조건에 맞는 모든 데이터 반환
- Page<T> findAll(Predicate, Pageable) - 조건에 맞는 페이지 데이터 반환
- Iterable findAll(Predicate, Sort) - 조건에 맞는 정렬된 데이터 반환
- T findOne(Predicate) - 조건에 맞는 데이터 1개 반환
com.shop.repository.ItemRepositoryTest.java
package com.shop.repository; import com.querydsl.core.BooleanBuilder; import com.querydsl.jpa.JPQLQueryFactory; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import com.shop.constant.ItemSellStatus; import com.shop.entity.Item; import com.shop.entity.QItem; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.test.context.TestPropertySource; import org.thymeleaf.util.StringUtils; import sun.management.counter.StringCounter; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; import static com.shop.entity.QItem.item; import static org.junit.jupiter.api.Assertions.*; import static sun.misc.MessageUtils.where; @SpringBootTest @TestPropertySource(locations = "classpath:application-test.properties") class ItemRepositoryTest { // 영속성 컨테스트를 사용하기 위해 EntitiyManager 빈 주입 @PersistenceContext EntityManager em; @Autowired ItemRepository itemRepository; //1~5는 sell 상태, 6~10은 sold_out 세팅 public void createItemList2(){ for(int i=1; i<=5; i++){ Item item = new Item(); item.setItemNm("테스트 상품" + i); item.setPrice(10000 + i); item.setItemDetail("테스트 상품 상세 설명"+ i); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); itemRepository.save(item); } for(int i=6; i<=10; i++){ Item item = new Item(); item.setItemNm("테스트 상품" + i); item.setPrice(10000 + i); item.setItemDetail("테스트 상품 상세 설명"+ i); item.setItemSellStatus(ItemSellStatus.SOLD_OUT); item.setStockNumber(0); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); itemRepository.save(item); } } @Test @DisplayName("상품 Querydsl 조회 테스트 2") public void queryDslTest2(){ this.createItemList2(); //쿼리에 들어갈 조건을 만들어주는 빌더, Predicate를 구현하고 있으며 메소드 체인 형식으로 사용가능 BooleanBuilder booleanBuilder = new BooleanBuilder(); QItem Item = item; String itemDetail = "테스트 상품 상세 설명"; int price = 10003; String itemSellStat = "SELL"; //필요한 상품을 조회하는데 필요한 and 조건 추가 booleanBuilder.and(item.itemDetail.like("%" + itemDetail + "%")); booleanBuilder.and(item.price.gt(price)); //상품의 판매상태가 SELL일 때만 booleanBuilder에 판매상태 조건을 동적으로 추가 if(StringUtils.equals(itemSellStat, ItemSellStatus.SELL)) { booleanBuilder.and(item.itemSellStatus.eq(ItemSellStatus.SELL)); } //데이터를 페이징해 조회하도록 PageRequest.of()메소드를 이용해 Pageable 객체를 생성 //첫 번째 인자는 조회할 페이지의 번호, 두 번째 인자는 한 페이지당 조회할 데이터의 개수 Pageable pageable = PageRequest.of(0, 5); Page<Item> itemPagingResult = // QueryDslPredicateExecutor 인터페이스에서 정의한 findAll() 메소드를 이용해 조건에 맞는 데이터를 // page 객체로 받아옵니다. itemRepository.findAll(booleanBuilder, pageable); System.out.println("total elements : " + itemPagingResult. getTotalElements ()); List<Item> resultItemList = itemPagingResult.getContent(); for(Item resultItem : resultItemList){ System.out.println(resultItem.toString()); } } }
'SpringBoot > 쇼핑몰 프로젝트 with JPA' 카테고리의 다른 글
[백견불야일타] 6장 상품 등록 및 조회하기 (0) 2022.10.08 [백견불야일타] 5장 연간 관계 매핑 (1) 2022.09.20 [백견불야일타] 4장 스프링 시큐리티를 이용한 회원 가입 및 로그인 (0) 2022.09.07 [백견불야일타] 3장 Thymeleaf 학습하기 (0) 2022.08.26 [백견불여일타] 1장 개발환경구축 (0) 2022.08.16