[Spring Boot] #22. 댓글 Entity와 Repository (feat. 테스트) + (SQL) LIKE 연산자

gogori6565·2024년 8월 22일
0

스프링 부트 공부

목록 보기
20/20

👉 Mission. 댓글(Comment) 엔티티와 리파지터리를 만들고, 이를 테스트 하시오


일대다, 다대일 관계

  • One-to-Many (일대다 관계) : 하나의 게시글에 수많은 댓글 (게시글의 입장)
  • Many-to-One (다대일 관계) : 수많은 댓글에 하나의 게시글 (댓글의 입장)
  • PK (Primary Key, 기본키) : 자신을 대표하는 ID로, 유일하고 반드시 존재(NOT NULL)한다.
  • FK (Foreign Key, 외래키) : 대상을 가리키는 ID로, 다른 기본 키를 참조하는 속성 혹은 속성들의 집합이다.

💻 댓글 Entity와 Repository 구현 - 실습

1. 댓글 Entity 생성

파일명 : entity/Comment.java

@Entity
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne //해당 댓글 엔티티 여러개가, 하나의 Article에 연관된다 -> 관계 설정
    @JoinColumn(name = "article_id") //"article_id" 컬럼에 Article의 대표값을 저장
    private Article article;

    @Column
    private String nickname;

    @Column
    private String body;
}
  • @JoinColumn 은 주로 Entity의 연관관계에서 외래키를 매핑하기 위해 사용된다.
  • @JoinColumn의 속성인 name은 매핑할 외래 키의 이름을 지정하는 속성이다.

➕ JPA에서 Entity를 매핑할 떄 다음과 같은 2가지를 관습적으로 사용한다.
1. @JoinColumn을 사용하는 FK 필드의 이름을 대상 Entity(대상 Table)의 이름으로 설정

private Article article;
  1. @JoinColumn을 사용할 때 name 속성으로 대상 Entity(대상 Table)의 이름 + PK로 하드하게 지정
@ManytoOne
@JoinColumn(name = "article_id")
private Article article;

2. 댓글 더미 데이터 생성 (data.sql)

-- article 더미 데이터
INSERT INTO article(id, title, content) VALUES (4, '당신의 인생 영화는?', '댓글 ㄱ');
INSERT INTO article(id, title, content) VALUES (5, '당신의 소울 푸드는?', '댓글 ㄱㄱ');
INSERT INTO article(id, title, content) VALUES (6, '당신의 취미는?', '댓글 ㄱㄱㄱ');

-- comment 더미 데이터
---- 4번 게시글의 댓글들
INSERT INTO comment(id, article_id, nickname, body) VALUES (1, 4, 'Park', '굳 윌 헌팅');
INSERT INTO comment(id, article_id, nickname, body) VALUES (2, 4, 'Kim', '아이 엠 샘');
INSERT INTO comment(id, article_id, nickname, body) VALUES (3, 4, 'Choi', '쇼생크의 탈출');

---- 5번 게시글의 댓글들
INSERT INTO comment(id, article_id, nickname, body) VALUES (4, 5, 'Park', '치킨');
INSERT INTO comment(id, article_id, nickname, body) VALUES (5, 5, 'Kim', '샤브샤브');
INSERT INTO comment(id, article_id, nickname, body) VALUES (6, 5, 'Choi', '초밥');

---- 6번 게시글의 댓글들
INSERT INTO comment(id, article_id, nickname, body) VALUES (7, 6, 'Park', '조깅');
INSERT INTO comment(id, article_id, nickname, body) VALUES (8, 6, 'Kim', '유튜브');
INSERT INTO comment(id, article_id, nickname, body) VALUES (9, 6, 'Choi', '독서');
  • sql의 주석은 --

3. 댓글 Repository 구현

<네이티브 쿼리 사용방법 2가지>

  1. @Query 어노테이션 사용
  2. xml 파일 생성 방법

=> 둘 사이에 정답은 없고, 보통 쿼리가 복잡하거나 최적화가 필요하면 xml, 쿼리가 단순하면 어노테이션을 사용하는 편

파일명 : repository/CommentRepository.java

public interface CommentRepository extends JpaRepository<Comment, Long> {
    // 특정 게시글의 모든 댓글 조회
    @Query(value =
            "SELECT * " +
            "FROM comment " +
            "WHERE article_id = :articleId",
            nativeQuery = true)
    List<Comment> findByArticleId(Long articleId);

    // 특정 닉네임의 모든 댓글 조회
    List<Comment> findByNickname(String nickname);
}
  • JpaRepository : CrudRepository를 확장한 것으로 데이터 CRUD 뿐만 아니라 일정 페이지의 데이터 조회 및 정렬 기능
파일명 : resources/META-INF/orm.xml

<?xml version="1.0" encoding="utf-8" ?>
<entity-mappings xmlns="https://jakarta.ee/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence/orm
                                     https://jakarta.ee/xml/ns/persistence/orm/orm_3_0.xsd"
                 version="3.0">
    <named-native-query
            name="Comment.findByNickname"
            result-class="com.example.studyproject.entity.Comment">
        <query>
            <![CDATA[
                SELECT
                    *
                FROM
                    comment
                WHERE
                    nickname = :nickname
            ]]>
        </query>
    </named-native-query>
</entity-mappings>
  • (간단 설명) <named-native-query 부분 아래, name="Comment.findByNickname" : Comment Repository에 있는 findByNickname 이라는 메서드는 <query> 안에 있는 쿼리 내용을 수행하도록 하겠다는 의미.
    result-class="com.example.studyproject.entity.Comment"> : 그리고 이 쿼리가 반환할 값의 타입은 Comment 엔티티라는 의미.

💻 '특정 문자열 포함 닉네임의 모든 댓글 조회' 테스트 - 실습

닉네임에 "i" 가 들어간 유저가 작성한 모든 댓글을 조회하는 테스트 케이스를 작성해보자.

@Test
@DisplayName("특정 닉네임의 모든 댓글을 조회")
void findByNickname() {
    /* Case 5 : "i"가 포함된 닉네임의 모든 댓글 조회 */
    {
        // 입력 데이터를 준비
        String nickname = "i";
        Article article4 = new Article(4L, "당신의 인생 영화는?", "댓글 ㄱ");
        Article article5 = new Article(5L, "당신의 소울 푸드는?", "댓글 ㄱㄱ");
        Article article6 = new Article(6L, "당신의 취미는?", "댓글 ㄱㄱㄱ");
        Comment a = new Comment(2L, article4, "Kim", "아이 엠 샘");
        Comment b = new Comment(5L, article5, "Kim", "샤브샤브");
        Comment c = new Comment(8L, article6, "Kim", "유튜브");
        Comment d = new Comment(3L, article4, "Choi", "쇼생크의 탈출");
        Comment e = new Comment(6L, article5, "Choi", "초밥");
        Comment f = new Comment(9L, article6, "Choi", "독서");

        // 실제 수행
        List<Comment> comments = commentRepository.findByNickname(nickname);

        // 예상하기
        List<Comment> expected = Arrays.asList(a, d, b, e, c, f);

        // 검증
        assertEquals(expected.toString(), comments.toString(), "닉네임에 i를 포함한 닉네임의 모든 댓글을 출력");
    }
}

이렇게 테스트 케이스는 잘 만들었는데 동작은 잘 할까?
아니. 지금 상태의 코드로는 빈 리스트가 출력되어 '실패'하게 된다.

'i가 포함된'이 아니라 'i'인 닉네임의 댓글을 조회하기 때문이다.
그러니까 지금부터 nickname에 입력된 변수가 '포함된' 값을 찾을 수 있게 본 코드를 고쳐보자.

SQL문 : LIKE 연산자

SQL에서 문자열의 부분 일치를 조회할 때는 'LIKE' 연산자와 '%', '_' 기호를 활용한다.

  • %: 0개 이상의 모든 문자를 대체 (글자 개수를 지정하지 않는 와일드 카드)
  • : 1개 이상의 모든 문자를 대체 ( 개수 만큼 글자 수가 지정되는 와일드 카드)
SELECT * FROM comment WHERE nickname LIKE '%i%'

comment 테이블의 nickname 컬럼에서 i 를 포함하는 값을 확인한다.

SELECT * FROM comment WHERE nickname LIKE '_i_'

comment 테이블의 nickname 컬럼에서 i 앞뒤로 각각 1개의 문자가 있는 문자열인 값을 확인한다.

SQL문 : concat 함수

그럼 query 문에 LIKE '%:nickname%' 를 추가하면 해결이 될까해서 추가해봤으나 테스트케이스는 여전히 실패였다.

왜 그럴까? SQL문에는 문자열을 연결해주는 concat 함수가 있다.
concat 함수는 기본적으로 사용자가 지정한 것(문자열, 컬럼)등의 글자를 합쳐주거나 일괄적으로 글자를 추가하려고 할 때 사용한다.

<query>
    <![CDATA[
        SELECT
            *
        FROM
            comment
        WHERE
            nickname
        LIKE
            concat('%', :nickname, '%')
    ]]>
</query>

즉 위와 같이 코드를 작성해주면 nickname 변수에 들어온 값을 포함한 닉네임을 DB에서 찾아낼 수 있게 된다!


강의 출처 : https://www.youtube.com/watch?v=_vDACE13Ubc&list=PLyebPLlVYXCiYdYaWRKgCqvnCFrLEANXt&index=1 [스프링 부트 입문 - 홍팍]

profile
p(´∇`)q

0개의 댓글