길벗 코딩자율학습단 14일차

donghan378·2025년 1월 26일

14일차

~14.3 댓글 리파지터리 만들기(종이책 p.391~425)

14장 댓글 엔티티와 리파지터리 만들기

14.1 댓글 기능의 개요

게시판을 보면 하나의 게시글에 수많은 댓글이 달린다. 이러한 관계를 one-to-many, 즉 일대다(1:n) 관계라고 한다. 반대로 댓글 입장에서 보면 여러 댓글이 하나의 게시글에 달리므로 many-to-one, 즉 다대일(n:1) 관계이다.

댓글을 저장하는 comment 테이블에는 id와 article_id라는 속성으로 구성할 수 있는데, id는 대표키(primary key), 또는 기본키이고 연관 대상을 가리키는 article_id는 외래키(Foreign Key)라고 한다. 외래키는 항상 연관된 테이블의 대표키를 가리킨다.

게시글에 댓글을 달기 위해 앞서 Article 엔티티와 ArticleRepository를 작성한 것처럼, Comment 엔티티와 CommentRepository를 만든다.

ArticleRepository를 만들 때는 CrudRepository를 상속받았지만, CommentRepository는 JpaRepository를 상속받도록 한다.

JpaRepository는 ListCrudRepository와 ListPagingAndSortingRepository를 상속받은 인터페이스로, CRUD 기능뿐만 아니라 엔티티를 페이지 단위로 조회 및 정렬하는 기능과 JPA에 특화된 여러 기능 등을 제공한다.

14.2 댓글 엔티티 만들기

  1. com.example.firstproejct/entity 에 Comment 라는 자바 클래스를 만든다.
  2. 클래스에서 사용할 여러가지 어노테이션을 추가한다.
    @Entity // 해당 클래스가 엔티티임을 선언, 클래스 필드를 바탕으로 DB에 테이블 생성
    @Getter // 각 필드 값을 조회할 수 있는 getter 메서드 자동 생성
    @ToString // 모든 필드를 출력할 수 있는 toString 메서드 자동 생성
    @AllArgsConstructor // 모든 필드를 매개변수로 갖는 생성자 자동 생성
    @NoArgsConstructor // 매개변수가 아예 없는 기본 생성자 자동 생성
  1. Comment 엔티티는 id(대표키), article(댓글의 부모 게시글), nickname(댓글을 단 사람), body(댓글 본문)로 구성된다. 각 속성을 만들기 위해 다음과 같이 필드를 선언한다.

    public class Comment {
    	private Long id;
    	private Article article;
    	private String nickname;
    	private String body;
    }
  2. id 필드에 @Id를 붙여 이 필드가 대표키임을 선언한다. @GeneratedValue를 붙여 대표키를 자동으로 1씩 증가시키는데, strategy=GenerationType.IDENTITY를 추가해 데이터를 생성할 때마다 DB가 알아서 id값을 1씩 증가하도록 한다.

  3. 댓글과 게시글은 다대일 관계이므로 article 필드에 @ManyToOne 어노테이션을 붙여 Comment 엔티티와 이 필드가 가리키는 Article 엔티티를 다대일 관계로 설정한다.

  4. 다대일 관계를 설정했다면 외래키도 매핑(연결)해 줘야 한다. 외래키 매핑은 @JoinColum 어노테이션을 사용하며 name 속성으로 매핑할 외래키 이름을 지정한다.

    @JoinColumn(name=”외래키_이름”)

    Article 엔티티의 id를 외래키로 지정할 것이므로 외래키 이름을 “article_id”로 짓는다.

    이렇게 외래키를 설정하면 Comment 엔티티로 생성될 DB테이블에 article_id라는 속성이 만들어지고, 여기에 저장된 값을 Article 엔티티의 대표키인 id와 매핑한다.

  5. 나머지 nickname, body 필드에는 @Column 어노테이션을 붙인다.

더미 데이터 추가하기

  1. 더미 데이터는 data.sql 파일에 추가한다.
  2. article 테이블에 4, 5, 6번 게시글을 추가한다.
  3. comment 테이블에 4, 5, 6번 게시글의 댓글을 추가한다.

    ※ SQL 문에서 한 줄 주석은 --(하이픈 2개)로, 여러 줄 주석은 /**/ 형태로 처리한다.

댓글 조회 쿼리 연습하기

  • 특정 게시글의 모든 댓글 조회
    4번 게시글의 모든 댓글 조회
    SELECT * FROM comment WHERE article_id = 4;
  • 특정 닉네임의 모든 댓글 조회
    nickname이 Park인 댓글 조회
    SELECT * FROM comment WHERE nickname = ‘Park’;

14.3 댓글 리파지터리 만들기

Comment 엔티티를 DB에 저장할 수 있도록 댓글 리파지터리를 만들어 보고, 테스트 코드를 작성해 코드를 검증해본다.

댓글 리파지터리 만들기

  1. repository 디렉터리에 CommentRepository라는 Interface를 만든다.

  2. JpaRepository 인터페이스를 상속받도록 한다. 형식은 다음과 같다.

    JpaRepository<대상_엔티티, 대표키_값의_타입>

    Comment 엔티티를 관리하므로 대상 엔티티에는 Comment, 대표키 값의 타입에는 id의 타입인 Long을 넣는다.

    public interface CommentRepository extends JpaRepository<Comment, Long> {}
    • cmd 를 누른 채로 JpaRepository를 클릭하면 세부 코드 내용을 볼 수 있다. 코드 상단을 보면 JpaRepository가 ListCrudRepository와 ListPagingAndSortingRepository 인터페이스를 상속받아 만들어진 것을 알 수 있다.
  3. 특정 게시글의 모든 댓글 조회, 특정 닉네임의 모든 댓글 조회를 위한 쿼리를 메서드로 작성한다.

    • 쿼리를 메서드로 작성하는 것이 가능한데, 이러한 메서드를 네이티브 쿼리 메서드(native query method)라고 한다. 네이티브 쿼리 메서드는 직접 작성한 SQL 쿼리를 리파지터리 메서드로 실행할 수 있게 해준다.
    • 네이티브 쿼리 메서드를 만드는 방법은 2가지인데, @Query 어노테이션을 이용하거나 orm.xml 파일을 이용하면 된다. 여기서는 특정 게시글의 모든 댓글 조회는 @Query 어노테이션으로, 특정 닉네임의 모든 댓글 조회는 orm.xml 파일을 이용해 구현해본다.

특정 게시글의 모든 댓글 조회

  1. 특정 게시글의 모든 댓글을 조회하는 메서드 이름을 findByArticleId로 짓고 매개변수로 articleId를 받는다. 메서드 실행 결과로 댓글의 묶음을 반환할 테니 반환형은 List<Comment>로 작성한다.

  2. findByArticleId() 메서드로 원하는 쿼리를 수행하기 위해 @Query 어노테이션을 붙인다.

    • 작성 형식은 다음과 같다.
    	@Query(value = “쿼리”, nativeQuery = true)
    • @Query 어노테이션은 SQL과 유사한 JPQL(Java Persistence Query Language)이라는 객체 지향 쿼리 언어를 통해 복잡한 쿼리 처리를 지원한다. native 속성을 true로 하면 기존 SQL문을 그대로 사용할 수 있다. 주의할 점은 SQL 문의 WHERE 절에 조건을 쓸 때 매개변수 앞에는 꼭 콜론(:)을 붙여 줘야 한다는 점이다.

특정 닉네임의 모든 댓글 조회

  1. 메서드 이름은 findByNickname으로 짓고, 매개변수로 nickname을 받아온다. 메서드 실행 결과로 댓글의 묶음을 반환하므로 반환형은 List<Comment>로 작성한다.

  2. findByNickname() 메서드에서 수행할 쿼리를 XML로 작성한다.

    • 이러한 XML을 네이티브 쿼리 XML(native query XML)이라고 한다.
    • 네이티브 쿼리 XML의 기본 경로와 파일 이름은 META-INF/orm.xml이다. 이 경로로 파일을 만들면 XML이 자동으로 인식된다.
  3. resources에 META-INF라는 디렉터리를 만들고 디렉터리에 orm.xml 파일을 생성한다.

    • 디렉터리 이름과 파일 이름은 반드시 META-INF, orm.xml 로 작성해야 한다.
  4. orm.xml 파일에 다음과 같은 코드를 입력한다. 엔티티 매핑을 위한 XML로 굳이 외우지 않아도 된다.

  5. <entity-mappings> 태그 안에 <named-native-query><query> 태그를 이용해 쿼리를 입력한다.

    • 형식은 다음과 같다.
    <named-native-query
            name=”쿼리_수행_대상_엔티티.메서드_이름”
          	result-class=”쿼리_수행_결과_반환하는_타입의_전체_패키지_경로”>
    	<query>
    		<![CDATA[
    			*<!— 쿼리 —>*
    		]]>
    	</query>
    </named-native-query>
    • <query> 태그에는 실제 수행할 쿼리를 적는다. 이때 쿼리는 <![CDATA[ … ]]> 구문 안에 적는데, 이 구문은 Character DATA, 즉 파싱되지 않은 문자 데이터를 쓸 때 사용한다. 이 구문을 사용해야 SQL 문의 값의 대소 비교 연산 등에 문제가 생기지 않는다.

서버를 재시작해서 문제없이 동작하는지 확인한다.

댓글 리파지터리 테스트 코드 작성하기

findByArticleId() 메서드와 findByNickname() 메서드가 문제없이 동작하는지 테스트 코드를 작성해 확인해 본다.

  1. findByArticleId()와 findByNickname 메서드의 테스트 파일을 만든다.
  2. ArticleService를 테스트할 때 @SpringBootTest 어노테이션을 붙여 스프링 부트와 연동했을 때와는 다르게, 리파지터리를 테스트하므로 @DataJpaTest 어노테이션을 붙인다. @DataJpaTest는 해당 클래스를 JPA와 연동해 테스트하겠다는 선언이다.
  3. CommentRepository를 테스트하기 위해 commentRepository 객체를 선언한다. 외부 객체를 주입해야 하므로 @Autowired를 붙여준다.

이렇게 까지가 테스트 코드의 기본 틀이다.

앞으로 테스트 케이스를 여러개 작성할 것이므로 테스트마다 중괄호({})로 묶고 테스트 단계를 주석으로 써 놓고 시작한다.

테스트 단계는 다음과 같다.

// 1. 입력 데이터 준비
// 2. 실제 데이터
// 3. 예상 데이터
// 4. 비교 및 검증
  • findByArticleId() 테스트

    1. 13장에서는 테스트의 메서드명을 한글로 직접 수정했었는데, 이번에는 메서드명을 수정하지 않고 @DisplayName 어노테이션을 사용한다.

      @DisplayName(”테스트_결과에_보여_줄_이름”)

      기본적으로 테스트 이름은 메서드 이름을 따라가는데, 메서드 이름은 그대로 둔 채 테스트 이름을 바꾸고 싶을 때 이 어노테이션을 사용한다.

    2. 4번 게시글의 모든 댓글을 조회하는 테스트를 해본다.

    3. 4번 게시글의 모든 댓글을 조회하므로 articleId에 4L을 넣는다. (입력 데이터 준비)

    4. commentRepository.findByArticleId(articleId) 메서드를 호출해 얻은 결과를 comments 리스트에 저장한다. (실제 데이터 가져오기)

    5. 예상 데이터를 확인하기 위해 DB에 접속해 확인한다.

      1. comment 테이블에서 확인한 4번 게시글의 댓글을 Comment a, b, c 객체에 저장한다.
      2. a, b, c 객체의 두 번째 필드는 부모 게시글인 article이다. 따라서 4번 게시글에 대한 객체를 생성한다.
      3. 마지막으로 a, b, c 객체를 하나의 리스트로 합치고 expected 리스트에 저장한다.
    6. assertEquals() 메서드로 예상 데이터의 문자열(expected.toString())과 실제 데이터의 문자열(comments.toString())이 같은지 비교한다.

    7. 같은 방법으로 1번 게시글의 모든 댓글을 조회하는 테스트도 진행해본다.

    8. 방법은 같으나 1번 게시글에는 댓글이 없으므로 a, b, c 객체 생성 코드를 모두 삭제하고 마지막에 Arryas.asList() 메서드의 매개변수도 모두 삭제한다.

    9. 테스트가 정상적으로 통과하는 것을 확인할 수 있다.

  • findByNickname() 테스트

    1. @DisplayName(”특정 닉네임의 모든 댓글 조회”)으로 테스트 이름 설정

    2. Park의 모든 댓글을 조회해본다.

    3. nickname에 “Park”를 넣는다. (입력 데이터 준비)

    4. commentRepository.findByNickname(nickname) 메서드를 호출해 얻은 결과를 comments 리스트에 저장한다. (실제 데이터 가져오기)

    5. 예상 데이터를 작성하기 위해 DB에서 데이터를 확인한다.

      1. Park이 작성한 댓글 1, 4, 7번 데이터를 Comment a, b, c 객체에 저장한다. 그러나 댓글의 부모 게시글이 모두 다르므로 a, b, c 객체 생성 시 article 필드에 각각 객체를 생성한다.
      2. a, b, c 객체를 하나의 리스트로 합치고 이를 expected 리스트에 저장한다.
    6. assertEquals() 메서드로 예상 데이터의 문자열과 실제 데이터의 문자열이 같은지 비교하고 검증한다.

    7. 테스트가 잘 통과하는 것을 확인할 수 있다.

0개의 댓글