하나의 게시글에 다수의 댓글이 달림. one-to-many = 일대다(1 : n)관계라고 함.many-to-one = 다대일(n : 1)관계임.article 테이블, comment 테이블이 있고 각 테이블마다 PK가 있음.comment테이블에 article_id속성을 추가해서 article 테이블과 조인을 걸면 됨.FK키를 통해 one-to-many관계를 설정.JPAEntityRepositoryJpaRepositoryListCrudRepository와 ListPagingAndSortingRepository를 상속받는 인터페이스.CRUD뿐만 아니라 엔터티를 페이지 단위로 조회 및 정렬하는 기능과 JPA에 특화된 여러 기능 등을 제공함.JpaRepository 인터페이스의 계층 구조. (출처)

RepositoryCrudRepository, ListCrudRepositoryCRUD 기능 제공.ListPagingAndSortingRepository, PagingAndSortingRepositoryJpaRepositoryCRUD 기능과 페이징 및 정렬 기능 뿐만 아니라 JPA에 특화된 기능을 추가로 제공.@Entity
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "article_id")
private Article article;
@Column
private String nickname;
@Column
private String body;
}
@Entity@Getter@ToString@AllArgsConstructor@NoArgsConstructor@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@ManyToOne@JoinColumn(name = "article_id")@ColumnH2 DB에서 확인.

더미 데이터 추가.
| Article | Comment |
|---|---|
![]() | ![]() |
PK는 각 테이블의 id, FK는 article 테이블의 대표키를 참조하는 article_idpublic interface CommentRepository extends JpaRepository<Comment, Long> {
}
JpaRepository<Comment, Long><>안에 대상 엔터티, PK 값의 타입을 적어주면 됨.CRUD작업만 한다면 CrudRepository를 사용해도 충분.CRUD + 페이징 처리, 정렬 작업을 해야 한다면 JpaRepository를 사용하는 것이 좋음.네이티브 쿼리 메서드(Native query method)라고 함.네이티브 쿼리 메서드는 작성한 SQL쿼리를 Repository 메서드로 실행할 수 있게 해줌.만드는 방법.@Query 어노테이션을 이용.orm.xml 파일을 이용. @Query(value = "SELECT * FROM comment WHERE article_id = :articleId", nativeQuery = true)
List<Comment> findByArticleId(Long ArticleId);
findByArticleId메서드로 원하는 쿼리를 실행하기 위해 @Query어노테이션을 붙임.@Query(value = "SQL Query", nativeQuery = true)@Query어노테이션은 SQL과 유사한 JPQL(Java Persistence Query Language)이라는 객체 지향 쿼리 언어를 통해 복잡한 쿼리 처리하는 걸 지원해줌.nativeQuery 속성을 true로 하면 기존 SQL문을 그대로 사용할 수 있음.:)을 붙여줘야 메서드에서 넘긴 매개변수와 매칭됨.List<Comment> findByNickname(String nickname);
XML로 작성.네이티브 쿼리 XML(Native query XML)이라고 함.기본 경로와 파일 이름은 resources -> META-INF -> orm.xml
엔터티 매핑을 위한 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">
</entity-mappings>
<entity-mappings>태그 안에 <named-native-query>와 <query>태그를 이용해서 쿼리를 입력. <named-native-query name="쿼리를 수행하는 대상 엔터티.메서드 이름"
result-class="쿼리 수행 결과 반환하는 타입의 전체 패키지 경로">
<query>
<![CDATA[
<!-- 쿼리 입력 -->
]]>
</query>
</named-native-query>
<![CDATA[ ]]> 구문은 Character DATAA, 즉 파싱(구문 분석)되지 않은 문자 데이터를 쓸 때 사용함.전체 코드.
<?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="findByNickname"
result-class="com.project.articlecomment.entity.Comment">
<query>
<![CDATA[
SELECT * FROM comment WHERE nickname = :nickname
]]>
</query>
</named-native-query>
</entity-mappings>
@DataJpaTest
class CommentRepositoryTest {
@Autowired
CommentRepository commentRepository;
}
@DataJpaTestJPA와 연동해서 테스트하겠다는 선언.@Autowired @Test
@DisplayName("특정 게시글의 모든 댓글 조회")
void findByArticleId() {
}
@DisplayName()()안에는 문자열 형식으로 ""를 사용해서 작성.테스트 케이스를 여러 개작성할 때는 테스트마다 중괄호({})로 묶어서하면 됨.@Test
@DisplayName("특정 게시글의 모든 댓글 조회")
void findByArticleId() {
{
Long articleId = 4L;
List<Comment> comments = commentRepository.findByArticleId(articleId);
Article article = new Article(4L, "연말에 뭐하세요?", "댓글로 알려주세요");
Comment a = new Comment(1L, article, "Park", "집돌이");
// 코드 생략.
List<Comment> expected = Arrays.asList(a, b, c);
assertEquals(expected.toString(), comments.toString(), "4번 글의 모든 댓글 출력.");
}
// case 2 : 2번 게시글.
{
Long articleId = 1L;
// 코드 생략.
assertEquals(expected.toString(), comments.toString(), "1번 글에는 댓글 없음.");
}
}
assertEquals(expected.toString(), comments.toString(), "4번 글의 모든 댓글 출력.");예상 데이터의 문자열과 실제 데이터의 문자열이 같은 지 비교함.List<Comment> expected = Arrays.asList(a, b, c);@Test
@DisplayName("특정 닉네임의 모든 댓글 조회")
void findByNickname() {
// case 1 : "Park"의 모든 댓글 조회.
{
String nickname = "Park";
List<Comment> comments = commentRepository.findByNickname(nickname);
Comment a = new Comment(1L, new Article(4L, "연말에 뭐하세요?", "댓글로 알려주세요"), nickname, "집돌이");
// 코드 생략.
List<Comment> expected = Arrays.asList(a, b, c);
assertEquals(expected.toString(), comments.toString());
}
}
Park가 작성한 댓글 데이터를 Comment a, b, c 객체에 저장.부모(article)게시글이 전부 다름.하나의 article 객체만을 이용해서 참조할 수는 없으므로 a, b, c 객체 생성시 article 필드에 각각 객체를 생성함.package org.springframework.data.jpa.repository;
import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.ListCrudRepository;
import org.springframework.data.repository.ListPagingAndSortingRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.query.QueryByExampleExecutor;
@NoRepositoryBean
public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
void flush();
<S extends T> S saveAndFlush(S entity);
<S extends T> List<S> saveAllAndFlush(Iterable<S> entities);
/** @deprecated */
@Deprecated
default void deleteInBatch(Iterable<T> entities) {
this.deleteAllInBatch(entities);
}
void deleteAllInBatch(Iterable<T> entities);
void deleteAllByIdInBatch(Iterable<ID> ids);
void deleteAllInBatch();
/** @deprecated */
@Deprecated
T getOne(ID id);
/** @deprecated */
@Deprecated
T getById(ID id);
T getReferenceById(ID id);
<S extends T> List<S> findAll(Example<S> example);
<S extends T> List<S> findAll(Example<S> example, Sort sort);
}