스프링 부트3 제 14장
게시판을 만들었으니 댓글 한번 달아 볼까?
댓글 엔티티와 리파지터리 설계
- 엔티티: DB 데이터를 담는 자바 객체로, 엔티티를 기반으로 테이블 생성
- 리파지터리: 엔티티를 관리하는 인터페이스로, 데이터 CRUD 등의 기능 제공
엔티티는 Article 엔티티를 만들듯이 만들면 된다. 단, 게시글과 일대다 관계를 유지하기 위해 @ManyToOne 어노테이션을 칼럼에 추가해야 한다.
3장에서 ArticleRepository를 만들 땐, CrudRepository를 사용했다. 이번엔 JpaRepository를 사용하여 CRUD뿐만 아니라 엔티티를 페이지 단위로 조회 및 정렬하는 기능과 JPA에 특화된 여러 기능들을 살펴보겠다.
댓글 엔티티
Comment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity // 해당 클래스가 엔티티임을 선언, 클래스 필드를 바탕으로 DB에 테이블 생성
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Comment {
@Id // id를 대표키로 지정
@GeneratedValue(strategy= GenerationType.IDENTITY) // DB가 자동으로 매김
private Long id;
@ManyToOne // Comment와 Article의 관계를 일대다로 설정
@JoinColumn(name="article_id") // 외래키 생성, Article의 기본키와 매핑
private Article article;
@Column // 해당 필드를 테이블의 속성으로 매핑
private String nickname;
@Column
private String body;
}
@ManyToOne 어노테이션을 통해 Comment 테이블과 Article 테이블을 일대다 관계로 설정했다.
또한 @JoinColumn을 통해 외래 키를 생성하고, Article 테이블의 기본 키와 매핑했다.
댓글 리파지터리
CommentRepository
1
2
3
4
5
6
7
public interface CommentRepository extends JpaRepository<Comment, Long> {
// 네이티브 쿼리 메서드를 사용하여 쿼리를 메서드로 작성할 것임
@Query(value = "select * from comment where article_id = :articleId", nativeQuery = true) // value 속성에 실행하려는 쿼리 작성
List<Comment> findByArticleId(Long articleId); // 특정 게시글의 모든 댓글 조회 메서드(@Query 어노테이션 사용)
List<Comment> findByNickname(String nickname); // 특정 닉네임의 모든 댓글 조회 메서드(orm.xml 사용, resources > META-INF)
}
쿼리를 메서드에서 사용하는 네이티브 쿼리 메서드(native query method)를 사용했다. 이것을 만들기 위해선 @Query 어노테이션을 쓰거나, 네이티브 쿼리 XML(native query XML) 파일을 쓰는 두 가지 방법이 있다.
참고로 @Query 어노테이션은 SQL과 유사한 JPQL(Java Persistence Query Language)를 사용한다. 하지만 여기선 nativeQuery를 true로 설정하여 SQL을 그대로 사용했다.
그리고 SQL 문의 매개변수 앞에는 반드시 콜론(:)을 붙여줘야 한다.
orm.xml의 경로는 resources/META-INF/orm.xml 이어야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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/persistance/orm/orm_3_0.xsd"
version="3.0">
<named-native-query name="Comment.findByNickname"
result-class="com.example.firstproject.entity.Comment">
<query>
<![CDATA[
select * from comment where nickname = :nickname
]]>
</query>
</named-native-query>
</entity-mappings>
테스트 코드
CommentRepositoryTest
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
// ... 생략
@DataJpaTest // 해당 클래스를 JPA와 연동해 테스팅
class CommentRepositoryTest {
@Autowired
CommentRepository commentRepository;
@Test
@DisplayName("특정 게시글의 모든 댓글 조회") // 메서드 이름은 그대로 두고 테스트 이름만 바꾸고 싶을 때
void findByArticleId() {
/* Case 1: 4번 게시글의 모든 댓글 조회 */
{
// 1. 입력 데이터 준비
Long articleId = 4L;
// 2. 실제 데이터
List<Comment> comments = commentRepository.findByArticleId(articleId);
// 3. 예상 데이터
Article article = new Article(4L, "당신의 인생 영화는?", "ㅈㄱㄴ");
Comment a = new Comment(1L, article, "Park", "굿 윌 헌팅");
Comment b = new Comment(2L, article, "Kim", "아이 엠 샘");
Comment c = new Comment(3L, article, "Choi", "쇼생크 탈출");
List<Comment> expected = Arrays.asList(a, b, c);
// 4. 비교 및 검증
assertEquals(expected.toString(), comments.toString(), "4번 게시글의 모든 댓글을 출력");
}
/* Case 2: 1번 게시글의 모든 댓글 조회 */
{
// 1. 입력 데이터 준비
Long articleId = 1L;
// 2. 실제 데이터
List<Comment> comments = commentRepository.findByArticleId(articleId);
// 3. 예상 데이터
Article article = new Article(1L, "aaaa", "1111");
List<Comment> expected = Arrays.asList();
// 4. 비교 및 검증
assertEquals(expected.toString(), comments.toString(), "1번 게시글은 댓글이 없음");
}
}
@Test
@DisplayName("특정 닉네임의 모든 댓글 조회")
void findByNickname() {
/* Case 1: Park의 모든 댓글 조회 */
{
// 1. 입력 데이터 준비
String nickname = "Park";
// 2. 실제 데이터
List<Comment> comments = commentRepository.findByNickname(nickname);
// 3. 예상 데이터
Comment a = new Comment(1L, new Article(4L, "당신의 인생 영화는?", "ㅈㄱㄴ"), "Park", "굿 윌 헌팅");
Comment b = new Comment(4L, new Article(5L, "당신의 소울 푸드는?", "ㅈㄱㄴ2222"), "Park", "치킨");
Comment c = new Comment(7L, new Article(6L, "당신의 취미는?", "ㅈㄱㄴ3333"), "Park", "조깅");
List<Comment> expected = Arrays.asList(a, b, c);
// 4. 비교 및 검증
assertEquals(expected.toString(), comments.toString(), "Park의 모든 댓글 출력");
}
}
}
@SpringBootTest 대신 @DataJpaTest 어노테이션을 써서 JPA와 연동해 테스트했다.
그리고 @DisplayName을 사용해 테스트에 이름을 붙여주었다.