JPQL์ด๋ JPA Query Language์ ์ค์๋ง๋ก, JPA์์ ์ฌ์ฉํ ์ ์๋ ์ฟผ๋ฆฌ์ด๋ค.
SQL๊ณผ ๋ฌธ๋ฒ์ด ๋งค์ฐ ๋น์ทํ๋ค. SQL๊ณผ์ ์ฐจ์ด์ ์ SQL์์๋ ํ ์ด๋ธ์ด๋ ์ปฌ๋ผ์ ์ด๋ฆ์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋ฌ๋ฆฌ JPQL์ ๋ค์๊ณผ ๊ฐ์ด ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ์ํํ๋ ์ฟผ๋ฆฌ์ด๊ธฐ์ ๋งคํ๋ ์ํฐํฐ์ ์ด๋ฆ๊ณผ ํ๋์ ์ด๋ฆ์ ์ฌ์ฉํ๋ค๋ ๊ฒ์ด๋ค.
SELECT p FROM Product(์ํฐํฐ ํ์
) p WHERE p.number(์ํฐํฐ ์์ฑ) = ?1;
๋ฆฌํฌ์งํ ๋ฆฌ๋ JpaRepository๋ฅผ ์์๋ฐ๋ ๊ฒ๋ง์ผ๋ก๋ ๋ค์ํ CRUD ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค.
ํ์ง๋ง ์ด๋ฌํ ๊ธฐ๋ณธ ๋ฉ์๋๋ค์ ์๋ณ์ ๊ธฐ๋ฐ์ผ๋ก ์์ฑ๋๊ธฐ์ ๋ณ๋์ ๋ฉ์๋๋ฅผ ์ ์ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ์ด๋ ๊ฐ๋จํ ์ฟผ๋ฆฌ๋ฌธ ์์ฑ์ ์ํด ์ฌ์ฉ๋๋ ๊ฒ์ด ์ฟผ๋ฆฌ ๋ฉ์๋์ด๋ค.
'find...By', 'exist...By'์ ๊ฐ์ ํค์๋๋ก ์ฟผ๋ฆฌ์ ์ฃผ์ ๋ฅผ ์ ํ๋ค.
'By'๋ ์์ ์ด์ ์์์ ๋ํ๋ด๋ ๊ตฌ๋ถ์ ์ญํ ์ด๋ค.
์์ ์ด ๋ถ๋ถ์ ๊ฒ์ ๋ฐ ์ ๋ ฌ ์กฐ๊ฑด์ ์ง์ ํ๋ ์์ญ์ด๋ค. ์ํฐํฐ ์์ฑ์ ์ ์ํ๊ณ , AND๋ OR์ ์ฌ์ฉํด ์กฐ๊ฑด ํ์ฅ์ด ๊ฐ๋ฅํ๋ค.
์) (๋ฆฌํดํ์ ) + {์ฃผ์ +์์ ์ด(์์ฑ)} ๊ตฌ์กฐ์ ๋ฉ์๋
List<Person> findByLastnameAndEmail(String lastName, String email);
์์ ์ด์ ๋ค์ด๊ฐ๋ ์ํฐํฐ ์์ฑ ์(Expression)์ ์์ ๊ฐ์ด ์ํฐํฐ์์ ๊ด๋ฆฌํ๊ณ ์๋ ์์ฑ(ํ๋)๋ง ์ฐธ์กฐ๊ฐ ๊ฐ๋ฅํ๋ค.
์กฐํํ๋ ๊ธฐ๋ฅ์ ์ํํ๋ ํค์๋์ด๋ค. '...'์ผ๋ก ํ์ํ ์์ญ์๋ ๋๋ฉ์ธ(์ํฐํฐ)์ ํํํ๋ค. ๊ทธ๋ฌ๋ ๋ฆฌํฌ์งํ ๋ฆฌ์์ ์ด๋ฏธ ๋๋ฉ์ธ์ ์ค์ ํ ํ์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ค๋ณต์ผ๋ก ํ๋จํด ์๋ตํ๊ธฐ๋ ํ๋ค. ๋ฆฌํด ํ์ ์ผ๋ก Collection์ด๋ Stream์ ์ํ ํ์ ํ์ ์ค์ ์ด ๊ฐ๋ฅํ๋ค.
// ๋ฆฌํฌ์งํ ๋ฆฌ์ ์ฟผ๋ฆฌ ๋ฉ์๋ ์์ฑ
Optional<Product> findByNumber(Long number);
List<Product> findAllByName(String name);
Product queryByNumber(Long number);
ํน์ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ๋์ง ํ์ธํ๋ ํค์๋์ด๋ค. ๋ฆฌํด ํ์ ์ผ๋ก๋ boolean์ ์ฌ์ฉํ๋ค.
boolean existsByNumber(Long number);
์กฐํ ์ฟผ๋ฆฌ๋ฅผ ์ํํ ํ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ก ๋์จ ๋ ์ฝ๋ ๊ฐ์๋ฅผ ๋ฆฌํดํ๋ค.
long countByName(String name);
์ญ์ ์ฟผ๋ฆฌ๋ฅผ ์ํํ๋ค. ๋ฆฌํด ํ์ ์ด ์๊ฑฐ๋ ์ญ์ ํ ํ์๋ฅผ ๋ฆฌํดํ๋ค.
void deleteByNumber(Long number);
long removeByName(String name);
์ฟผ๋ฆฌ๋ฅผ ํตํด ์กฐํ๋ ๊ฒฐ๊ด๊ฐ์ ๊ฐ์๋ฅผ ์ ํํ๋ ํค์๋์ด๋ค. ๋ ํค์๋๋ ๋์ผํ ๋์์ ์ํํ๋ค. ์ฃผ์ ์ By ์ฌ์ด์ ์์นํ๋ค. ์ผ๋ฐ์ ์ผ๋ก ํ ๋ฒ์ ๋์์ผ๋ก ์ฌ๋ฌ ๊ฑด์ ์กฐํํ ๋ ์ฌ์ฉํ๊ณ , ๋จ ๊ฑด ์กฐํ๋ <number.>๋ฅผ ์๋ตํ๋ค.
List<Product> findFirst5ByName(String name);
List<Product> findTop100ByName(String name);
JPQL์ ์์ ์ด ๋ถ๋ถ์์ ์ฌ์ฉํ๋ค.
๊ฐ์ ์ผ์น๋ฅผ ์กฐ๊ฑด์ผ๋ก ์ฌ์ฉํ๋ ์กฐ๊ฑด์ ํค์๋์ด๋ค. ์๋ต๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ผ๋ฉฐ Equals์ ๋์ผํ ๊ธฐ๋ฅ์ ์ํํ๋ค.
// findByNumber ๋ฉ์๋์ ๋์ผํ๊ฒ ๋์
Product findByNumberIs(Long number);
Product findByNumberEquals(Long number);
๊ฐ์ ๋ถ์ผ์น๋ฅผ ์กฐ๊ฑด์ผ๋ก ์ฌ์ฉํ๋ ์กฐ๊ฑด์ ํค์๋์ด๋ค. Is๋ ์๋ตํ๊ณ Not ํค์๋๋ง ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
Product findByNumberIsNot(Long number);
Product findByNumberNot(Long number);
๊ฐ์ด null์ธ์ง ๊ฒ์ฌํ๋ ์กฐ๊ฑด์ ํค์๋์ด๋ค.
List<Product> findByUpdatedAtNull();
List<Product> findByUpdatedAtIsNull();
List<Product> findByUpdatedAtNotNull();
List<Product> findByUpdatedAtIsNotNull();
boolean ํ์ ์ผ๋ก ์ง์ ๋ ์นผ๋ผ๊ฐ์ ํ์ธํ๋ ํค์๋์ด๋ค.
Product findByisActiveTrue();
Product findByisActiveIsTrue();
Product findByisActiveFalse();
Product findByisActiveIsFalse();
์ฌ๋ฌ ์กฐ๊ฑด์ ๋ฌถ์ ๋ ์ฌ์ฉํ๋ค.
Product findByNumberAndName(Long number, String name);
Product findByNumberOrName(Long number, String name);
์ซ์๋ datetime ์ปฌ๋ผ์ ๋์์ผ๋ก ํ ๋น๊ต ์ฐ์ฐ์ ์ฌ์ฉํ ์ ์๋ ์กฐ๊ฑด์ ํค์๋์ด๋ค.
GreaterThan, LessThan ํค์๋๋ ๋น๊ต ๋์์ ๋ํ ์ด๊ณผ/๋ฏธ๋ง์ ๊ฐ๋
์ผ๋ก ๋น๊ต ์ฐ์ฐ์ ์ํํ๋ค. ๊ฒฝ๊ณ๊ฐ์ ํฌํจํ๋ ค๋ฉด Equal ํค์๋๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค.
List<Product> findByPriceIsGreaterThan(Long price);
List<Product> findByPriceGreaterThan(Long price);
List<Product> findByPriceGreaterThanEqual(Long price);
List<Product> findByPriceIsLessThan(Long price);
List<Product> findByPriceLessThan(Long price);
List<Product> findByPriceLessThanEqual(Long price);
List<Product> findByPriceIsBetween(Long lowPrice, Long highPrice);
List<Product> findByPriceBetween(Long lowPrice, Long highPrice);
์ปฌ๋ผ๊ฐ์์ ์ผ๋ถ ์ผ์น ์ฌ๋ถ๋ฅผ ํ์ธํ๋ ์กฐ๊ฑด์ ํค์๋์ด๋ค.
SQL ์ฟผ๋ฆฌ๋ฌธ์์ ๊ฐ์ ์ผ๋ถ๋ฅผ ํฌํจํ๋ ๊ฐ์ ์ถ์ถํ ๋ ์ฌ์ฉํ๋ '%' ํค์๋์ ๋์ผํ ์ญํ ์ ํ๋ ํค์๋์ด๋ค.
์๋์ผ๋ก ์์ฑ๋๋ SQL๋ฌธ์ ๋ณด๋ฉด Containing ํค์๋๋ ๋ฌธ์์ด์ ์ ๋, StartingWith ํค์๋๋ ๋ฌธ์์ด์ ์, EndingWith ํค์๋๋ ๋ฌธ์์ด์ ๋์ '%'๊ฐ ๋ฐฐ์น๋๋ค. ์ฌ๊ธฐ์ ๋ณ๋๋ก ๊ณ ๋ คํด์ผ ํ๋ ํค์๋๋ Like ํค์๋์ธ๋ฐ, ์ด ํค์๋๋ ์ฝ๋ ์์ค์์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด์ ์ ๋ฌํ๋ ๊ฐ์ %๋ฅผ ๋ช
์์ ์ผ๋ก ์
๋ ฅํด์ผ ํ๋ค.
List<Product> findByNameLike(String name);
List<Product> findByNameIsLike(String name);
List<Product> findByNameContains(String name);
List<Product> findByNameContaining(String name);
List<Product> findByNameIsContaining(String name);
List<Product> findByNameStartsWith(String name);
List<Product> findByNameStartingWith(String name);
List<Product> findByNameIsStartingWith(String name);
List<Product> findByNameEndsWith(String name);
List<Product> findByNameEndingWith(String name);
List<Product> findByNameIsEndingWith(String name);
๊ธฐ๋ณธ ์ฟผ๋ฆฌ ๋ฉ์๋ ์์ฑ ํ OrderBy ํค์๋๋ฅผ ์ฝ์ ํ๋ค. ์ ๋ ฌํ๊ณ ์ ํ๋ ์นผ๋ผ๊ณผ ์ค๋ฆ์ฐจ์/๋ด๋ฆผ์ฐจ์์ ์ค์ ํ๋ค.
List<Product> findByNameOrderByNumberAsc(String name); // Asc : ์ค๋ฆ์ฐจ์ // ์ํ์ ๋ณด๋ฅผ ์ด๋ฆ์ผ๋ก ๊ฒ์ํ ํ ์ํ ๋ฒํธ๋ก ์ค๋ฆ์ฐจ์ ์ ๋ ฌ
List<Product> findByNameOrderByNumberDesc(String name); // Desc : ๋ด๋ฆผ์ฐจ์
์ฌ๋ฌ ๊ฐ์ ์กฐ๊ฑด ์ฌ์ฉ ์, And๋ Or ํค์๋๋ ์ฌ์ฉํ์ง ์๋๋ค.
List<Product> findByNameOrderByPriceAscStockDesc(String name); // ๋จผ์ Price๋ฅผ ๊ธฐ์ค์ผ๋ก ์ค๋ฆ์ฐจ์ ์ ๋ ฌ ํ ํ์์๋ก ์ฌ๊ณ ์๋์ ๊ธฐ์ค์ผ๋ก ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌ ์ํ
๋งค๊ฐ๋ณ์๋ฅผ ํ์ฉํ ์ฟผ๋ฆฌ ์ ๋ ฌ์ด๋ค.
List<Product> findByName(String name, Sort sort);
์ด๋ฆ์ ํค์๋๋ฅผ ๋ฃ์ง ์๊ณ Sort ๊ฐ์ฒด๋ฅผ ํ์ฉํด ๋งค๊ฐ๋ณ์๋ก ๋ฐ์๋ค์ธ ์ ๋ ฌ ๊ธฐ์ค์ ๊ฐ์ง๊ณ ์ฟผ๋ฆฌ๋ฌธ์ ์์ฑํ๋ค. ์ฟผ๋ฆฌ ๋ฉ์๋๋ฅผ ์ ์ํ๋ ๋จ๊ณ์์ ์ฝ๋๊ฐ ์ค์ด๋๋ ์ฅ์ ์ด ์๋ค.
@SpringBootTest
public class ProductRepositoryTest0 {
@Autowired
ProductRepository productRepository;
@Test
void sortingAndPagingTest() {
Product product1 = new Product();
product1.setName("ํ");
product1.setPrice(1000);
product1.setStock(100);
product1.setCreatedAt(LocalDateTime.now());
product1.setUpdatedAt(LocalDateTime.now());
Product product2 = new Product();
product2.setName("ํ");
product2.setPrice(5000);
product2.setStock(300);
product2.setCreatedAt(LocalDateTime.now());
product2.setUpdatedAt(LocalDateTime.now());
Product product3 = new Product();
product3.setName("ํ");
product3.setPrice(500);
product3.setStock(50);
product3.setCreatedAt(LocalDateTime.now());
product3.setUpdatedAt(LocalDateTime.now());
Product savedProduct1 = productRepository.save(product1);
Product savedProduct2 = productRepository.save(product2);
Product savedProduct3 = productRepository.save(product3);
productRepository.findByName("ํ", Sort.by(Sort.Order.asc("price")));
productRepository.findByName("ํ", Sort.by(Sort.Order.asc("price"), Sort.Order.desc("stock")));
}
}
Sort ๋ถ๋ถ์ ํ๋์ ๋ฉ์๋๋ก ๋ถ๋ฆฌํด ์ฟผ๋ฆฌ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ
...์๋จ ์ฝ๋ ๋์ผ, ์๋ต
productRepository.findByName("ํ", getSort());
}
private Sort getSort() {
return Sort.by(
Sort.Order.asc("price"),
Sort.Order.desc("stock")
);
}
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ ์ฝ๋๋ฅผ ๊ฐ์๋ก ๋๋ ํ์ด์ง๋ฅผ ๊ตฌ๋ถํ๋ ๊ฒ์ด๋ค.
ex) 25๊ฐ ๋ ์ฝ๋ -> ๋ ์ฝ๋๋ฅผ 7๊ฐ์ฉ, ์ด 4๊ฐ์ ํ์ด์ง๋ก ๊ตฌ๋ถ / ํน์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์จ๋ค.
JPA์์๋ ํ์ด์ง ์ฒ๋ฆฌ๋ฅผ ์ํด Page์ Pageable๋ฅผ ์ฌ์ฉํ๋ค.
// ํ์ด์ง ์ฒ๋ฆฌ๋ฅผ ์ํ ์ฟผ๋ฆฌ ๋ฉ์๋
Page<Product> findByName(String name, Pageable pageable);
๋ฆฌํด ํ์ ์ผ๋ก Page๋ฅผ ์ค์ ํ๊ณ ๋งค๊ฐ๋ณ์์๋ Pageable ํ์ ์ ๊ฐ์ฒด๋ฅผ ์ ์ํ๋ค.
// ํ์ด์ง ์ฟผ๋ฆฌ ๋ฉ์๋ ํธ์ถ
Page<Product> productPage = productRepository.findByName("ํ", PageRequest.of(0, 2));
๋ฆฌํด ํ์ ์ผ๋ก Page ๊ฐ์ฒด๋ฅผ ๋ฐ์์ผ ํ๊ธฐ์ Page<Product>๋ก ํ์ ์ ์ ์ธํด ๊ฐ์ฒด๋ฅผ ๋ฆฌํด์ ๋ฐ๋๋ค. Pageable ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๋ฌํ๊ธฐ ์ํด PageRequest ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ค. PageRequest๋ Pageable์ ๊ตฌํ์ฒด์ด๋ค.
์ผ๋ฐ์ ์ผ๋ก PageRequest๋ of ๋ฉ์๋๋ฅผ ํตํด PageRequest ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. of ๋ฉ์๋๋ ๋งค๊ฐ๋ณ์์ ๋ฐ๋ผ ๋ค์ํ ํํ๋ก ์ค๋ฒ๋ก๋ฉํ๋ค.
Page ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ์ถ๋ ฅํ๋ฉด ํด๋น ๊ฐ์ฒด์ ๊ฐ์ ๋ณด์ฌ์ฃผ์ง ์๊ณ ๋ช ๋ฒ์งธ ํ์ด์ง์ ํด๋นํ๋์ง๋ง ํ์ธ์ด ๊ฐ๋ฅํ๋ค.
Page<Product> productPage = productRepository.findByName("ํ", PageRequest.of(0, 2));
System.out.println(productPage.getContent()); // ๋ฐฐ์ด ํํ๋ก ๊ฐ ์ถ๋ ฅ
@Query ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํด ์ง์ JPQL์ ์์ฑํ ์ ์๋ค.
@Query("SELECT p FROM Product AS p WHERE p.name = ?1")
List<Product> findByName(String name);
FROM ๋ค์์ ์ํฐํฐ ํ์ ์ ์ง์ ํ๋ค.
๋ณ์นญ์ ์์ฑํ๋ค. (AS ์๋ต ๊ฐ๋ฅ)
WHERE๋ฌธ์์๋ SQL๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ์กฐ๊ฑด์ ์ง์ ํ๋ค.
์กฐ๊ฑด๋ฌธ์์ ์ฌ์ฉํ '?1'์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๋ฌ๋ฐ๊ธฐ ์ํ ์ธ์์ ํด๋นํ๋ค.
1์ ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ฅผ ์๋ฏธํ๋ค.
ํ๋ผ๋ฏธํฐ์ ์์๊ฐ ๋ฐ๋๋ฉด ์ค๋ฅ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ด ์์ด ๋ค์๊ณผ ๊ฐ์ด @Param ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
@Query("SELECT p FROM Product AS p WHERE p.name = :name")
List<Product> findByNameParam(@Param("name") String name);
ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์ธ๋ฉํ๋ ๋ฐฉ์์ผ๋ก ๋ฉ์๋๋ฅผ ๊ตฌํํ๋ฉด ์ฝ๋์ ๊ฐ๋ ์ฑ์ด ๋์์ง๊ณ ์ ์ง๋ณด์๊ฐ ์์ํด์ง๋ค.
@Query๋ฅผ ์ฌ์ฉํ๋ฉด ์ํฐํฐ ํ์ ์ด ์๋๋ผ ์ํ๋ ์ปฌ๋ผ์ ๊ฐ๋ง ์ถ์ถ์ด ๊ฐ๋ฅํ๋ค.
@Query("SELECT p.name, p.price, p.stock FROM Product p WHERE p.name = :name")
List<Object[]> findByNameParam2(@Param("name") String name);
SELECT์ ๊ฐ์ ธ์ค๊ณ ์ ํ๋ ์ปฌ๋ผ์ ์ง์ ํ๋ค. ์ด๋ ๋ฉ์๋์์๋ Object ๋ฐฐ์ด์ ๋ฆฌ์คํธ ํํ๋ก ๋ฆฌํด ํ์ ์ ์ง์ ํด์ผ ํ๋ค.
๋ฉ์๋์ ์ด๋ฆ์ ๊ธฐ๋ฐ์ผ๋ก ์์ฑํ๋ JPQL์ ํ๊ณ๋ @Query ์ด๋
ธํ
์ด์
์ ํตํด ๋๋ถ๋ถ ํด์ํ ์ ์์ง๋ง ์ง์ ๋ฌธ์์ด์ ์
๋ ฅํ๊ธฐ ๋๋ฌธ์ ์ปดํ์ผ ์์ ์ ์๋ฌ๋ฅผ ์ก์ง ๋ชปํ๊ณ ๋ฐํ์ ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์๋ค.
์ฟผ๋ฆฌ์ ๋ฌธ์์ด์ด ์๋ชป๋ ๊ฒฝ์ฐ์๋ ์ ํ๋ฆฌ์ผ์ด์
์ด ์คํ๋ ํ ๋ก์ง์ด ์คํ๋๊ณ ๋์์ผ ์ค๋ฅ ๋ฐ๊ฒฌํ๊ฒ ๋๋ค.
์ด๋ฌํ ์ด์ ๋ก ๊ฐ๋ฐ ํ๊ฒฝ์์๋ ๋ฌธ์ ๊ฐ ์์ด ๋ณด์ด๋ค๊ฐ ์ค์ ์ด์ ํ๊ฒฝ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐฐํฌํ๊ณ ๋์ ์ค๋ฅ๊ฐ ๋ฐ๊ฒฌ๋๋ ๋ฆฌ์คํฌ๊ฐ ์๋ค.
์ด ๊ฐ์ ๋ฌธ์ ํด๊ฒฐ์ ์ํด QueryDSL ์ฌ์ฉํ๋ค.
QueryDSL์ ๋ฌธ์์ด์ด ์๋ ์ฝ๋๋ก ์ฟผ๋ฆฌ ์์ฑ์ ๋์์ค๋ค.
์ ์ ํ์ ์ ์ด์ฉํด SQL๊ณผ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋๋ก ์ง์ํ๋ ํ๋ ์์ํฌ. ๋ฌธ์์ด์ด๋ XML ํ์ผ์ ํตํด ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๋์ QueryDSL์ด ์ ๊ณตํ๋ Fluent API๋ฅผ ํ์ฉํด ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ค.
1. ์์กด์ฑ ์ถ๊ฐ
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
2. APT ํ๋ฌ๊ทธ์ธ ์ถ๊ฐ
<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>
<options>
<querydsl.entityAccessors>true</querydsl.entityAccessors>
</options>
</configuration>
</execution>
</executions>
</plugin>
3. ๋ฉ์ด๋ธ์ compile ๋จ๊ณ๋ฅผ ํด๋ฆญํด ๋น๋ ์์ ์ํ
๋น๋๊ฐ ์๋ฃ๋๋ฉด ์์์ ์์ฑํ๋ outputDirectory์ ์ค์ ํ generated-source ๊ฒฝ๋ก์ ๋ค์๊ณผ ๊ฐ์ด Q๋๋ฉ์ธ ํด๋์ค๊ฐ ์์ฑ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
4. IntelliJ์์ [File] -> [Project Structure]
[Mark as]์ [Sources] ๋๋ฌ IDE์์ ์์คํ์ผ๋ก ์ธ์ํ ์ ์๊ฒ ์ค์ ํ๋ค.
QueryDSL ์ ์ฉ์ด ์๋ฃ๋ ๋ชจ์ต์ด๋ค.
QueryDSL์ ์ง๊ธ๊น์ง ์์ฑํ๋ ์ํฐํฐ ํด๋์ค์ Q๋๋ฉ์ธ์ด๋ผ๋ ์ฟผ๋ฆฌ ํ์ ์ ํด๋์ค๋ฅผ ์์ฒด์ ์ผ๋ก ์์ฑํด์ ๋ฉํ๋ฐ์ดํฐ๋ก ์ฌ์ฉํ๋ค. ์ด๋ฅผ ํตํด SQL๊ณผ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํด ์ ๊ณตํ๋ค.
@PersistenceContext
EntityManager entityManager;
@Test
void queryDslTest() {
JPAQuery<Product> query = new JPAQuery(entityManager);
QProduct qProduct = QProduct.product;
List<Product> productList = query
.from(qProduct)
.where(qProduct.name.eq("ํ"))
.orderBy(qProduct.price.asc())
.fetch();
for (Product product : productList) {
System.out.println("----------------");
System.out.println();
System.out.println("Product Number : " + product.getNumber());
System.out.println("Product Name : " + product.getName());
System.out.println("Product Price : " + product.getPrice());
System.out.println("Product Stock : " + product.getStock());
System.out.println();
System.out.println("----------------");
}
}
List ํ์ ์ผ๋ก ๊ฐ์ ๋ฆฌํด ๋ฐ๊ธฐ ์ํด์๋ fetch() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค.
@Test
void queryDslTest2() {
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
QProduct qProduct = QProduct.product;
List<Product> productList = jpaQueryFactory.selectFrom(qProduct)
.where(qProduct.name.eq("ํ"))
.orderBy(qProduct.price.asc())
.fetch();
for (Product product : productList) {
System.out.println("----------------");
System.out.println();
System.out.println("Product Number : " + product.getNumber());
System.out.println("Product Name : " + product.getName());
System.out.println("Product Price : " + product.getPrice());
System.out.println("Product Stock : " + product.getStock());
System.out.println();
System.out.println("----------------");
}
}
๋ง์ฝ ์ ์ฒด ์ปฌ๋ผ์ ์กฐํํ์ง ์๊ณ ์ผ๋ถ๋ง ์กฐํํ๊ณ ์ถ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด selectFrom()์ด ์๋ select()์ from() ๋ฉ์๋๋ฅผ ๊ตฌ๋ถํด์ ์ฌ์ฉํ๋ฉด ๋๋ค.
@Test
void queryDslTest3() {
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
QProduct qProduct = QProduct.product;
// select ๋์์ด ํ๋์ธ ๊ฒฝ์ฐ
List<String> productList = jpaQueryFactory
.select(qProduct.name)
.from(qProduct)
.where(qProduct.name.eq("ํ"))
.orderBy(qProduct.price.asc())
.fetch();
for (String product : productList) {
System.out.println("----------------");
System.out.println("Product Name : " + product);
System.out.println("----------------");
}
// select ๋์์ด ์ฌ๋ฌ ๊ฐ์ผ ๊ฒฝ์ฐ (,)๋ก ๊ตฌ๋ถํด์ ์์ฑ
List<Tuple> tupleList = jpaQueryFactory
.select(qProduct.name, qProduct.price)
.from(qProduct)
.where(qProduct.name.eq("ํ"))
.orderBy(qProduct.price.asc())
.fetch();
for (Tuple product : tupleList) {
System.out.println("----------------");
System.out.println("Product Name : " + product.get(qProduct.name));
System.out.println("Product Name : " + product.get(qProduct.price));
System.out.println("----------------");
}
}
@Configuration
public class QueryDSLConfiguration {
@PersistenceContext
EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
JPAQueryFactory ๊ฐ์ฒด๋ฅผ @Bean ๊ฐ์ฒด๋ก ๋ฑ๋กํด ๋๋ฉด ๋งค๋ฒ JPAQueryFactory๋ฅผ ์ด๊ธฐํํ์ง ์๊ณ ์คํ๋ง ์ปจํ ์ด๋์์ ๊ฐ์ ธ๋ค ์ธ ์ ์๋ค.
@Autowired
JPAQueryFactory jpaQueryFactory;
@Test
void queryDSLTest4() {
QProduct qProduct = QProduct.product;
List<String> productList = jpaQueryFactory
.select(qProduct.name)
.from(qProduct)
.where(qProduct.name.eq("ํ"))
.orderBy(qProduct.price.asc())
.fetch();
for (String product : productList) {
System.out.println("----------------");
System.out.println("Product Name : " + product);
System.out.println("----------------");
}
}
์คํ๋ง ๋ฐ์ดํฐ JPA์์๋ QueryDSL์ ๋์ฑ ํธํ๊ฒ ์ฌ์ฉํ ์ ์๊ฒ QuerydslPredicateExecutor ์ธํฐํ์ด์ค์ QuerydslRepositorySupport ํด๋์ค๋ฅผ ์ ๊ณตํ๋ค.
JpaRepository์ ํจ๊ป ๋ฆฌํฌ์งํ ๋ฆฌ์์ QueryDSL์ ์ฌ์ฉํ ์ ์๊ฒ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ๋ค.
QuerydslPredicateExecutor๋ฅผ ์ฌ์ฉํ๋ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์์ฑํ๋ค.
public interface QProductRepository extends JpaRepository<Product, Long>, QuerydslPredicateExecutor<Product> {
}
QuerydslPredicateExecutor์์ ์ ๊ณตํ๋ ๋ฉ์๋
๋๋ถ๋ถ Predicate ํ์
์ ๋งค๊ฐ๋ณ์๋ก ๋ฐ๋๋ค.
Predicate๋ ํํ์์ ์์ฑํ ์ ์๊ฒ QueryDSL์์ ์ ๊ณตํ๋ ์ธํฐํ์ด์ค์ด๋ค.
@SpringBootTest
public class QProductRepositoryTest {
@Autowired
QProductRepository qProductRepository;
@Test
public void queryDSLTest1() {
Predicate predicate = QProduct.product.name.containsIgnoreCase("ํ")
.and(QProduct.product.price.between(1000, 2500));
Optional<Product> foundProduct = qProductRepository.findOne(predicate);
if (foundProduct.isPresent()) {
Product product = foundProduct.get();
System.out.println(product.getNumber());
System.out.println(product.getName());
System.out.println(product.getPrice());
System.out.println(product.getStock());
}
}
}
Predicate๋ ๊ฐ๋จํ๊ฒ ํํ์์ผ๋ก ์ ์ํ๋ ์ฟผ๋ฆฌ์ด๋ค.
์๋ Predicate๋ฅผ ๋ช
์์ ์ผ๋ก ์ ์ํ๊ณ ์ฌ์ฉํ์ง๋ง, ๋ค์๊ณผ ๊ฐ์ด ์์ ๋ถ๋ง ๊ฐ์ ธ๋ค ์ฌ์ฉํ ์๋ ์๋ค.
@Test
public void queryDSLTest2() {
QProduct qProduct = QProduct.product;
Iterable<Product> productList = qProductRepository.findAll(
qProduct.name.contains("ํ")
.and(qProduct.price.between(550, 1500))
);
for (Product product : productList) {
System.out.println(product.getNumber());
System.out.println(product.getName());
System.out.println(product.getPrice());
System.out.println(product.getStock());
}
}
QuerydslPredicateExecutor๋ฅผ ํ์ฉํ๋ฉด ๋์ฑ ํธํ๊ฒ QueryDSL์ ์ฌ์ฉํ ์ ์์ง๋ง join์ด๋ fetch ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๋ ๋จ์ ์ด ์กด์ฌํ๋ค.
๊ฐ์ฅ ๋ณดํธ์ ์ผ๋ก ์ฌ์ฉํ๋ ๋ฐฉ์์ CustomRepository๋ฅผ ํ์ฉํด ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ์์ด๋ค.
ProductRepositoryCustom
์ธํฐํ์ด์ค๋ฅผ ์์ฑํ๊ณ ์ฟผ๋ฆฌ๋ก ๊ตฌํํ๊ณ ์ ํ๋ ๋ฉ์๋๋ฅผ ์ ์ํ๋ค.
public interface ProductRepositoryCustom {
List<Product> findByName(String name);
}
ProductRepositoryCustom ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด์ธ ProductRepositoryCustomImpl ํด๋์ค๋ฅผ ์์ฑํ๋ค.
QueryDSL ์ฌ์ฉ์ ์ํด QuerydslRepositorySupport๋ฅผ ์์๋ฐ๊ณ ProductRepositoryCustom ์ธํฐํ์ด์ค ๊ตฌํํ๋ค. QuerydslRepositorySupport๋ฅผ ์์๋ฐ์ผ๋ฉด ์์ฑ์๋ฅผ ํตํด ๋๋ฉ์ธ ํด๋์ค๋ฅผ ๋ถ๋ชจ ํด๋์ค์ ์ ๋ฌํด์ผ ํ๋ค.
์ธํฐํ์ด์ค์ ์ ์ํ ๋ฉ์๋๋ฅผ ๊ตฌํํ๋ค.
@Component
public class ProductRepositoryCustomImpl extends QuerydslRepositorySupport implements ProductRepositoryCustom {
public ProductRepositoryCustomImpl() {
super(Product.class);
}
// ์ธํฐํ์ด์ค์ ์ ์ํ ๋ฉ์๋ ๊ตฌํ
@Override
public List<Product> findByName(String name) {
QProduct product = QProduct.product;
List<Product> productList = from(product)
.where(product.name.eq(name))
.select(product)
.fetch();
return productList;
}
}
ProductRepository ์ธํฐํ์ด์ค
๊ธฐ์กด์ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์์ฑํ๋ ๊ฒ๊ณผ ๋์ผํ๊ฒ JpaRepository๋ฅผ ์์๋ฐ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก JpaRepository์์ ์ ๊ณตํ๋ ๋ฉ์๋๋ ์ฌ์ฉํ ์ ์๊ณ , ๋ณ๋๋ก ProductRepositoryCustom ์ธํฐํ์ด์ค์์ ์ ์ํ ๋ฉ์๋๋ ๊ตฌํ์ฒด๋ฅผ ํตํด ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
@Repository("productRepositorySupport")
public interface ProductRepository extends JpaRepository<Product, Long>, ProductRepositoryCustom {
}
@SpringBootTest
public class ProductRepositoryTest {
@Autowired
ProductRepository productRepository;
@Test
void findByNameTest() {
List<Product> productList = productRepository.findByName("ํ");
for (Product product : productList) {
System.out.println(product.getNumber());
System.out.println(product.getName());
System.out.println(product.getPrice());
System.out.println(product.getStock());
}
}
}
๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ๊ตฌ์ฑํ๋ฉด์ ๋ชจ๋ ๋ก์ง์ ๊ตฌํํ๊ธฐ ๋๋ฌธ์ findByName() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ๋๋ ์์ ๊ฐ์ด ๊ฐ๋จํ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค.
JPA์์ 'Audit'์ด๋ '๊ฐ์ํ๋ค'๋ผ๋ ๋ป
๊ฐ ๋ฐ์ดํฐ๋ง๋ค '๋๊ฐ', '์ธ์ ' ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๊ณ ๋ณ๊ฒฝํ๋์ง ๊ฐ์ํ๋ค๋ ์๋ฏธ
๋ํ์ ์ผ๋ก ๋ง์ด ์ฌ์ฉ๋๋ ํ๋
์ด๋ฌํ ํ๋๋ค์ ๋งค๋ฒ ์ํฐํฐ ์์ฑ ๋๋ ๋ณ๊ฒฝ ๋๋ง๋ค ๊ฐ์ ์ฃผ์ ํด์ผ ํ๋ ๋ฒ๊ฑฐ๋ก์. ์ด๋ฅผ ํด์ํ๊ธฐ ์ํด Spring Data JPA์์ ์ด๋ฌํ ๊ฐ์ ์๋์ผ๋ก ๋ฃ์ด์ฃผ๋ ๊ธฐ๋ฅ ์ ๊ณต.
@SpringBootApplication
@EnableJpaAuditing
public class JpaApplication {
public static void main(String[] args) {
SpringApplication.run(JpaApplication.class, args);
}
}
OR
@Configuration
@EnableJpaAuditing
public class JpaAuditingConfiguration {
}
์ฝ๋์ ์ค๋ณต์ ์์ ๊ธฐ ์ํด ๊ฐ ์ํฐํฐ์ ๊ณตํต์ผ๋ก ๋ค์ด๊ฐ๊ฒ ๋๋ ์ปฌ๋ผ(ํ๋)์ ํ๋์ ํด๋์ค๋ก ๋นผ๋ ์์ ์ ์ํํ๋ค.
@Getter
@Setter
@ToString
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt; // ์์ฑ ์ผ์
@LastModifiedDate
private LocalDateTime updatedAt; // ๋ณ๊ฒฝ ์ผ์
}
์์ ๊ฐ์ด BaseEntity๋ฅผ ์์ฑํ๊ณ Product ์ํฐํฐ ํด๋์ค์์ ๊ณตํต ์ปฌ๋ผ์ ์ ๊ฑฐํด์ ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ ํ๋ค.
@Entity
@Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Table(name = "product")
public class Product extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long number;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer price;
@Column(nullable = false)
private Integer stock;
}
@ToString, @EqualAndHashCode ์ด๋ ธํ ์ด์ ์ ์ ์ฉํ callSuper ์์ฑ์ ๋ถ๋ชจ ํด๋์ค์ ํ๋๋ฅผ ํฌํจํ๋ ์ญํ ์ ์ํํ๋ค.
์ด๋ ๊ฒ ์ค์ ํ๋ฉด ๋งค๋ฒ LocalDateTime.now() ๋ฉ์๋๋ฅผ ์ฌ์ฉํด ์๊ฐ์ ์ฃผ์ ํ์ง ์์๋ ์๋์ผ๋ก ๊ฐ์ด ์์ฑ๋๋ค.
@Test
public void auditingTest() {
Product product = new Product();
product.setName("ํ");
product.setPrice(1000);
product.setStock(100);
Product savedProduct = productRepository.save(product);
System.out.println("productName : " + savedProduct.getName());
System.out.println("createdAt : " + savedProduct.getCreatedAt());
}
// ์ถ๋ ฅ ๊ฒฐ๊ณผ
productName : ํ
createdAt : 2023-11-20T16:13:13.546600200
Product ์ํฐํฐ์ ์์ฑ์ผ์๋ฅผ ์ ๋ ฅํ์ง ์์์ง๋ง ๋ฐ์ดํฐ๋ฒ ์ด์ค์๋ ์์ฑ์ผ์๊ฐ ์ ์ฅ๋๋ฉฐ, ์ํฐํฐ์ ํ๋๋ฅผ ์ถ๋ ฅํด ๋ณด๋ฉด ํด๋น ์๊ฐ์ด ์ถ๋ ฅ๋จ.
1. JPA์์ ์ฌ์ฉํ ์ ์๋ ์ฟผ๋ฆฌ๋ฅผ ( JPQL )์ด๋ผ๊ณ ํ๋ค.
2. ( find...By )์/๋ ์กฐํํ๋ ๊ธฐ๋ฅ์ ์ํํ๋ ์ฟผ๋ฆฌ ๋ฉ์๋์ ์ฃผ์ ํค์๋์ด๋ค.
3. ( ํ์ด์ง )์/๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ ์ฝ๋๋ฅผ ๊ฐ์๋ก ๋๋ ํ์ด์ง๋ฅผ ๊ตฌ๋ถํ๋ ๊ฒ์ด๋ค.
4. ( @Query ) ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ฉด ์ง์ JPQL์ ์์ฑํ ์ ์๋ค.
5. ( QueryDSL )์/๋ ์ ์ ํ์ ์ ์ด์ฉํด SQL๊ณผ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋๋ก ์ง์ํ๋ ํ๋ ์์ํฌ์ด๋ค.
6. JPA Auditing ๊ธฐ๋ฅ์ ํ์ฑํํ๋ ๋ฐฉ๋ฒ์ผ๋ก๋ main() ๋ฉ์๋๊ฐ ์๋ ํด๋์ค์ ( @EnableJpaAuditing ) ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์ด ์๋ค.
7. ( @CreatedDate )์/๋ ๋ฐ์ดํฐ ์์ฑ ๋ ์ง๋ฅผ ์๋์ผ๋ก ์ฃผ์ ํด ์ฃผ๋ ์ด๋ ธํ ์ด์ ์ด๋ค.
1. Person ์ํฐํฐ ๋ฆฌ์คํธ๋ฅผ ๋ฆฌํดํ๋ ์ฟผ๋ฆฌ ๋ฉ์๋๋ฅผ ์์ฑํ๋ผ. ์ด ๋ฉ์๋๋ age ์์ฑ๊ณผ city ์์ฑ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์กฐํํด์ผ ํ๋ค.
2. Person ์ํฐํฐ ๋ฆฌ์คํธ๋ฅผ ๋ฆฌํดํ๋ ์ฟผ๋ฆฌ ๋ฉ์๋๋ฅผ ์์ฑํ๋ผ. ์ด ๋ฉ์๋๋ firstname ์์ฑ๊ณผ phone ์์ฑ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์กฐํํด์ผ ํ๋ค.
1๋ฒ ๋ต
List<Person> findByAgeAndCity(int age, String city);
2๋ฒ ๋ต
List<Person> findByFirstnameAndPhone(String firstname, String phone);
[์คํ๋ง 1ํ] 7์ฅ. ํ ์คํธ ์ฝ๋ ์์ฑํ๊ธฐ (0) | 2024.12.27 |
---|---|
[์คํ๋ง 1ํ] 6์ฅ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๋ (0) | 2024.11.29 |
[์คํ๋ง 1ํ] 5์ฅ~6.5์ฅ. API ์์ฑ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๋ (0) | 2024.11.22 |
[์คํ๋ง 1ํ] 1์ฅ~4์ฅ. ์คํ๋ง ๋ถํธ ๊ฐ๋ฐ ํ๊ฒฝ๊ณผ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐํ๊ธฐ (0) | 2024.11.15 |
[์คํ๋ง 1ํ] ์คํ๋ง ์ ๋ฌธ ์น์ 7~8 (0) | 2024.11.08 |