본문 바로가기
Framekwork/SPRING

[스프링부트] Querydsl 사용 테스트 01 (댓글 있는 리스트 전체 조회)

by 아이엠제니 2024. 3. 9.

 


 

 

 

Querydsl에 대해 찾아보고, 적용해 본 후?

내가 직접 간단한 설계를 해보고, Querydsl까지 적용해보고 싶다는 생각을 했다.

물론 너무 복잡한 건 아직 무리이다.

그리고 이번에 이 연습을 하면서 느낀 건, Query에 대해서도 더 공부가 필요하다는 것이다.

할 게 많군요..!

 

 

 

👉 DB 설계

ERD CLOUD

DB 설계를 위해 이용한 툴은 ERD CLOUD이다.

아티스트가 메인인데, 엔터테인먼트를 메인으로 해버렸...

이건 뭐 필요에 따라 바꾸면 될 것 같다.

여러 가지 생각을 하다가, 구 덕질을 해본 입장으로?

이게 가장 생각하기 좋은 것 같아서, 이 주제로 해봤다.

내가 생각한 관계는 이러하다. (이해한 게 맞는지,,, 모르겠지만, 틀린 게 있다면 알려주시면 감사하겠습니다.)

 

  • 엔터테인먼트-아티스트: 1:N
    • 하나의 엔터테인먼트에는 여러 아티스트가 있다고 가정한다.
    • 아티스트는 한 소속사만 가질 수 있다. (물론 요즘 여러 소속사에 속하기도 하지만... 하나에만 속한다고 가정한다.)
  • 아티스트-앨범: 1:N
    • 아티스트는 여러 개의 앨범을 가질 수 있다.
  • 앨범-구입처: N:N
    • 하나의 앨범은 여러 구입처에서 구입할 수 있다.
    • 구입처에서는 여러 앨범을 가지고 있을 수 있다.
  • 구입처-댓글: 1:N
    • 한 구입처의 앨범에는 여러 개의 댓글이 달릴 수 있다.

 

이렇게 관계를 생각해 봤다.

댓글을 달 때는 가입한 사람만이 달 수 있게 하는 게 맞겠지만?

지금 상황에서는 꽤나 복잡해질 것 같고, 지금은 Querydsl을 연습해 보는 게 먼저라서 복잡한 건 배제했다.

 

 

 

0. Querydsl 관련 설정

💾 pom.xml

        <!-- QueryDSL -->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <version>5.0.0</version>
            <classifier>jakarta</classifier>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>5.0.0</version>
            <classifier>jakarta</classifier>
        </dependency>

 

QueryDSL 관련 의존성은 위처럼 설정을 했다.

보통 구글링을 해보면, 의존성 추가할 때 플러그인 설정도 같이 하는 것 같은데!

최신 버전에서는 플러그인 추가는 안 해도 된다.

https://devje.tistory.com/280

그것 때문에 한참을 헤맸다...

 

💾 config/QuerytDslConfiguration.java

package com.example.querydsltest.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QueryDslConfiguration {

    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {

        return new JPAQueryFactory(entityManager);
    }
}

 

Querydsl 사용을 위한 설정 클래스도 추가한다.

 

 

 


1. entity

DB 설계한 것을 바탕으로 entity를 먼저 작성했다.

관계를 생각하며,  join컬럼도 사용했다.

 

💾 Entertainment.java

package com.example.querydsltest.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = "Entertainment")
@Getter
@Setter
public class Entertainment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long enterId;

    private String enterName;
    
}

 

💾 Artist.java

package com.example.querydsltest.entity;

import com.example.querydsltest.entity.Entertainment;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = "Artist")
@Getter
@Setter
public class Artist {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long artistId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "enter_id")
    private Entertainment enterId;

    private String artistName;
    
}

 

💾 Album.java

package com.example.querydsltest.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = "Album")
@Getter
@Setter
public class Album {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long albumId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "artist_id")
    private Artist artistId;

    private String albumName;

    private int albumPrice;
    
}

 

💾 Place.java

package com.example.querydsltest.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = "Place")
@Getter
@Setter
public class Place {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long placeId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "album_id")
    private Album albumId;

    private String placeName;
    
}

 

💾 Comment.java

package com.example.querydsltest.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = "Comment")
@Getter
@Setter
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long commentId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "place_id")
    private Place placeId;

    private String commentUser;

    private String comment;
    
}

 

 

 


2. 임시 데이터

entity 생성한 이후에 한 번 실행을 해서, db 생성을 한 후에!

임시 데이터를 밀어 넣었다.

혹시라도 따라 할 사람이 있을지 모르겠지만..?!

참고하실 분들을 위해 임시 데이터도 공유한다.

 

-- Entertainment
insert into entertainment (enter_id, enter_name) values(1,"SM");
insert into entertainment (enter_id, enter_name) values(2,"YG");
insert into entertainment (enter_id, enter_name) values(3,"JYP");
insert into entertainment (enter_id, enter_name) values(4,"하이브");

-- Artist
insert into artist (enter_id, artist_name) values(1,"동방신기");
insert into artist (enter_id, artist_name) values(1,"샤이니");
insert into artist (enter_id, artist_name) values(1,"레드벨벳");
insert into artist (enter_id, artist_name) values(1,"에스파");
insert into artist (enter_id, artist_name) values(2,"빅뱅");
insert into artist (enter_id, artist_name) values(2,"블랙핑크");
insert into artist (enter_id, artist_name) values(2,"위너");
insert into artist (enter_id, artist_name) values(2,"악동뮤지션");
insert into artist (enter_id, artist_name) values(3,"2PM");
insert into artist (enter_id, artist_name) values(3,"데이식스");
insert into artist (enter_id, artist_name) values(3,"트와이스");
insert into artist (enter_id, artist_name) values(3,"스트레이키즈");
insert into artist (enter_id, artist_name) values(4,"방탄소년단");
insert into artist (enter_id, artist_name) values(4,"뉴진스");
insert into artist (enter_id, artist_name) values(4,"투모로우바이투게더");
insert into artist (enter_id, artist_name) values(4,"르세라핌");

-- Album
insert into album (artist_id, album_name,album_price) values(1,"Tri-Angle",12000);
insert into album (artist_id, album_name,album_price) values(1,"MIROTIC",15000);
insert into album (artist_id, album_name,album_price) values(5,"Bigbang Vol.1",13000);
insert into album (artist_id, album_name,album_price) values(5,"Stand Up",14000);
insert into album (artist_id, album_name,album_price) values(10,"Every DAY6 February",15000);
insert into album (artist_id, album_name,album_price) values(10,"The Book of Us : Gravity",10000);
insert into album (artist_id, album_name,album_price) values(13,"화양연화 Young Forever",13000);
insert into album (artist_id, album_name,album_price) values(13,"WINGS",12000);

-- Place
insert into place (album_id,place_name) values(1,"예스24");
insert into place (album_id,place_name) values(2,"알라딘");
insert into place (album_id,place_name) values(3,"교보문고");
insert into place (album_id,place_name) values(4,"예스24");
insert into place (album_id,place_name) values(5,"알라딘");
insert into place (album_id,place_name) values(6,"교보문고");
insert into place (album_id,place_name) values(7,"예스24");
insert into place (album_id,place_name) values(8,"알라딘");
insert into place (album_id,place_name) values(1,"교보문고");

-- Comment
insert into comment (place_id, comment_user, comment) values(1,"하늘","최고!!!");
insert into comment (place_id, comment_user, comment) values(2,"슬기","멋있어요!!!");
insert into comment (place_id, comment_user, comment) values(3,"홍","짱!!!");
insert into comment (place_id, comment_user, comment) values(4,"민","와우!!!");
insert into comment (place_id, comment_user, comment) values(5,"종이","최고네요!!!");
insert into comment (place_id, comment_user, comment) values(6,"제인","닥치고 구매!!!");
insert into comment (place_id, comment_user, comment) values(7,"미나","귀호강!!!");
insert into comment (place_id, comment_user, comment) values(8,"큘라","재구매 각!!!");
insert into comment (place_id, comment_user, comment) values(1,"미리","재구매!!!");
insert into comment (place_id, comment_user, comment) values(6,"데이","미쳤다!!!");

 

 

 

조회

select
e.enter_id , e.enter_name,
a.artist_name,
a1.album_name, 
a1.album_price,
p.place_name , 
c.comment_user, c.comment 
from entertainment e 
inner join artist a on a.enter_id = e.enter_id
inner join album a1 on a1.artist_id = a.artist_id
inner join place p ON p.album_id = a1.album_id
inner join comment c on c.place_id = p.place_id ;

임시 데이터까지 모두 넣은 후에!

전체 db를 join해서 어떻게 나오는지 확인을 해본다.

 

 

최종적으로 나는 댓글이 있는 데이터들만 출력하고 싶다.

이제 이걸 QueryDSL을 이용해 작업하면 된다.

 

 

 


3. DTO

💾 ResponseDto.java

package com.example.querydsltest.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseDto {
    private Long enterId;
    
    private String enterName;
    
    private String artistName;
    
    private String albumName;
    
    private int albumPrice;
    
    private String placeName;
    
    private String commentUser;
    
    private String comment;
    
}

그전에 dto를 먼저 작성한다.

호출했을 때 위 필드들을 출력할 거다.

 

 

 


4. Repository

package com.example.querydsltest.repository;

import com.example.querydsltest.dto.EntertainmentResponseDto;
import com.example.querydsltest.entity.*;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;

import java.util.List;

public class EntertainmentRepositoryImpl {

    private final JPAQueryFactory queryFactory;

    public EntertainmentRepositoryImpl(JPAQueryFactory queryFactory) {
        this.queryFactory = queryFactory;
    }

    QEntertainment qEntertainment = QEntertainment.entertainment;
    QArtist qArtist = QArtist.artist;
    QAlbum qAlbum = QAlbum.album;
    QPlace qPlace = QPlace.place;
    QComment qComment = QComment.comment1;

    public List<EntertainmentResponseDto> searchList() {
        return queryFactory
                .select(Projections.constructor(EntertainmentResponseDto.class,
                        qEntertainment.enterId,
                        qEntertainment.enterName,
                        qArtist.artistName,
                        qAlbum.albumName,
                        qAlbum.albumPrice,
                        qPlace.placeName,
                        qComment.commentUser,
                        qComment.comment))
                .from(qEntertainment)
                .innerJoin(qArtist).on(qArtist.enterId.enterId.eq(qEntertainment.enterId))
                .innerJoin(qAlbum).on(qAlbum.artistId.artistId.eq(qArtist.artistId))
                .innerJoin(qPlace).on(qPlace.albumId.albumId.eq(qAlbum.albumId))
                .innerJoin(qComment).on(qComment.placeId.placeId.eq(qPlace.placeId))
                .fetch();
    }
}

그리고 위에서 쿼리 조회 시 작성했던 쿼리를 참고하여!

Querydsl 문법을 이용해, 다시 작성한다.

 

 

 


5. Service

💾 EntertainmentService.java

package com.example.querydsltest.service;

import com.example.querydsltest.dto.EntertainmentResponseDto;
import com.example.querydsltest.repository.EntertainmentRepositoryImpl;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@RequiredArgsConstructor
@Service
public class EntertainmentService {
    private final EntertainmentRepositoryImpl entertainmentRepositoryImpl;

    @Transactional
    public List<EntertainmentResponseDto> searchList() {
        return entertainmentRepositoryImpl.searchList();
    }
}

전체 조회를 할 것이기 때문에, service에서는 전체 조회를 return 한다.

 

 

 


6. Controller

💾 EntertainmentController.java

package com.example.querydsltest.controller;

import com.example.querydsltest.dto.EntertainmentResponseDto;
import com.example.querydsltest.service.EntertainmentService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
@RequestMapping("/enter")
@RequiredArgsConstructor
public class EntertainmentController {

    private final EntertainmentService entertainmentService;

    @GetMapping("/list")
    public ResponseEntity<List<EntertainmentResponseDto>> list() {
        List<EntertainmentResponseDto> entertainment = entertainmentService.searchList();
        if (!entertainment.isEmpty()) {
            return ResponseEntity.ok(entertainment);
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
        }
    }
}

그리고 컨트롤까지 작성을 한다.

http:/localhost:8080/enter/list

 

get으로 호출했을 때!

원하는 결과를 출력할 수 있다.

 

 

 


7. Postman으로 조회

포스트맨에서 호출을 해본다.

파라미터는 없기 때문에, 그냥 'send' 버튼을 클릭하면 된다.

 

 

 

오!

잘 나온다.

 

 

 

쿼리로 확인 시, 총 10개의 행이 출력되었는데!

 

 

 

[
    {
        "enterId": 1,
        "enterName": "SM",
        "artistName": "동방신기",
        "albumName": "Tri-Angle",
        "albumPrice": 12000,
        "placeName": "예스24",
        "commentUser": "하늘",
        "comment": "최고!!!"
    },
    {
        "enterId": 1,
        "enterName": "SM",
        "artistName": "동방신기",
        "albumName": "Tri-Angle",
        "albumPrice": 12000,
        "placeName": "예스24",
        "commentUser": "미리",
        "comment": "재구매!!!"
    },
    {
        "enterId": 1,
        "enterName": "SM",
        "artistName": "동방신기",
        "albumName": "MIROTIC",
        "albumPrice": 15000,
        "placeName": "알라딘",
        "commentUser": "슬기",
        "comment": "멋있어요!!!"
    },
    {
        "enterId": 2,
        "enterName": "YG",
        "artistName": "빅뱅",
        "albumName": "Stand Up",
        "albumPrice": 14000,
        "placeName": "교보문고",
        "commentUser": "홍",
        "comment": "짱!!!"
    },
    {
        "enterId": 2,
        "enterName": "YG",
        "artistName": "빅뱅",
        "albumName": "Bigbang Vol.1",
        "albumPrice": 13000,
        "placeName": "예스24",
        "commentUser": "민",
        "comment": "와우!!!"
    },
    {
        "enterId": 3,
        "enterName": "JYP",
        "artistName": "데이식스",
        "albumName": "Every DAY6 February",
        "albumPrice": 15000,
        "placeName": "알라딘",
        "commentUser": "종이",
        "comment": "최고네요!!!"
    },
    {
        "enterId": 3,
        "enterName": "JYP",
        "artistName": "데이식스",
        "albumName": "The Book of Us : Gravity",
        "albumPrice": 10000,
        "placeName": "교보문고",
        "commentUser": "제인",
        "comment": "닥치고 구매!!!"
    },
    {
        "enterId": 3,
        "enterName": "JYP",
        "artistName": "데이식스",
        "albumName": "The Book of Us : Gravity",
        "albumPrice": 10000,
        "placeName": "교보문고",
        "commentUser": "데이",
        "comment": "미쳤다!!!"
    },
    {
        "enterId": 4,
        "enterName": "하이브",
        "artistName": "방탄소년단",
        "albumName": "화양연화 Young Forever",
        "albumPrice": 13000,
        "placeName": "예스24",
        "commentUser": "미나",
        "comment": "귀호강!!!"
    },
    {
        "enterId": 4,
        "enterName": "하이브",
        "artistName": "방탄소년단",
        "albumName": "WINGS",
        "albumPrice": 12000,
        "placeName": "알라딘",
        "commentUser": "큘라",
        "comment": "재구매 각!!!"
    }
]

실제로도 10개가 출력된 것을 볼 수 있었다.

 

이 이후에는!

enterId, enterName, artistName, albumName, albumPrice, placeName 까지 동일한 내용이면 한 번만 출력이 되고!

commentUser, comment만  list에 담아서 출력될 수 있게 수정을 해보려고 한다.

 

 

 

300x250