Querydsl 예제

윤준혁·2024년 5월 20일

설정

build.gradle

buildscript {
	ext {
		queryDslVersion = "5.0.0"
	}
}

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.0.6'
	id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.studyjun'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

	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"
}

tasks.named('test') {
	useJUnitPlatform()
}

application.yml

spring:
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true
        show_sql: true
  datasource:
    url: jdbc:mariadb://localhost:3306/studyquerydsl
    username: studyquerydsl
    password: 1234
    driver-class-name: org.mariadb.jdbc.Driver

코드

Config

@Configuration
public class QuerydslConfig {
    @Autowired
    private EntityManager em;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(em);
    }
}

Entity

@Data
@Entity
public class Student {
    @Id
    private Long id;
    private String name;
    private Integer age;
}
@Data
@Entity
public class Score {
    @Id
    private Long scoreId;
    private String subject;
    private Integer score;
    private Long studentId;
}

Repository

public interface SubjectScoreRepository extends JpaRepository<Score, Long>, SubjectScoreCustom {
}
public interface SubjectScoreCustom {
    List<SubjectScoreResponse> findScoresByStudent(Long studentId);
}
@RequiredArgsConstructor
public class SubjectScoreCustomImpl implements SubjectScoreCustom{

    private final JPAQueryFactory jpaQueryFactory;

    @Override
    public List<SubjectScoreResponse> findScoresByStudent(Long studentId) {
        QStudent st = QStudent.student;
        QScore sc = QScore.score1;

        JPAQuery<Tuple> query = jpaQueryFactory.select(st.id, st.name, st.age, sc.subject, sc.score)
                .from(st)
                .join(sc)
                .on(st.id.eq(sc.studentId))
                .where(st.id.eq(studentId))
                .orderBy(st.id.desc());

        return query.fetchJoin().fetch().stream().map(tuple -> SubjectScoreResponse.builder()
                .studentId(tuple.get(st.id))
                .name(tuple.get(st.name))
                .age(tuple.get(st.age))
                .subject(tuple.get(sc.subject))
                .score(tuple.get(sc.score))
                .build()).toList();
    }
}

Service

@Service
@RequiredArgsConstructor
public class SubjectScoreService {
    private final SubjectScoreRepository subjectScoreRepository;

    public List<SubjectScoreResponse> findScoresByStudent(Long studentId) {
        return subjectScoreRepository.findScoresByStudent(studentId);
    }
}

Controller

@RestController
@RequestMapping("/subject")
@RequiredArgsConstructor
public class SubjectScoreController {
    private final SubjectScoreService subjectScoreService;

    @GetMapping(value="/score/{studentId}")
    public ResponseEntity<List<SubjectScoreResponse>> getScoresByStudent (@PathVariable Long studentId) {
        List<SubjectScoreResponse> subjectSocreList = subjectScoreService.findScoresByStudent(studentId);
        return ResponseEntity.ok(subjectSocreList);
    }
}

Dto

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SubjectScoreResponse {
    private Long studentId;
    private String name;
    private Integer age;
    private String subject;
    private Integer score;
}

테스트 데이터

경로

  • src/main/resources 안에 위치

SQL구문

INSERT INTO student (id, age, name) VALUES (1, 20, 'Hong Gildong');
INSERT INTO student (id, age, name) VALUES (2, 22, 'Lee Sunsin');
INSERT INTO student (id, age, name) VALUES (3, 19, 'Jang Bogo');
INSERT INTO student (id, age, name) VALUES (4, 25, 'Kim Yushin');

INSERT INTO score (score_id, subject, score, student_id) VALUES (1, 'Math', 95, 1);
INSERT INTO score (score_id, subject, score, student_id) VALUES (2, 'Korean', 88, 1);
INSERT INTO score (score_id, subject, score, student_id) VALUES (3, 'English', 79, 1);
INSERT INTO score (score_id, subject, score, student_id) VALUES (4, 'Math', 78, 2);
INSERT INTO score (score_id, subject, score, student_id) VALUES (5, 'Korean', 89, 2);
INSERT INTO score (score_id, subject, score, student_id) VALUES (6, 'English', 71, 2);
INSERT INTO score (score_id, subject, score, student_id) VALUES (7, 'Math', 100, 3);
INSERT INTO score (score_id, subject, score, student_id) VALUES (8, 'Korean', 91, 3);
INSERT INTO score (score_id, subject, score, student_id) VALUES (9, 'English', 88, 3);
INSERT INTO score (score_id, subject, score, student_id) VALUES (10, 'Math', 76, 4);
INSERT INTO score (score_id, subject, score, student_id) VALUES (11, 'Korean', 91, 4);
INSERT INTO score (score_id, subject, score, student_id) VALUES (12, 'English', 80, 4);

결과

결과 이미지

https://github.com/matddung/StudyQuerydsl

결론

  • JPA에 비해 초기 설정이 조금 복잡하다고 느낄 수 있지만 SQL이 더 익숙하다면 확실히 편리할 듯 함
  • 오류가 명확히 표현되어 실수를 바로잡기가 편리
  • Querydsl은 JPA 뿐만 아니라 JDO, JDBC, Lucene, MongoDB와 같은 여러 저장소 기술과도 잘 통합된다고 하니 하나의 일관된 쿼리 작성 방식으로 다양한 저장소 기술을 다룰 수 있다고 생각함

0개의 댓글