일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- ExecutorService
- 스레드
- JWT
- 비사이드프로젝트
- 프로세스
- querydsl
- ExceptionHandlerFilter
- springboot
- 비동기
- Spring Cloud OpenFeign
- microsoft
- Apple 로그인
- Spring Security
- 오블완
- 2024년 상반기 회고
- 멀티프로세싱
- asciidoctor
- REDIS
- OpenFeign
- FeignClients
- 멀티태스킹
- Spring Reactive Programming
- spring boot
- 사이드 프로젝트
- 도메인 주도 설계(DDD) 기반 마이크로서비스(MSA) 모델링
- 코드로 배우는 스프링 부트 웹 프로젝트
- 멀티스레드
- 네이버클라우드 서버
- OAuth2.0
- 티스토리챌린지
- Today
- Total
기록하기
[사이드 프로젝트 - 비사이드] Spring Boot + JPA + Querydsl 적용 본문
비사이드 13기에 참여하게 되면서 프로젝트 세팅에서 매번 헷갈리는 내용인 Querydsl 과 애플 로그인 구현, 네이버 클라우드와 관련된 내용을 정리해보려고 한다.
- Spring Boot + JPA + Querydsl 적용(현재글)
- 네이버 클라우드 Global DNS, SSL 설정
- 애플 로그인 구현
Spring Boot + JPA + Querydsl 적용과 관련해서는 이미 다른 분들께서 블로그 정리를 너무나도 잘해주셨다. 그래서 블로그 작성을 고민하다가 나중에 또 세팅할 때 헷갈릴 것 같아 추후 확인을 위해 다시 정리해보려고 한다.
build.gradle 설정
설정은 다음과 같이 진행을 했다.
참고로 프로젝트 환경은 아래와 같다.
- Spring Boot 2.7.7
- Java 17
- gradle 7.6
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.7'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
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-web'
//querydsl 설정 추가
implementation 'com.querydsl:querydsl-jpa'
implementation 'com.querydsl:querydsl-apt'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
//querydsl 설정 추가
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
//querydsl 설정 추가
//QClass 도 같이 build 되기 위해 src/main/generated 위치 명시
def querydslSrcDir = 'src/main/generated'
sourceSets {
main {
java {
srcDirs += [ querydslSrcDir ]
}
}
}
//다른 Java 버전에서도 java.annotation.Generated 로 import 하도록 설정
compileJava {
options.compilerArgs << '-Aquerydsl.generatedAnnotationClass=javax.annotation.Generated'
}
//위에서 선언한 src/main/generated 위치에 QClass 저장
tasks.withType(JavaCompile) {
options.generatedSourceOutputDirectory = file(querydslSrcDir)
}
//build.clean 시 QClass 모두 삭제
clean {
delete file(querydslSrcDir)
}
//querydsl 설정 추가
- implementation 'com.querydsl:querydsl-jpa' : Querydsl 을 사용하기 위한 라이브러리
- implementation 'com.querydsl:querydsl-apt' : QClass 생성하기 위한 라이브러리
- annotationProcessor 'com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa' : @Entity 클래스를 찾고, QClass 생성
- annotationProcessor 'jakarta.persistence:jakarta.persistence-api' : java 에서 jakarta 로 변경됨.
- annotationProcessor 'jakarta.annotation:jakarta.annotation-api' : java 에서 jakarta 로 변경됨.
이렇게 설정을 하고 build or compileJava 를 실행했을 때 QClass 들이 생성이 되면 성공한 것이다.
Querydsl 사용
QuerydslConfig 설정
설정 파일은 다음과 같이 설정을 해주었다.
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Configuration
public class QuerydslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
Entity 클래스 생성
이후 Repository 클래스들을 생성하여 실제로 데이터를 잘 조회해오는지 확인을 해봐야하는데, 이를 위해 Diary, BaseEntity 라는 객체를 생성했다.
Diary 는 일기를 작성하는 곳인데 프로젝트 주제가 '술일기' 였기에 술 마신 날을 기록하는 엔티티라고 볼 수 있고, 구체적인 컬럼은 삭제하고 초기 설계 내용으로 대신해보겠다.
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.TimeZone;
@Getter
@Entity
@NoArgsConstructor
@Table(name = "diary")
public class Diary {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private LocalDateTime diaryDt;
private String drinkName;
private Long drinkCount;
private String content;
}
그리고 BaseEntity 는 생성일, 업데이트일과 같이 공통적으로 처리되는 날짜 관련 컬럼을 이곳에서 관리하도록 설계해, 각 엔티티가 BaseEntity 를 상속 받도록 구현했다.
아 그리고 추후에 테스트를 하면서 발견한 오류인데 createdAt 에 @Column(updatable = false) 를 하지 않으면 추후에 update 가 될 때 null 이 된다. 그렇기 때문에 이 설정을 꼭 해줘야한다.
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@Column(updatable = false)
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
Repository 클래스 생성
자 그러면 이제 Entity 클래스도 생성이 되었으니 관련 Repository 를 생성하면 된다.
Repository 구조를 잡는 것에는 여러 방법이 있다. 필자가 선택한 방법은 Spring Data JPA Custom Repository 사용 방법이다.
Spring Data JPA 공식 문서에 나와 있는 내용을 참고했는데, 요약을 하자면
- CustomRepository 인터페이스 객체를 생성한다.
- 1번에서 생성한 CustomRepository 인터페이스의 구현체를 생성한다.
- JPARepository 인터페이스 객체를 생성한다. 이때, JpaRepository 나 CrudRepository 를 상속받는데 이것 뿐만 아니라 1번에서 작성한 CustomRepository 인터페이스도 상속받는다.
위 내용으로 정리할 수 있다.
실제 코드로 구현을 해본다면 아래와 같다.
1번 CustomRepository 인터페이스 객체 생성
public interface DiaryQuerydslRepository {
}
2번 CustomRepository 인터페이스의 구현체 생성
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class DiaryQuerydslRepositoryImpl implements DiaryQuerydslRepository {
private final JPAQueryFactory queryFactory;
}
3번 JPARepository 인터페이스 객체 생성
import org.springframework.data.jpa.repository.JpaRepository;
public interface DiaryJpaRepository extends JpaRepository<Diary, Long>, DiaryQuerydslRepository {
}
이렇게 구성하면 Repository 클래스 구성도 끝난 것이다! 아 여기서 주의할 것은, 구현체 파일명이 interface 명 + impl 이어야 한다. 이유는 DiaryJpaRepository 에 객체를 주입할 때 구현체 클래스를 삽입해주기 때문이다. 이 점만 주의해서 따라온다면 문제 없이 구성할 수 있을 것이다.
테스트
이제 마지막으로 테스트를 해보려고 한다. 일단 현재까지 구성한 상황에서 build 나 compildJava 를 실행한다면 다음과 같은 파일이 생긴 것을 확인할 수 있다.
그러면 비즈니스 로직을 구성할 때 Querydsl 을 어떻게 사용해야하는걸까? 그리고 실제로 Repository DI 는 어떻게 해야하는 걸까?
이에 대한 답변은, Service Layer 에서 DiaryJpaRepository 하나만 의존성 주입을 받아서 사용하면 된다는 것이다.
실제로 로직을 구현해서 잘 동작하는지 확인을 해보자
위 Diary 클래스에서 drinkName 으로 조회를 해오는데 이를 Querydsl 로 구현을 해야한다고 가정해보자
Controller
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
@RequestMapping("/diary")
public class DiaryController {
private final DiaryService diaryService;
@GetMapping("/test/querydsl/{drinkName}")
public ResponseEntity testQuerydsl(@PathVariable String drinkName) {
Diary diary = diaryService.findByDrinkName(drinkName);
return ResponseEntity.ok(diary);
}
}
간단한 endpoint 를 만들어보았다.
Service
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class DiaryService {
private final DiaryJpaRepository diaryJpaRepository;
public Diary findByDrinkName(String drinkName) {
return diaryJpaRepository.findByDrinkName(drinkName);
}
}
여기서 DiaryJpaRepository 만 주입을 받고, findByDrinkName 메소드를 생성하려고 할 때 아래와 같이 2개의 클래스에서 생성할 수 있다고 나오게 된다. 여기서 우리는 DiaryQuerydslRepository 를 선택한 뒤 그 구현체인 DiaryQuerydslRepositoryImpl 에서 메소드를 오버라이딩하여 설계하면 된다.
DiaryQuerydslRepositoryImpl
로직을 구현하면 아래와 같다.
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import static com.example.querydsl.QDiary.diary;
@RequiredArgsConstructor
public class DiaryQuerydslRepositoryImpl implements DiaryQuerydslRepository {
private final JPAQueryFactory queryFactory;
@Override
public Diary findByDrinkName(String drinkName) {
return queryFactory
.selectFrom(diary)
.where(diary.drinkName.eq(drinkName))
.fetchFirst();
}
}
실제로 잘 동작하는지 확인해보자
http 테스트 파일을 만들어서 실행해보니 원하는 데이터를 잘 조회해오는 것을 확인했다.
아래 블로그 내용을 참고해서 구현해보았는데, 더 구조가 복잡한 프로젝트를 설계할 때 기본이 되는 내용인 것 같아 정리해보았다.
참고
'Server > Spring Boot' 카테고리의 다른 글
[사이드 프로젝트 - 비사이드] 13기 참여 후기 (0) | 2023.04.25 |
---|---|
Spring Boot OAuth2.0 애플 로그인 & 탈퇴 구현 (4) | 2023.01.05 |
REST Docs 설정 정리 (0) | 2022.09.26 |
EhCache - StringCacheKeyGenerator 사용 시 @Cacheable 적용 (0) | 2022.09.18 |
logback 설정 - 특정 변수에 따라 log 파일 다른 위치에 저장하기 (0) | 2022.08.25 |