JPA, QueryDSL 사용해보기

LeeKyoungChang·2023년 2월 5일
0
post-thumbnail

📚 1. 프로젝트 생성 및 초기 설정

📖 A. 프로젝트 생성

✔️ springinitializer
스크린샷 2023-02-05 오전 11 56 30

 

✔️ build.gradle

// 추가  
buildscript {  
   ext {  
      queryDslVersion = "5.0.0"  
   }  
}  
  
plugins {  
   id 'java'  
   id 'org.springframework.boot' version '2.7.8'  
   id 'io.spring.dependency-management' version '1.0.15.RELEASE'  
  
  
   // 추가  
   id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"  
}  
  
group = 'QueryDSLStudy'  
version = '0.0.1-SNAPSHOT'  
sourceCompatibility = '11'  
  
configurations {  
   compileOnly {  
      extendsFrom annotationProcessor  
   }  
}  
  
repositories {  
   mavenCentral()  
}  
  
dependencies {  
   implementation 'org.springframework.boot:spring-boot-starter-data-jpa'  
   implementation 'org.springframework.boot:spring-boot-starter-web'  
  
   //querydsl 추가  
   implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"  
   annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"  
  
   compileOnly 'org.projectlombok:lombok'  
   developmentOnly 'org.springframework.boot:spring-boot-devtools'  
   runtimeOnly 'com.h2database:h2'  
   runtimeOnly 'com.mysql:mysql-connector-j'  
   annotationProcessor 'org.projectlombok:lombok'  
  
   // 테스트에서 lombok 사용 - 추가  
   testCompileOnly 'org.projectlombok:lombok'  
   testAnnotationProcessor 'org.projectlombok:lombok'  
  
   testImplementation 'org.springframework.boot:spring-boot-starter-test'  
}  
  
tasks.named('test') {  
   useJUnitPlatform()  
}  
  
  
  
// 추가  
// querydsl 설정 추가  
  
// querydsl에서 사용할 경로를 설정한다.  
def querydslDir = "$buildDir/generated/querydsl"  
  
// JPA 사용 여부와 사용할 경로를 설정  
querydsl {  
   jpa = true  
   querydslSourcesDir = querydslDir  
}  
  
// build 시 사용할 sourceSet 추가한다.  
sourceSets {  
   main.java.srcDir querydslDir  
}  
  
// querydsl이 compileClassPath를 상속하도록 설정  
configurations {  
   compileOnly {  
      extendsFrom annotationProcessor  
   }  
   querydsl.extendsFrom compileClasspath  
}  
  
// querydsl 컴파일 시 사용할 옵션 설정  
compileQuerydsl {  
   options.annotationProcessorPath = configurations.querydsl  
}

 

✔️ test

스크린샷 2023-02-05 오전 11 58 57
  • Controller 추가
스크린샷 2023-02-05 오후 12 00 21
  • compileQuerydsl로 실행

 

실행 후, 구체적으로 어떠한 것이 생기는 지 궁금하다면 이전 참고자료 를 보면 될 것 같다.

QHello가 생성되었다.

스크린샷 2023-02-05 오후 3 16 36

 

 

📖 B. H2 Database 생성

✔️ h2 database

스크린샷 2023-02-05 오후 12 31 12

 

✔️ 실행 파일 h2start

스크린샷 2023-02-05 오후 12 31 36

 

✔️ 생성된 h2 DB 연결
스크린샷 2023-02-05 오후 12 30 05

스크린샷 2023-02-05 오후 12 32 41

 

 

📖 C. application.yml

spring:  
  datasource:  
    url: jdbc:h2:tcp://localhost//Users/leekyoungchang/Desktop/Study/computer/db/h2Database/querydslDB  
    username: sa  
    password: 1234  
    driver-class-name: org.h2.Driver  
  
  jpa:  
    hibernate:  
      ddl-auto: update  
    properties:  
      hibernate:  
        format_sql: true  
  
logging:  
  level:  
    org.hibernate.SQL: debug

 

 

📚 2. 토이 프로젝트 동작 구조

📡 실행 순서
UserSignUpRequestDto -> UserController -> UserService -> UserRepository -> UserRepositoryInformation -> UserRepositoryInformationImpl

✔️ Entity 생성후, Tasks/other/compileQuerydsl 클릭을 통해 QEntity 클래스 생성

스크린샷 2023-02-05 오후 4 34 45 스크린샷 2023-02-05 오후 4 34 18

 

✏️ Native Query

Native Query : 순수 쿼리, 자기가 사용하는 DB에 맞게 쿼리문을 직접 만들 때 주로 사용한다.

  • Hibernate가 지원하는 메서드 내부에서는 JDBC API가 동작하고 있어, 개발자가 직접 SQL을 작성하지 않아도 된다.
  • 만약 사용자가 sql을 입력했을 때 Hibernate가 자동으로 그 DB에 해당하는 sql문으로 바꾸어준다.
  • 그런데 Native Query를 사용하면 사용자는 사용하는 DB에 맞게 쿼리문을 직접 입력해야 한다.
스크린샷 2023-02-05 오후 4 44 00

 

나는 @QueryProjection 을 이용해 결과를 반환했다.

3. QueryProjection 설명

✔️ QueryProjection 사용해보기

스크린샷 2023-02-06 오전 12 36 05

@QueryProjection을 dto에 추가후, compileQuerydsl 클릭!

스크린샷 2023-02-06 오전 12 36 30
  • QUserCheckRequestDto 생성된다.

 

📖 A. Controller, Service, Repository

시작하기 전 테스트 값 미리 넣기

스크린샷 2023-02-06 오전 12 06 34

 

✔️ Controller

@Slf4j  
@Controller  
public class UserController {  
  
    @Autowired  
    UserService userService;  
  
    @PostMapping("/test")  
    public ResponseEntity<List<UserCheckRequestDto>> signIn(UserSignUpRequestDto userRequestDto){  
        log.info("user : " + userRequestDto.getUsername() + " pwd : " + userRequestDto.getPassword());  
        userRequestDto.setUsername("chang");  
        userRequestDto.setPassword("chang");  
        return ResponseEntity.ok(userService.signIn(userRequestDto));  
    }  
}
  • 여기서 문제점 : 사용자가 어떠한 값을 담아서 /test로 보내도 null이 결과로 나온다. (고치지 못한 문제점)
  • 그래서 일부러 setUsername, setPassword를 추가
스크린샷 2023-02-06 오전 12 05 04

 

✔️ Service

  
@Transactional(readOnly = true)  
@RequiredArgsConstructor  
@Service  
public class UserService {  
  
    private final UserRepository userRepository;  
  
  
    // 회원가입  
//    public UserResponseDto signup(UserSignUpRequestDto userRequestDto){  
//  
//    }  
  
  
    // 로그인  
    public List<UserCheckRequestDto> signIn(UserSignUpRequestDto userRequestDto){  
        return userRepository.findUserInformation(userRequestDto);  
    }  
}

 

✔️ Repository

public interface UserRepository extends JpaRepository<UserEntity, Long>, UserRepositoryInformation {  
  
}

 

 

📖 B. repository

✔️ UserRepository는 왜 UserRepositoryInformation을 상속받는가?

보통 sql문이 길거나 복잡할 때 xxImpl 클래스를 만들어서, sql문을 적는다. (interface <-> xxxImpl)

즉, QueryDsl 사용하기 위해서 상속받았다.

 

✔️ interface

public interface UserRepositoryInformation {  
    List<UserCheckRequestDto> findUserInformation(UserSignUpRequestDto userRequestDto);  
}

 

✔️ xxImpl

import QueryDSLStudy.user.QUserCheckRequestDto;  
import QueryDSLStudy.user.UserCheckRequestDto;  
import QueryDSLStudy.user.UserSignUpRequestDto;  
import com.querydsl.jpa.impl.JPAQueryFactory;  
  
import javax.persistence.EntityManager;  
import java.util.List;  
  
import static QueryDSLStudy.user.QUserEntity.userEntity;  
  
  
public class UserRepositoryInformationImpl implements UserRepositoryInformation {  
  
    private final JPAQueryFactory jpaQueryFactory;  
  
    public UserRepositoryInformationImpl(EntityManager em) {  
        this.jpaQueryFactory = new JPAQueryFactory(em);  
    }  
  
    @Override  
    public List<UserCheckRequestDto> findUserInformation(UserSignUpRequestDto userRequestDto) {  
  
        return jpaQueryFactory  
                .select(new QUserCheckRequestDto(  
                        userEntity.username.as("username"),  
                        userEntity.password.as("password"),  
                        userEntity.address.as("address"),  
                        userEntity.phone.as("phone")))  
                .from(userEntity)  
                .where(  
                        userEntity.username.eq(userRequestDto.getUsername()),  
                        userEntity.password.eq(userRequestDto.getPassword())  
                ).fetch();  
  
  
    }  
}
  • QueryDSLStudy.user.QUserEntity.userEntity; 내부적으로 QUserEntity 생성한 객체를 사용한다.
  • JPAQueryFactory 을 사용해서 select문을 이용한다.
  • fetch join
  • 결과로 해당 usernamepassword를 가진 회원의 정보가 List로 넘어온다.

 

✔️ 실행 결과

스크린샷 2023-02-06 오전 12 06 41

 

결과로, 복잡한 sql문이 있을 때 어떻게 해결해야할지? Querydsl 사용법을 알게 된 것 같다.

 

 

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글