~14.3 댓글 리파지터리 만들기(종이책 p.391~425)
게시판을 보면 하나의 게시글에 수많은 댓글이 달린다. 이러한 관계를 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에 특화된 여러 기능 등을 제공한다.
@Entity // 해당 클래스가 엔티티임을 선언, 클래스 필드를 바탕으로 DB에 테이블 생성
@Getter // 각 필드 값을 조회할 수 있는 getter 메서드 자동 생성
@ToString // 모든 필드를 출력할 수 있는 toString 메서드 자동 생성
@AllArgsConstructor // 모든 필드를 매개변수로 갖는 생성자 자동 생성
@NoArgsConstructor // 매개변수가 아예 없는 기본 생성자 자동 생성
Comment 엔티티는 id(대표키), article(댓글의 부모 게시글), nickname(댓글을 단 사람), body(댓글 본문)로 구성된다. 각 속성을 만들기 위해 다음과 같이 필드를 선언한다.
public class Comment {
private Long id;
private Article article;
private String nickname;
private String body;
}
id 필드에 @Id를 붙여 이 필드가 대표키임을 선언한다. @GeneratedValue를 붙여 대표키를 자동으로 1씩 증가시키는데, strategy=GenerationType.IDENTITY를 추가해 데이터를 생성할 때마다 DB가 알아서 id값을 1씩 증가하도록 한다.
댓글과 게시글은 다대일 관계이므로 article 필드에 @ManyToOne 어노테이션을 붙여 Comment 엔티티와 이 필드가 가리키는 Article 엔티티를 다대일 관계로 설정한다.
다대일 관계를 설정했다면 외래키도 매핑(연결)해 줘야 한다. 외래키 매핑은 @JoinColum 어노테이션을 사용하며 name 속성으로 매핑할 외래키 이름을 지정한다.
@JoinColumn(name=”외래키_이름”)
Article 엔티티의 id를 외래키로 지정할 것이므로 외래키 이름을 “article_id”로 짓는다.
이렇게 외래키를 설정하면 Comment 엔티티로 생성될 DB테이블에 article_id라는 속성이 만들어지고, 여기에 저장된 값을 Article 엔티티의 대표키인 id와 매핑한다.
나머지 nickname, body 필드에는 @Column 어노테이션을 붙인다.

SELECT * FROM comment WHERE article_id = 4;SELECT * FROM comment WHERE nickname = ‘Park’;Comment 엔티티를 DB에 저장할 수 있도록 댓글 리파지터리를 만들어 보고, 테스트 코드를 작성해 코드를 검증해본다.
repository 디렉터리에 CommentRepository라는 Interface를 만든다.
JpaRepository 인터페이스를 상속받도록 한다. 형식은 다음과 같다.
JpaRepository<대상_엔티티, 대표키_값의_타입>
Comment 엔티티를 관리하므로 대상 엔티티에는 Comment, 대표키 값의 타입에는 id의 타입인 Long을 넣는다.
public interface CommentRepository extends JpaRepository<Comment, Long> {}
특정 게시글의 모든 댓글 조회, 특정 닉네임의 모든 댓글 조회를 위한 쿼리를 메서드로 작성한다.
특정 게시글의 모든 댓글을 조회하는 메서드 이름을 findByArticleId로 짓고 매개변수로 articleId를 받는다. 메서드 실행 결과로 댓글의 묶음을 반환할 테니 반환형은 List<Comment>로 작성한다.
findByArticleId() 메서드로 원하는 쿼리를 수행하기 위해 @Query 어노테이션을 붙인다.
@Query(value = “쿼리”, nativeQuery = true)

메서드 이름은 findByNickname으로 짓고, 매개변수로 nickname을 받아온다. 메서드 실행 결과로 댓글의 묶음을 반환하므로 반환형은 List<Comment>로 작성한다.
findByNickname() 메서드에서 수행할 쿼리를 XML로 작성한다.
resources에 META-INF라는 디렉터리를 만들고 디렉터리에 orm.xml 파일을 생성한다.
orm.xml 파일에 다음과 같은 코드를 입력한다. 엔티티 매핑을 위한 XML로 굳이 외우지 않아도 된다.

<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. 입력 데이터 준비
// 2. 실제 데이터
// 3. 예상 데이터
// 4. 비교 및 검증
findByArticleId() 테스트
13장에서는 테스트의 메서드명을 한글로 직접 수정했었는데, 이번에는 메서드명을 수정하지 않고 @DisplayName 어노테이션을 사용한다.
@DisplayName(”테스트_결과에_보여_줄_이름”)
기본적으로 테스트 이름은 메서드 이름을 따라가는데, 메서드 이름은 그대로 둔 채 테스트 이름을 바꾸고 싶을 때 이 어노테이션을 사용한다.
4번 게시글의 모든 댓글을 조회하는 테스트를 해본다.
4번 게시글의 모든 댓글을 조회하므로 articleId에 4L을 넣는다. (입력 데이터 준비)
commentRepository.findByArticleId(articleId) 메서드를 호출해 얻은 결과를 comments 리스트에 저장한다. (실제 데이터 가져오기)
예상 데이터를 확인하기 위해 DB에 접속해 확인한다.

assertEquals() 메서드로 예상 데이터의 문자열(expected.toString())과 실제 데이터의 문자열(comments.toString())이 같은지 비교한다.
같은 방법으로 1번 게시글의 모든 댓글을 조회하는 테스트도 진행해본다.
방법은 같으나 1번 게시글에는 댓글이 없으므로 a, b, c 객체 생성 코드를 모두 삭제하고 마지막에 Arryas.asList() 메서드의 매개변수도 모두 삭제한다.
테스트가 정상적으로 통과하는 것을 확인할 수 있다.
findByNickname() 테스트
@DisplayName(”특정 닉네임의 모든 댓글 조회”)으로 테스트 이름 설정
Park의 모든 댓글을 조회해본다.
nickname에 “Park”를 넣는다. (입력 데이터 준비)
commentRepository.findByNickname(nickname) 메서드를 호출해 얻은 결과를 comments 리스트에 저장한다. (실제 데이터 가져오기)
예상 데이터를 작성하기 위해 DB에서 데이터를 확인한다.
assertEquals() 메서드로 예상 데이터의 문자열과 실제 데이터의 문자열이 같은지 비교하고 검증한다.

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