쌩로그

스프링 DB2편 - Ch05,06,07. 데이터 접근 기술 - JPA, 스프링 데이터 JPA, Querydsl 본문

Spring Project/Spring & Spring Boot

스프링 DB2편 - Ch05,06,07. 데이터 접근 기술 - JPA, 스프링 데이터 JPA, Querydsl

.쌩수. 2024. 8. 15. 23:48
반응형

목록

  1. 포스팅 개요
  2. 본론
      2-1. JPA 설정
      2-2. JPA 적용1 - 리포지토리 분석
      2-3. JPA 적용2 - 예외 변환
      2-4. 스프링 데이터 JPA 적용1
      2-5. 스프링 데이터 JPA 적용2
      2-6. Querydsl 설정
  3. 요약

1. 포스팅 개요

인프런에서 영한님의 스프링 DB 1편 Section 0507. 데이터 접근 기술 - JPAQuerydsl을 학습하며 정리한 포스팅이다.

참고로 영한님의 JPA 로드맵과 겹치는 부분 혹은 책에서 나오는 부분은 생략한다.

이미 JPA는 학습을 하고 책까지 보며, 정리를 한 시점이라, 복습차원에서 본다고 생각하고 편하게 들을 예정이다.

참고로 본론의 챕터는 기록할 만한 것만 했기 때문에 강의와는 분명히 다르다.

개념적인 부분이나 설정부분을 주로 다룰 것인데,

호~옥시나.. 보는 분이 혼동할 수 있는 내용도 있다.
개발 코드 부분일건데, 그 부분은 넘어가라는 표시를 혹시나 해둘 것이다.

2. 본론

2-1. JPA 설정

spring-boot-starter-data-jpa 라이브러리를 사용하면 JPA와 스프링 데이터 JPA를 스프링 부트와 통합하 고, 설정도 아주 간단히 할 수 있다.
spring-boot-starter-data-jpa 라이브러리를 사용해서 간단히 설정하는 방법을 알아보자.

build.gradle 에 다음 의존 관계를 추가한다.

//JPA, 스프링 데이터 JPA 추가 
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

spring-boot-starter-data-jpaspring-boot-starter-jdbc 도 함께 포함(의존)한다.
따라서 해당 라이브러리 의존관계를 제거해도 된다.
참고로 mybatis-spring-boot-starterspring-boot-starter- jdbc 를 포함하기 때문에 제거해도 된다.

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'  

configurations {  
    compileOnly {  
       extendsFrom annotationProcessor  
    }  
}  

repositories {  
    mavenCentral()  
}  

dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'  
    implementation 'org.springframework.boot:spring-boot-starter-web'  

    //JdbcTemplate 추가  
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'  
    //MyBatis 추가  
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'  
    //JPA, 스프링 데이터 JPA 추가  
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'  
    //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()  
}

다음과 같은 라이브러리가 추가된다.

  • hibernate-core : JPA 구현체인 하이버네이트 라이브러리
  • jakarta.persistence-api : JPA 인터페이스
  • spring-data-jpa : 스프링 데이터 JPA 라이브러리

application.properties 에 다음 설정을 추가하자.
main 경로의 application.properties이다.

logging.level.org.hibernate.SQL=DEBUG  
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

test 경로의 application.properties이다.

logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
  • org.hibernate.SQL=DEBUG : 하이버네이트가 생성하고 실행하는 SQL을 확인할 수 있다.
  • org.hibernate.type.descriptor.sql.BasicBinder=TRACE : SQL에 바인딩 되는 파라미터를 확인 할 수 있다.
  • spring.jpa.show-sql=true : 참고로 이런 설정도 있다. 이전 설정은 logger 를 통해서 SQL이 출력된다.
    • 이 설정은 System.out 콘솔을 통해서 SQL이 출력된다.
    • 따라서 이 설정은 권장하지는 않는다.
    • (둘다 켜면 logger , System.out 둘다 로그가 출력되어서 같은 로그가 중복해서 출력된다.)

스프링 부트 3.0

스프링 부트 3.0 이상을 사용하면 하이버네이트 6 버전이 사용되는데, 로그 설정 방식이 달려졌다.
다음과 같이 로그를 설정해야 한다.

#JPA log
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE

2-2. JPA 적용2 - 리포지토리 분석

참고로 테스트의 경우 마지막에 트랜잭션이 롤백되기 때문에 JPA는 UPDATE SQL을 실행하지 않는다.
테스트에서 UPDATE SQL을 확인하려면 @Commit 을 붙이면 확인할 수 있다.

@Test  
@Commit // 붙이면 됨.
void updateItem() {...}

2-3. JPA 적용3 - 예외 변환

JPA의 경우 예외가 발생하면 JPA 예외가 발생하게 된다.

@Repository 
@Transactional 
public class JpaItemRepositoryV1 implements ItemRepository {

    private final EntityManager em; 

    @Override 
    public Item save(Item item) { 
        em.persist(item); 
        return item; 
    } 

}
  • EntityManager 는 순수한 JPA 기술이고, 스프링과는 관계가 없다. 따라서 엔티티 매니저는 예외가 발생하면 JPA 관련 예외를 발생시킨다.
  • JPA는 PersistenceException 과 그 하위 예외(겁나 많다)를 발생시킨다.
    • 추가로 JPA는 IllegalStateException , IllegalArgumentException 을 발생시킬 수 있다.
  • 그렇다면 JPA 예외를 스프링 예외 추상화( DataAccessException )로 어떻게 변환할 수 있을까?
  • 비밀은 바로 @Repository 에 있다.
    • @Repository를 주석처리하고, 문법 오류를 내면(일부러), IllegalArgumentException오류가 발생하는데,
    • 주석을 풀고, 문법 오류를 내면 InvalidDataAccessApiUsageException 예외가 발생한다.
      • 해당 예외는 spring의 예외임을 확인할 수 있다(패키지를 통해서)

예외 변환 전

@Repository의 기능

  • @Repository 가 붙은 클래스는 컴포넌트 스캔의 대상이 된다.
  • @Repository 가 붙은 클래스는 예외 변환 AOP의 적용 대상이 된다.
    • 스프링과 JPA를 함께 사용하는 경우 스프링은 JPA 예외 변환기 ( PersistenceExceptionTranslator )를 등록한다.
    • 예외 변환 AOP 프록시JPA 관련 예외가 발생하면 JPA 예외 변환기를 통해 발생한 예외를 스프링 데이터 접근 예외로 변환한다.

예외 변환 후

결과적으로 리포지토리에 @Repository 애노테이션만 있으면 스프링이 예외 변환을 처리하는 AOP를 만들어준다.

log.info("repository={}", itemRepository.getClass());

테스트 중간에 위와 같은 코드를 작성하고 아래에서 나온 로그 결과를 보면 다음과 같다.

2024-08-15 20:49:31.281  INFO 21568 --- [    Test worker] h.itemservice.domain.ItemRepositoryTest  : repository=class hello.itemservice.repository.jpa.JpaItemRepository$$EnhancerBySpringCGLIB$$a754bc8b

프록시로 되어있음을 확인할 수 있다.

참고

스프링 부트는 PersistenceExceptionTranslationPostProcessor 를 자동으로 등록하는데, 여기에서 @Repository 를 AOP 프록시로 만드는 어드바이저가 등록된다.

참고

복잡한 과정을 거쳐서 실제 예외를 변환하는데, 실제 JPA 예외를 변환하는 코드는 EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible() 이다.

2-4. 스프링 데이터 JPA 적용1

넘어가길 바람.

설정

스프링 데이터 JPA는 spring-boot-starter-data-jpa 라이브러리를 넣어주면 된다.

build.gradle 추가

//JPA, 스프링 데이터 JPA 추가 
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 

그런데 이미 앞에서 JPA를 설정하면서 spring-boot-starter-data-jpa 라이브러리를 넣어주었다.
여기에는 JPA , 하이버네이트, 스프링 데이터 JPA( spring-data-jpa ), 그리고 스프링 JDBC 관련 기능도 모두 포함되어 있다.
따라서 스프링 데이터 JPA가 이미 추가되어있으므로 별도의 라이브러리 설정은 하지 않아도 된다.

예시 개발 코드에 대한 부분이므로 되도록 넘기길 바람.

itemservice.repository.jpa패키지에서 SpringDataJpaItemRepository를 다음처럼 작성하자.

package hello.itemservice.repository.jpa;  

import hello.itemservice.domain.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 SpringDataJpaRepository extends JpaRepository<Item, Long> {  

    List<Item> findByItemNameLike(String itemName);  
    List<Item> findByPriceLessThanEqual(Integer price);  

    // 쿼리 메서드(아래 메서드와 같은 기능 수행)  
    List<Item> findByItemNameLikeAndPriceLessThanEqual(String itemName, Integer price);  

    // 쿼리 직접 실행  
    @Query("select i from Item i where i.itemName like :itemName and i.price <= :price")  
    List<Item> findItems(@Param("itemName") String itemName, @Param("price") Integer price);  

}
  • 스프링 데이터 JPA가 제공하는 JpaRepository 인터페이스를 인터페이스 상속 받으면 기본적인 CRUD 기능을 사용할 수 있다.
  • 그런데 이름으로 검색하거나, 가격으로 검색하는 기능은 공통으로 제공할 수 있는 기능이 아니다. 따라서 쿼리 메서드 기능을 사용하거나 @Query 를 사용해서 직접 쿼리를 실행하면 된다.

여기서는 데이터를 조건에 따라 4가지로 분류해서 검색한다.

  • 모든 데이터 조회
  • 이름 조회
  • 가격 조회
  • 이름 + 가격 조회

동적 쿼리를 사용하면 좋겠지만, 스프링 데이터 JPA는 동적 쿼리에 약하다.
이번에는 직접 4가지 상황을 스프링 데이터 JPA로 구현해보자.

그리고 이 문제는 이후에 Querydsl에서 동적 쿼리로 깔끔하게 해결한다.

참고

스프링 데이터 JPA도 Example 이라는 기능으로 약간의 동적 쿼리를 지원하지만, 실무에서 사용하기는 기능이 빈약하다.
실무에서 JPQL 동적 쿼리는 Querydsl을 사용하는 것이 좋다.

findAll()
코드에는 보이지 않지만 JpaRepository 공통 인터페이스가 제공하는 기능이다.
모든 Item 을 조회한다.
다음과 같은 JPQL이 실행된다.
`select i from Item i

findByItemNameLike()
이름 조건만 검색했을 때 사용하는 쿼리 메서드이다.
다음과 같은 JPQL이 실행된다.
select i from Item i where i.name like ?

findByPriceLessThanEqual()
가격 조건만 검색했을 때 사용하는 쿼리 메서드이다.
다음과 같은 JPQL이 실행된다.
select i from Item i where i.price <= ?

findByItemNameLikeAndPriceLessThanEqual()
이름과 가격 조건을 검색했을 때 사용하는 쿼리 메서드이다.
다음과 같은 JPQL이 실행된다.
select i from Item i where i.itemName like ? and i.price <= ?

findItems()
메서드 이름으로 쿼리를 실행하는 기능은 다음과 같은 단점이 있다.

    1. 조건이 많으면 메서드 이름이 너무 길어진다.
    1. 조인 같은 복잡한 조건을 사용할 수 없다.

메서드 이름으로 쿼리를 실행하는 기능은 간단한 경우에는 매우 유용하지만, 복잡해지면 직접 JPQL 쿼리를 작성하는 것이 좋다.

  • 쿼리를 직접 실행하려면 @Query 애노테이션을 사용하면 된다.
  • 메서드 이름으로 쿼리를 실행할 때는 파라미터를 순서대로 입력하면 되지만, 쿼리를 직접 실행할 때는 파라미터를 명시적으로 바인딩 해야 한다.
  • 파라미터 바인딩은 @Param("itemName") 애노테이션을 사용하고, 애노테이션의 값에 파라미터 이름을 주면 된다.

2-5. 스프링 데이터 JPA 적용2

넘어가길 바람.

JpaItemRepositoryV2

package hello.itemservice.repository.jpa;  

import hello.itemservice.domain.Item;  
import hello.itemservice.repository.ItemRepository;  
import hello.itemservice.repository.ItemSearchCond;  
import hello.itemservice.repository.ItemUpdateDto;  
import lombok.RequiredArgsConstructor;  
import org.springframework.stereotype.Repository;  
import org.springframework.transaction.annotation.Transactional;  
import org.springframework.util.StringUtils;  

import java.util.List;  
import java.util.Optional;  

@Repository  
@Transactional  
@RequiredArgsConstructor  
public class JpaItemRepositoryV2 implements ItemRepository {  

    private final SpringDataJpaItemRepository repository;  

    @Override  
    public Item save(Item item) {  
        return repository.save(item);  
    }  

    @Override  
    public void update(Long itemId, ItemUpdateDto updateParam) {  
        Item findItem = repository.findById(itemId).orElseThrow();  
        findItem.setItemName(updateParam.getItemName());  
        findItem.setPrice(updateParam.getPrice());  
        findItem.setQuantity(updateParam.getQuantity());  
    }  

    @Override  
    public Optional<Item> findById(Long id) {  
        return repository.findById(id);  
    }  

    @Override  
    public List<Item> findAll(ItemSearchCond cond) {  
        String itemName = cond.getItemName();  
        Integer maxPrice = cond.getMaxPrice();  

        if (StringUtils.hasText(itemName) && maxPrice != null) {  
            return repository.findItems("%" + itemName + "%", maxPrice);  
        } else if(StringUtils.hasText(itemName)) {  
            return repository.findByItemNameLike("%" + itemName + "%");  
        } else if(maxPrice != null) {  
            return repository.findByPriceLessThanEqual(maxPrice);  
        } else {  
            return repository.findAll();  
        }  
    }  

}

의존관계와 구조

  • ItemServiceItemRepository 에 의존하기 때문에 ItemService 에서 SpringDataJpaItemRepository 를 그대로 사용할 수 없다.
  • 물론 ItemServiceSpringDataJpaItemRepository 를 직접 사용하도록 코드를 고치면 되겠지만, 우리는 ItemService 코드의 변경없이 ItemServiceItemRepository 에 대한 의존을 유지하면서 DI를 통해 구현 기술을 변경하고 싶다.

조금 복잡하지만, 새로운 리포지토리를 만들어서 이 문제를 해결해보자.
여기서는 JpaItemRepositoryV2ItemRepositorySpringDataJpaItemRepository 사이를 맞추 기 위한 어댑터 처럼 사용된다.

클래스 의존 관계

  • JpaItemRepositoryV2ItemRepository 를 구현한다. 그리고 SpringDataJpaItemRepository 를 사용한다.

런타임 객체 의존 관계

  • 런타임의 객체 의존관계는 다음과 같이 동작한다.
  • itemService -> jpaItemRepositoryV2 -> springDataJpaItemRepository(프록시 객체)

이렇게 중간에서 JpaItemRepository 가 어댑터 역할을 해준 덕분에 ItemService 가 사용하는 ItemRepository 인터페이스를 그대로 유지할 수 있고 클라이언트인 ItemService 의 코드를 변경하지 않아도 되는 장점이 있다.

다음으로 기능에 대해서 알아보자.

save()
repository.save(item)
스프링 데이터 JPA가 제공하는 save() 를 호출한다.

update()
스프링 데이터 JPA가 제공하는 findById() 메서드를 사용해서 엔티티를 찾는다.
그리고 데이터를 수정한다.
이후 트랜잭션이 커밋될 때 변경 내용이 데이터베이스에 반영된다. (JPA가 제공하는 기능이다.)

findById()
repository.findById(itemId)
스프링 데이터 JPA가 제공하는 findById() 메서드를 사용해서 엔티티를 찾는다.

findAll()
데이터를 조건에 따라 4가지로 분류해서 검색한다.

  • 모든 데이터 조회
  • 이름 조회
  • 가격 조회
  • 이름 + 가격 조회

모든 조건에 부합할 때는 findByItemNameLikeAndPriceLessThanEqual() 를 사용해도 되고, repository.findItems() 를 사용해도 된다.
그런데 보는 것 처럼 조건이 2개만 되어도 이름이 너무 길어지는 단점이 있다.
따라서 스프링 데이터 JPA가 제공하는 메서드 이름으로 쿼리를 자동으로 만들어주는 기능과 @Query 로 직접 쿼리를 작성하는 기능 중에 적절한 선택이 필요하다.

추가로 코드를 잘 보면 동적 쿼리가 아니라 상황에 따라 각각 스프링 데이터 JPA의 메서드를 호출해서 상당히 비효율적인 코드인 것을 알 수 있다.
앞서 이야기했듯이 스프링 데이터 JPA는 동적 쿼리 기능에 대한 지원이 매우 약하다.
이 부분은 이후에 Querydsl을 사용해서 개선해본다.
기능을 모두 개발했으니, 설정하고 실행해보자.

itemservice.config패키지의 SpringDataJpaConfig클래스이다.

package hello.itemservice.config;  

import hello.itemservice.repository.ItemRepository;  
import hello.itemservice.repository.jpa.JpaItemRepositoryV2;  
import hello.itemservice.repository.jpa.SpringDataJpaItemRepository;  
import hello.itemservice.service.ItemService;  
import hello.itemservice.service.ItemServiceV1;  
import lombok.RequiredArgsConstructor;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  

@Configuration  
@RequiredArgsConstructor  
public class SpringDataJpaConfig {  

    private final SpringDataJpaItemRepository springDataJpaItemRepository;  

    @Bean  
    public ItemService itemService() {  
        return new ItemServiceV1(itemRepository());  
    }  

    @Bean  
    public ItemRepository itemRepository() {  
        return new JpaItemRepositoryV2(springDataJpaItemRepository);  
    }  
}
  • SpringDataJpaItemRepository 는 스프링 데이터 JPA가 프록시 기술로 만들어주고 스프링 빈으로도 등록해준다.

ItemServiceApplication를 다음처럼 변경하자.

@Slf4j  
//@Import(MemoryConfig.class)  
//@Import(JdbcTemplateV1Config.class)  
//@Import(JdbcTemplateV2Config.class)  
//@Import(JdbcTemplateV3Config.class)  
//@Import(MyBatisConfig.class)  
//@Import(JpaConfig.class)  
@Import(SpringDataJpaConfig.class)  
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")  
public class ItemServiceApplication {...}
  • SpringDataJpaConfig 를 사용하도록 변경했다.

테스트를 실행하자

먼저 ItemRepositoryTest 를 통해서 리포지토리가 정상 동작하는지 확인해보자.
테스트가 모두 성공해야 한다.

애플리케이션을 실행하자

ItemServiceApplication 를 실행해서 애플리케이션이 정상 동작하는지 확인해보자.

예외 변환

스프링 데이터 JPA도 스프링 예외 추상화를 지원한다.
스프링 데이터 JPA가 만들어주는 프록시에서 이미 예외 변환을 처리하기 때문에, @Repository 와 관계없이 예외가 변환된다.

주의! - 하이버네이트 버그

하이버네이트 5.6.6 ~ 5.6.7 을 사용하면 Like 문장을 사용할 때 다음 예외가 발생한다.

스프링 부트 2.6.5 버전은 문제가 되는 하이버네이트 5.6.7을 사용한다.

java.lang.IllegalArgumentException: Parameter value [\] did not match expected type [java.lang.String (n/a)]

build.gradle에 다음을 추가해서 하이버네이트 버전을 문제가 없는 5.6.5.Final 로 맞추자.

ext["hibernate.version"] = "5.6.5.Final"

정리

스프링 데이터 JPA는 이 외에도 정말 수 많은 편리한 기능을 제공한다.
심지어 우리가 어렵게 사용하는 페이징을 위한 기능들도 제공한다.
스프링 데이터 JPA는 단순히 편리함을 넘어서 많은 개발자들이 똑같은 코드로 중복 개발하는 부분을 개선해준다.
(영한님)개인적으로 스프링 데이터 JPA는 실무에서 기본으로 선택하는 기술이다.

2-6. Querydsl 설정

스프링 부트 2.x와 스프링 부트 3.x의 설정이 다르다.

스프링 부트 2.x 설정

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'  

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'  

    //JdbcTemplate 추가  
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'  
    //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'  
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"  
    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')  
}

Querydsl로 추가된 부분은 다음 두 부분이다.

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" 

    ...
}
  • "com.querydsl:querydsl-apt:$ {dependencyManagement.importedProperties['querydsl.version']}:jpa" 은 한줄로 붙어있는 코드이다.
//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거 
clean { 
    delete file('src/main/generated') 
}

스프링 부트 3.x 설정

스프링 부트 3.x를 사용한다면 다음과 같이 설정하면 된다. 2.x와 일부 차이가 있다.

dependencies { 
    //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" }
  • 2.x 와 비교하면 다음 부분이 jpa -> jakarta 로 변경되었다.
  • implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
  • "com.querydsl:querydsl-apt:$ {dependencyManagement.importedProperties['querydsl.version']}:jakarta"
//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거 
clean { 
    delete file('src/main/generated') 
}

검증 - Q 타입 생성 확인 방법

  • Preferences(윈도우는 setting) -> Build, Execution, Deployment -> Build Tools -> Gradle

여기에 가면 크게 2가지 옵션을 선택할 수 있다. 참고로 옵션은 둘다 같게 맞추어 두자.

    1. Gradle: Gradle을 통해서 빌드한다.
    1. IntelliJ IDEA: IntelliJ가 직접 자바를 실행해서 빌드한다.

옵션 선택1 - Gradle - Q타입 생성 확인 방법

Gradle IntelliJ 사용법

  • Gradle -> Tasks -> build -> clean
  • Gradle -> Tasks -> other -> compileJava

Gradle 콘솔 사용법

  • ./gradlew clean compileJava

Q 타입 생성 확인

  • build -> generated -> sources -> annotationProcessor -> java/main 하위에
    • hello.itemservice.domain.QItem 이 생성되어 있어야 한다.

참고

Q타입은 컴파일 시점에 자동 생성되므로 버전관리(GIT)에 포함하지 않는 것이 좋다.
gradle 옵션을 선택하면 Q타입은 gradle build 폴더 아래에 생성되기 때문에 여기를 포함하지 않아야 한다.
대부분 gradle build 폴더를 git에 포함하지 않기 때문에 이 부분은 자연스럽게 해결된다.

Q타입 삭제

gradle clean 을 수행하면 build 폴더 자체가 삭제된다.
따라서 별도의 설정은 없어도 된다.

옵션 선택2 - IntelliJ IDEA - Q타입 생성 확인 방법

Build -> Build Project 또는
Build -> Rebuild 또는
main() , 또는 테스트를 실행하면 된다.

src/main/generated 하위에

  • hello.itemservice.domain.QItem 이 생성되어 있어야 한다.

참고

Q타입은 컴파일 시점에 자동 생성되므로 버전관리(GIT)에 포함하지 않는 것이 좋다.
IntelliJ IDEA 옵션을 선택하면 Q타입은 src/main/generated 폴더 아래에 생성되기 때문에 여기를 포함하지 않는 것이 좋다.

Q타입 삭제

//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
clean { 
    delete file('src/main/generated') 
} ``` 

`IntelliJ IDEA` 옵션을 선택하면 `src/main/generated` 에 파일이 생성되고, 필요한 경우 Q파일을 직접 삭제 해야 한다. 
`gradle` 에 해당 스크립트를 추가하면 `gradle clean` 명령어를 실행할 때 `src/main/generated` 의 파일도 함께 삭제해준다. 

#### 참고 
Querydsl은 이렇게 설정하는 부분이 사용하면서 조금 귀찮은 부분인데, IntelliJ가 버전업 하거나 Querydsl의 Gradle 설정이 버전업 하면서 적용 방법이 조금씩 달라지기도 한다. 
그리고 본인의 환경에 따라서 잘 동작하지 않기도 한다. 
공식 메뉴얼에 소개 되어 있는 부분이 아니기 때문에, 설정에 수고로움이 있지만 `querydsl gradle` 로 검색하면 본인 환경에 맞는 대안을 금방 찾을 수 있을 것이다.

적용 부분은 넘어간다.
# 3. 요약

기본 JPA 부터 Querydsl까지 개괄적인 내용을 살펴보았다.

여기 나오지 않는 대부분의 강의 내용들은 이미 다 정리했으므로, [여기](https://ssangsu.tistory.com/category/Spring/JPA)를 참고하면 좋을 것이다.
저기는 참고로 내 블로그다..
728x90
Comments