Home Project Lab 5. 게시판 개발(QueryDsl, 조회수 개발) - 2
Post
Cancel

Project Lab 5. 게시판 개발(QueryDsl, 조회수 개발) - 2

QueryDsl를 사용하는 이유

    1. QueryDsl을 사용하는 가장 큰 이유는 JPA 에서 제공하지 않는 동적 쿼리를 수행할 수 있기 때문이다.
    1. 문자열이 아닌 자바 소스 코드로 쿼리를 작성하기에 컴파일 시점에 문법 오류를 발견할 수 있다.
    1. DB 종류와 관계없이 하나의 통일된 문법으로 쿼리를 작성할 수 있다.
    1. IDE에서 제공하는 소스 코드 자동 완성 기능을 활용 할 수 있다.

출처: https://ict-nroo.tistory.com/117

QueryDsl 적용

  • QueryDsl을 적용하는 방법은 크게 두 가지가 있다.

1. QueryDsl plugins 사용

  • ‘com.ewerk.gradle.plugins.querydsl’ 플러그인을 사용하는 방법으로, 여러 문제가 발생하기에 ‘2. Gradle annotationProcessor’ 방법을 사용해야 한다.
  • Gradle 버전 4.6 이전과 이후일 때 적용 방법이 다르고, 2018년 이후 플러그인이 더이상 업데이트 되지 않고 있으며, Q Domain을 IntelliJ가 자동으로 인식하지 못하여 프로젝트에서 수동으로 Q Domain 경로를 지정해야 하는 번거로움이 있다.
  • 해당 에러는 프로젝트 빌드할 때 중간에 멈추게 만들어 devtools를 통한 auto 빌드에 큰 불폄함을 초래한다. 하지만 에러 해결 방법을 찾지 못했다.

출처: https://www.inflearn.com/questions/23530

2. Gradle annotationProcessor 사용

  • 기존 QueryDsl plugins 사용으로 발생하는 불편함과 에러를 해결하기 위한 방법을 찾던 도중 Gradle annotationProcessor로 QueryDsl을 사용하는 방법을 적용하였다.
  • 해당 방법은 Gradle build 에러도 발생하지 않고 Q Domain 경로를 자동으로 인식하므로, 해당 방법을 사용해야 한다.

출처: http://honeymon.io/tech/2020/07/09/gradle-annotation-processor-with-querydsl.html

의존성 관리

  • Querydsl 의존성과 Q Domain를 생성하는 annotationProcessor를 추가한다.
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
<build.gradle>

project(":module-domain-core") {
   dependencies {
       compile project(":module-system-common")

       implementation("com.querydsl:querydsl-core")
       implementation("com.querydsl:querydsl-jpa")
   }
}

...

def queryDslProjects = [project(":module-domain-core")]
configure(queryDslProjects) {
   dependencies {

       annotationProcessor("com.querydsl:querydsl-apt:${dependencyManagement.importedProperties["querydsl.version"]}:jpa") // querydsl JPAAnnotationProcessor 사용 지정
       annotationProcessor("jakarta.persistence:jakarta.persistence-api") // java.lang.NoClassDefFoundError(javax.annotation.Entity) 발생 대응
       annotationProcessor("jakarta.annotation:jakarta.annotation-api") // java.lang.NoClassDefFoundError (javax.annotation.Generated) 발생 대응
   }

   // clean 태스크 실행시 QClass 삭제
   clean {
       delete file("src/main/generated") // intelliJ Annotation processor Q Domain 생성 위치
   }
}


  • Gradle build를 수행하면 다음 이미지와 같이 프로젝트에서 Q Domain을 자동으로 생성하고, Q Domain 경로를 자동으로 인식한다.
  • 다음은 Gradle build 할 때 Q Domain을 자동으로 생성한 코드다.

image

image

출처: http://honeymon.io/tech/2020/07/09/gradle-annotation-processor-with-querydsl.html

Config

  • QueryDsl을 프로젝트 내에서 사용할 수 있도록 필요한 부분을 설정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<module-domain-core/kr/ac/univ/common/config/QueryDslConfig>

package kr.ac.univ.common.config;

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);
   }
}

Repository

  • QueryDsl를 사용하여 다음과 같은 쿼리를 작성하였다.
  • findByTitle: 제목으로 게시글을 검색한다.(테스트 용도로 구현)
  • updateViewCountById: 게시글 조회수를 1 증가시킨다.
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<module-domain-core/kr/ac/univ/noticeBoard/repository/NoticeBoardRepositoryImpl>

package kr.ac.univ.noticeBoard.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import kr.ac.univ.noticeBoard.domain.NoticeBoard;
import kr.ac.univ.noticeBoard.domain.QNoticeBoard;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;

import javax.transaction.Transactional;
import java.util.List;

import static kr.ac.univ.event.domain.QNoticeBoard.noticeBoard;

@Repository
@Transactional
public class NoticeBoardRepositoryImpl extends QuerydslRepositorySupport {
   private final JPAQueryFactory queryFactory;

   public NoticeBoardRepositoryImpl(JPAQueryFactory queryFactory ) {
       super(NoticeBoard.class);
       this.queryFactory = queryFactory;
   }

   public List<NoticeBoard> findByTitle(String title) {
       /*
        * SELECT *
        *   FROM notice_board
        *  WHERE title = 'title'
        */
       return queryFactory
               .selectFrom(noticeBoard)
               .where(noticeBoard.title.eq(title))
               .fetch();
   }

   public long updateViewCountById(Long idx) {
       /*
        * UPDATE notice_board
        *    SET view_count = view_count + 1
        *  WHERE id = 'id';
        */
       return queryFactory
               .update(noticeBoard)
               .set(noticeBoard.viewCount, noticeBoard.viewCount.add(1))
               .where(noticeBoard.idx.eq(idx))
               .execute();
   }

}

JUnit Test

  • 200개의 데이터를 등록한 다음, QueryDsl 작성한 findByTitle과 updateViewCountById 쿼리가 정상적으로 동작하는지 테스트 하였다.
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<module-app-web/src/test/java/kr/ac/univ/QueryDslTest>

package kr.ac.univ;

import kr.ac.univ.common.domain.enums.ActiveStatus;
import kr.ac.univ.noticeBoard.domain.NoticeBoard;
import kr.ac.univ.noticeBoard.repository.NoticeBoardRepository;
import kr.ac.univ.noticeBoard.repository.NoticeBoardRepositoryImpl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;

import static org.junit.jupiter.api.Assertions.assertEquals;

@Slf4j
@SpringBootTest
@EnableAutoConfiguration
@ExtendWith(SpringExtension.class)
public class QueryDslTest {
   @Autowired
   NoticeBoardRepository noticeBoardRepository;

   @Autowired
   NoticeBoardRepositoryImpl noticeBoardRepositoryImpl;

   @BeforeEach
   public void init() {
       IntStream.rangeClosed(1, 200)
               .forEach(index -> noticeBoardRepository.save(NoticeBoard.builder()
                       .title("게시글" + index)
                       .content("컨텐츠")
                       .viewCount(0L)
                       .activeStatus(ActiveStatus.ACTIVE)
                       .build()));
   }

   @Test
   @DisplayName("QueryDsl로 구현한 findByTitle 테스트")
   public void Test() {
       List<NoticeBoard> list = noticeBoardRepositoryImpl.findByTitle("게시글10");

       for (NoticeBoard noticeboard : list) {
           assertEquals(noticeboard.getTitle(), "게시글10");
       }
   }

   @Test
   @DisplayName("QueryDsl로 구현한 updateViewCountByIdx 테스트")
   public void Test2() {
       // update된 column 개수 반환
       Long cnt = noticeBoardRepositoryImpl.updateViewCountById(1L);
       assertEquals(cnt, 1);
   }

   @Test
   @DisplayName("JPA findById를 사용한 viewCount 테스트")
   public void Test3() {
       Optional<NoticeBoard> optionalNoticeBoard = noticeBoardRepository.findById(1L);
       NoticeBoard noticeBoard = null;

       if (optionalNoticeBoard.isPresent()) {
           noticeBoard = optionalNoticeBoard.get();
       }

       // idx 1인 column을 조회한다.
       assertEquals(noticeBoard.getIdx(), 1L);
       assertEquals(noticeBoard.getViewCount(), 1L);
   }
}

Service

  • NoticeBoard의 비즈니스 로직이다.
  • findNoticeBoardByIdx: 게시글을 조회할 때 NoticeBoardReposityImpl의 updateViewCountById 메소드를 호출하여 게시글 조회수를 1 증가시킨다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<module-domain-core/src/main/java/kr/ac/univ/noticeBoard/service/NoticeBoardService>

@Service
public class NoticeBoardService {
    private final NoticeBoardRepository noticeBoardRepository;
    private final NoticeBoardRepositoryImpl noticeBoardRepositoryImpl;

    public NoticeBoardService(NoticeBoardRepository noticeBoardRepository, NoticeBoardRepositoryImpl noticeBoardRepositoryImpl) {
        this.noticeBoardRepository = noticeBoardRepository;
        this.noticeBoardRepositoryImpl = noticeBoardRepositoryImpl;
    }

    ...

    public NoticeBoardDto findNoticeBoardByIdx(Long idx) {
        noticeBoardRepositoryImpl.updateViewCountById(idx);

        return NoticeBoardMapper.INSTANCE.toDto(noticeBoardRepository.findById(idx).orElse(new NoticeBoard()));
    }

    ...

프로젝트 실행 및 결과

  • 다음 이미지와 같이 게시글을 조회하는 경우 조회수가 1 증가하는 것을 확인할 수 있다.

image

This post is licensed under CC BY 4.0 by the author.

Project Lab 4. 게시판 개발 - 1

무기체계 소프트웨어의 정적시험 개요

Comments powered by Disqus.

Trending Tags