공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.
JPA 엔티티 설정
Article 클래스는 게시글을 나타내는 JPA 엔티티로, 게시글의 제목, 본문, 해시태그와 함께 생성일시, 생성자, 수정일시, 수정자 등의 메타데이터를 포함한다. 이 클래스는 데이터베이스의 article 테이블과 매핑되며, JPA Auditing을 통해 생성 및 수정 정보를 자동으로 관리한다.
package org.example.projectboard.domain;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import java.time.LocalDateTime;
import java.util.Objects;
@Getter
@ToString
@Table(indexes = {
@Index(columnList = "title"),
@Index(columnList = "hashtag"),
@Index(columnList = "createdAt"),
@Index(columnList = "createdBy"),
})
@EntityListeners(AuditingEntityListener.class)
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter @Column(nullable = false) private String title; // 제목
@Setter @Column(nullable = false, length = 10000) private String content; // 본문
@Setter private String hashtag; // 해시태그
@CreatedDate @Column(nullable = false) private LocalDateTime createdAt; // 생성일시
@CreatedBy @Column(nullable = false, length = 100) private String createdBy; // 생성자
@LastModifiedDate @Column(nullable = false) private LocalDateTime modifiedAt; // 수정일시
@LastModifiedBy @Column(nullable = false, length = 100) private String modifiedBy; // 수정자
protected Article(){}
private Article(String title, String content, String hashtag) {
this.title = title;
this.content = content;
this.hashtag = hashtag;
}
public static Article of(String title, String content, String hashtag) {
return new Article(title, content, hashtag);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Article article)) return false;
return id != null && id.equals(article.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
애노테이션
- @Getter: Lombok 애노테이션으로 모든 필드에 대한 getter 메서드를 자동으로 생성한다.
- @ToString: Lombok 애노테이션으로 모든 필드를 포함한 toString 메서드를 자동으로 생성한다.
- @Table(indexes = {...}): 이 엔티티의 테이블에 인덱스를 설정한다.
- @Index(columnList = "title"): title 컬럼에 인덱스를 설정한다.
- @Index(columnList = "hashtag"): hashtag 컬럼에 인덱스를 설정한다.
- @Index(columnList = "createdAt"): createdAt 컬럼에 인덱스를 설정한다.
- @Index(columnList = "createdBy"): createdBy 컬럼에 인덱스를 설정한다.
- @EntityListeners(AuditingEntityListener.class) : 엔티티의 생성 및 수정 시점에 자동으로 auditing 정보를 업데이트할 수 있다.
- @Entity: 이 클래스가 JPA 엔티티임을 나타낸다.
필드
- @Id: 이 필드가 엔티티의 고유 식별자임을 나타낸다.
- @GeneratedValue(strategy = GenerationType.IDENTITY): 기본 키 생성을 데이터베이스에 위임한다.
- @Setter: Lombok 애노테이션으로 해당 필드에 대한 setter 메서드를 자동으로 생성한다.
- @Column: JPA 애노테이션으로 데이터베이스 컬럼과 매핑되며, 속성 값을 설정한다.
- nullable = false: 해당 컬럼이 null 값을 허용하지 않음을 나타낸다.
- length: 해당 컬럼의 최대 길이를 설정한다.
생성자
- protected Article()
- JPA 스펙에 따라 엔티티 클래스에는 기본 생성자가 필요하며, protected로 설정하여 외부에서 직접 접근하지 못하도록 한다.
- private Article(String title, String content, String hashtag)
- 제목, 본문, 해시태그를 인자로 받는 생성자로, 객체 생성을 of 메서드를 통해서만 가능하게 한다.
정적 팩토리 메서드:
- public static Article of(String title, String content, String hashtag)
- 제목, 본문, 해시태그를 인자로 받아 새로운 Article 객체를 생성하고 반환하는 정적 팩토리 메서드.
equals 및 hashCode 메서드:
- @Override public boolean equals(Object o)
- 두 객체가 같은지 비교하는 메서드로, id 필드가 같으면 동일한 객체로 간주한다. 여기서 id가 null이 아니어야 한다.
- @Override public int hashCode()
- 객체의 해시 코드를 반환하는 메서드로, id 필드를 기준으로 해시 코드를 생성한다.
JPA Auditing 필드
- @CreatedDate: 엔티티가 생성될 때 자동으로 설정되는 생성일시.
- @CreatedBy: 엔티티가 생성될 때 자동으로 설정되는 생성자.
- @LastModifiedDate: 엔티티가 수정될 때 자동으로 설정되는 수정일시.
- @LastModifiedBy: 엔티티가 수정될 때 자동으로 설정되는 수정자.
이 필드들은 @Column(nullable = false)로 설정되어 있어, null 값을 허용하지 않으며 데이터베이스에 저장될 때 반드시 값이 설정되어야 한다.
Setter를 클래스 단위로 걸지 않은 이유는 사용자가 특정 필드에 접근하지 못하게 막고 싶어서이다. 예를 들어, id는 JPA 영속성 컨텍스트가 자동으로 부여해 주는 번호이다. 그리고 밑의 메타 데이터도 자동으로 세팅되게 만들 것이다. 그래서 Setter를 열어두면 임의로 변경될 수 있는데, 이는 설계 의도에 벗어난다.
또한 생성자는 최초 insert할 때 누가 만들었는지를 알아야 하는데, 지금 인증 기능을 달아 놓지 않았다. 그러니까 누가 만들었는지에 대한 정보는 어디에도 없다. 따라서 이것을 세팅하기 위해 JPA에서 AuditorAware 기능을 추가한다.
그리고 메타 데이터를 자동으로 세팅하게끔 만들어주는데 이때 사용하는 기술이 JPA의 auditing이다.
이를 위해 별도의 설정 클래스를 생성한다.
package org.example.projectboard.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import java.util.Optional;
@EnableJpaAuditing
@Configuration
public class JpaConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of("eunchan"); // TODO: 스프링 시큐리티로 인증 기능을 붙이게 될 때 수정할 예정이다.
}
}
@EnableJpaAuditing:
- JPA의 Auditing 기능을 활성화한다. 이를 통해 엔티티의 생성자와 수정자 정보를 자동으로 관리할 수 있다.
@Configuration:
- 스프링 컨텍스트에서 이 클래스가 설정 클래스임을 나타낸다. 즉, 이 클래스는 스프링의 설정 정보와 빈 정의를 포함하고 있다.
@Bean:
- auditorProvider 메서드를 스프링 빈으로 등록한다. 이 빈은 AuditorAware 인터페이스를 구현하여 현재의 감사 정보를 제공한다.
AuditorAware 구현:
- auditorProvider 메서드는 AuditorAware<String>를 구현하여 현재의 생성자 정보를 제공한다.
- 여기서는 Optional.of("eunchan")을 반환하여 모든 엔티티에 대해 "eunchan"이라는 사용자명을 생성자 정보로 설정한다.
- 실제 애플리케이션에서는 스프링 시큐리티와 통합하여 현재 인증된 사용자의 정보를 반환하도록 수정할 예정이다. 이를 위해 TODO 주석을 추가하였다.
Auditing을 사용하는 이유
JPA Auditing 기능을 사용하면 엔티티가 생성되거나 수정될 때, 자동으로 특정 필드 값을 설정할 수 있다. 예를 들어, createdAt, createdBy, modifiedAt, modifiedBy 같은 필드는 엔티티가 처음 생성되거나 수정될 때 자동으로 값을 설정해 주므로, 코드의 일관성을 유지하고 수작업으로 설정하는 번거로움을 줄일 수 있다.
이를 위해 @EnableJpaAuditing 애노테이션을 사용하여 Auditing 기능을 활성화하고, 엔티티 클래스에 필요한 애노테이션을 추가하여 Auditing 기능을 사용할 수 있다.
이렇게 설정하면, Article 엔티티 클래스에서 createdAt, createdBy, modifiedAt, modifiedBy 같은 메타 데이터 필드가 자동으로 설정된다. 이를 통해 개발자는 비즈니스 로직에만 집중할 수 있으며, 데이터의 일관성과 무결성을 유지할 수 있다.
ArticleComment 클래스
ArticleComment 클래스는 게시글에 달리는 댓글을 나타내는 JPA 엔티티로, 댓글의 본문과 함께 생성일시, 생성자, 수정일시, 수정자 등의 메타데이터를 포함한다. 이 클래스는 데이터베이스의 article_comment 테이블과 매핑되며, JPA Auditing을 통해 생성 및 수정 정보를 자동으로 관리한다.
package org.example.projectboard.domain;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import java.time.LocalDateTime;
import java.util.Objects;
@Getter
@ToString
@Table(indexes = {
@Index(columnList = "content"),
@Index(columnList = "createdAt"),
@Index(columnList = "createdBy"),
})
@EntityListeners(AuditingEntityListener.class)
@Entity
public class ArticleComment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter @ManyToOne(optional = false) private Article article; // 게시글 (ID)
@Setter @Column(nullable = false, length = 500) private String content; // 본문
@CreatedDate @Column(nullable = false) private LocalDateTime createdAt; // 생성일시
@CreatedBy @Column(nullable = false, length = 100) private String createdBy; // 생성자
@LastModifiedDate @Column(nullable = false) private LocalDateTime modifiedAt; // 수정일시
@LastModifiedBy @Column(nullable = false, length = 100) private String modifiedBy; // 수정자
public ArticleComment() {}
public ArticleComment(Article article, String content) {
this.article = article;
this.content = content;
}
public static ArticleComment of(Article article, String content) {
return new ArticleComment(article, content);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ArticleComment that)) return false;
return id != null && id.equals(that.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
애노테이션:
- @Getter: Lombok 애노테이션으로 모든 필드에 대한 getter 메서드를 자동으로 생성한다.
- @ToString: Lombok 애노테이션으로 모든 필드를 포함한 toString 메서드를 자동으로 생성한다.
- @Table(indexes = {...}): 이 엔티티의 테이블에 인덱스를 설정한다.
- @Index(columnList = "content"): content 컬럼에 인덱스를 설정한다.
- @Index(columnList = "createdAt"): createdAt 컬럼에 인덱스를 설정한다.
- @Index(columnList = "createdBy"): createdBy 컬럼에 인덱스를 설정한다.
- @EntityListeners(AuditingEntityListener.class) : 엔티티의 생성 및 수정 시점에 자동으로 auditing 정보를 업데이트할 수 있다.
- @Entity: 이 클래스가 JPA 엔티티임을 나타낸다.
필드:
- @Id: 이 필드가 엔티티의 고유 식별자임을 나타낸다.
- @GeneratedValue(strategy = GenerationType.IDENTITY): 기본 키 생성을 데이터베이스에 위임한다.
- @Setter: Lombok 애노테이션으로 해당 필드에 대한 setter 메서드를 자동으로 생성한다.
- @ManyToOne(optional = false): 이 필드가 Article 엔티티와 다대일 관계임을 나타내며, 필수 필드로 설정한다.
- @Column: JPA 애노테이션으로 데이터베이스 컬럼과 매핑되며, 속성 값을 설정한다.
- nullable = false: 해당 컬럼이 null 값을 허용하지 않음을 나타낸다.
- length: 해당 컬럼의 최대 길이를 설정한다.
생성자:
- protected ArticleComment() {}: JPA 스펙에 따라 엔티티 클래스에는 기본 생성자가 필요하며, protected로 설정하여 외부에서 직접 접근하지 못하도록 한다.
- private ArticleComment(Article article, String content): 게시글과 본문을 인자로 받는 생성자로, 객체 생성을 of 메서드를 통해서만 가능하게 한다.
정적 팩토리 메서드:
- public static ArticleComment of(Article article, String content): 게시글과 본문을 인자로 받아 새로운 ArticleComment 객체를 생성하고 반환하는 정적 팩토리 메서드.
equals 및 hashCode 메서드:
- @Override public boolean equals(Object o): 두 객체가 같은지 비교하는 메서드로, id 필드가 같으면 동일한 객체로 간주한다. 여기서 id가 null이 아니어야 한다.
- @Override public int hashCode(): 객체의 해시 코드를 반환하는 메서드로, id 필드를 기준으로 해시 코드를 생성한다.
이제 Article 클래스와 ArticleComment 클래스 간의 양방향 연관관계를 설정한다.
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//양방향
@ToString.Exclude
@OrderBy("id")
@OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
private final Set<ArticleComment> articleComments = new LinkedHashSet<>();
...
}
@Entity
public class ArticleComment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//양방향
@Setter @ManyToOne(optional = false) private Article article; // 게시글 (ID)
...
}
양방향 연관관계 설정:
- Article 클래스는 @OneToMany 애노테이션을 사용하여 여러 ArticleComment 객체와 연관된다.
- ArticleComment 클래스는 @ManyToOne 애노테이션을 사용하여 Article 객체와 연관된다.
무한루프 방지:
- @ToString.Exclude: Lombok의 @ToString 애노테이션을 사용할 때, 연관된 엔티티를 제외하여 무한루프를 방지한다.
CascadeType 설정:
- cascade = CascadeType.ALL: Article 객체가 저장, 삭제될 때 연관된 ArticleComment 객체들도 함께 저장, 삭제된다.
정렬:
- @OrderBy("id"): ArticleComment 객체들이 id 순서대로 정렬된다.
이 설정을 통해 Article 클래스와 ArticleComment 클래스 간의 양방향 연관관계가 설정된다. 이를 통해 양방향으로 데이터에 접근할 수 있으며, Lombok의 @ToString.Exclude 애노테이션을 사용하여 무한루프를 방지할 수 있다.
다음으로 ArticleRepository를 생성한다.
게시판 프로젝트에서 데이터베이스와 상호작용하기 위해 Spring Data JPA를 사용한다. 이를 위해 엔티티 클래스들을 정의한 후, 해당 엔티티들을 처리하는 리포지토리 인터페이스를 작성한다.
ArticleRepository
ArticleRepository는 Article 엔티티에 대한 CRUD 연산을 수행하는 인터페이스다.
JpaRepository<Article, Long>를 상속받아 여러 유용한 메서드를 사용할 수 있다.
package org.example.projectboard.repository;
import org.example.projectboard.domain.Article;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ArticleRepository extends JpaRepository<Article, Long> {
}
- JpaRepository를 상속받으면 findById, findAll, save, delete와 같은 기본적인 데이터베이스 연산 메서드를 자동으로 사용할 수 있다.
- 이로 인해 반복적인 코드를 줄이고 간결하게 데이터 접근을 관리할 수 있다.
다음은 JPA 기능을 테스트하기 위한 테스트 코드이다.
package org.example.projectboard.repository;
import org.example.projectboard.config.JpaConfig;
import org.example.projectboard.domain.Article;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
@DisplayName("JPA 연결 테스트")
@Import(JpaConfig.class)
@DataJpaTest
class JpaRepositoryTest {
private final ArticleRepository articleRepository;
private final ArticleCommentRepository articleCommentRepository;
public JpaRepositoryTest(
@Autowired ArticleRepository articleRepository,
@Autowired ArticleCommentRepository articleCommentRepository
) {
this.articleRepository = articleRepository;
this.articleCommentRepository = articleCommentRepository;
}
@DisplayName("select 테스트")
@Test
void givenTestData_whenSelecting_thenWorksFine() {
// given
// when
List<Article> articles = articleRepository.findAll();
// then
assertThat(articles)
.isNotNull()
.hasSize(123);
}
}
- JpaRepositoryTest 클래스는 Article과 ArticleComment 엔티티의 JPA 기능을 테스트한다.
- @DataJpaTest 애노테이션을 사용하여 JPA 관련 테스트 설정을 자동으로 구성한다.
- @Import(JpaConfig.class) 애노테이션을 사용하여 JPA 구성 클래스 JpaConfig를 불러온다.
- givenTestData_whenSelecting_thenWorksFine 메서드는 ArticleRepository를 사용하여 모든 Article을 조회한 후, 조회된 데이터가 예상한 크기와 일치하는지 검증한다.
- assertThat을 사용하여 데이터가 예상대로 조회되는지 확인한다.
테스트 데이터 대량 생성을 위해 mockaroo 사이트를 이용한다.
아래와 같은 설정으로 데이터를 생성한다.
생성된 데이터를 data.sql 파일에 붙여 넣어 테스트 데이터로 사용한다.
-- 123개의 게시글
insert into article (title, content, hashtag, created_by, modified_by, created_at, modified_at) values ('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.', 'Aenean fermentum. Donec ut mauris eget massa tempor convallis. Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh.', 'Aquamarine', 'Odette', 'Angel', '2024-06-29 02:15:00', '2024-06-28 11:51:06');
insert into article (title, content, hashtag, created_by, modified_by, created_at, modified_at) values ('Fusce consequat.', 'Vestibulum quam sapien, varius ut, blandit non, interdum in, ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Duis faucibus accumsan odio. Curabitur convallis.', null, 'Cymbre', 'Paulo', '2023-11-23 11:52:49', '2024-06-21 14:17:24');
insert into article (title, content, hashtag, created_by, modified_by, created_at, modified_at) values ('In hac habitasse platea dictumst.', 'Donec diam neque, vestibulum eget, vulputate ut, ultrices vel, augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec pharetra, magna vestibulum aliquet ultrices, erat tortor sollicitudin mi, sit amet lobortis sapien sapien non mi. Integer ac neque.', 'Crimson', 'Aleen', 'Aldo', '2024-06-25 12:41:25', '2024-05-28 06:33:41');
insert into article (title, content, hashtag, created_by, modified_by, created_at, modified_at) values ('Cras pellentesque volutpat dui.', 'Aenean fermentum. Donec ut mauris eget massa tempor convallis. Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh.', 'Indigo', 'Ivor', 'Ursula', '2024-05-02 02:37:23', '2023-09-20 21:53:30');
insert into article (title, content, hashtag, created_by, modified_by, created_at, modified_at) values ('Morbi non quam nec dui luctus rutrum.', 'Quisque id justo sit amet sapien dignissim vestibulum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla dapibus dolor vel est. Donec odio justo, sollicitudin ut, suscipit a, feugiat et, eros.', 'Puce', 'Shelby', 'Charlena', '2024-07-26 07:00:50', '2024-02-07 21:34:58');
insert into article (title, content, hashtag, created_by, modified_by, created_at, modified_at) values ('Morbi ut odio.', 'In sagittis dui vel nisl. Duis ac nibh. Fusce lacus purus, aliquet at, feugiat non, pretium quis, lectus.', 'Khaki', 'Nero', 'Aliza', '2024-04-26 01:55:16', '2024-07-16 15:29:12');
insert into article (title, content, hashtag, created_by, modified_by, created_at, modified_at) values ('Sed sagittis.', 'Sed ante. Vivamus tortor. Duis mattis egestas metus.', 'Goldenrod', 'Norah', 'Danyette', '2023-11-19 23:33:16', '2023-11-23 22:19:00');
insert into article (title, content, hashtag, created_by, modified_by, created_at, modified_at) values ('Fusce consequat.', 'Duis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus.', 'Red', 'Cyndia', 'Leland', '2024-07-09 09:29:37', '2023-11-18 11:03:48');
insert into article (title, content, hashtag, created_by, modified_by, created_at, modified_at) values ('In hac habitasse platea dictumst.', 'Duis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus.', 'Blue', 'Beth', 'Forrest', '2024-01-16 06:07:03', '2024-03-03 16:22:20');
insert into article (title, content, hashtag, created_by, modified_by, created_at, modified_at) values ('Nulla tempus.', 'Donec diam neque, vestibulum eget, vulputate ut, ultrices vel, augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec pharetra, magna vestibulum aliquet ultrices, erat tortor sollicitudin mi, sit amet lobortis sapien sapien non mi. Integer ac neque.', 'Blue', 'Kurtis', 'Maryann', '2024-03-25 15:18:48', '2024-04-28 04:44:37');
insert into article (title, content, hashtag, created_by, modified_by, created_at, modified_at) values ('Nullam porttitor lacus at turpis.', 'Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus vestibulum sagittis sapien. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.', 'Green', 'Mona', 'Edita', '2024-01-29 20:17:46', '2024-03-02 15:13:18');
.
.
.
-- 1000개의 댓글
insert into article_comment (article_id, content, created_by, modified_by, created_at, modified_at) values (1, 'Proin interdum mauris non ligula pellentesque ultrices. Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.', 'Wolfgang', 'Teressa', '2023-08-28 01:46:41', '2024-06-03 19:21:42');
insert into article_comment (article_id, content, created_by, modified_by, created_at, modified_at) values (2, 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin risus. Praesent lectus.', 'Nickolaus', 'Lisetta', '2023-10-17 02:48:32', '2024-07-06 20:18:14');
insert into article_comment (article_id, content, created_by, modified_by, created_at, modified_at) values (3, 'Cras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque.', 'Gabe', 'Olivette', '2024-02-21 01:12:52', '2024-01-03 03:12:20');
insert into article_comment (article_id, content, created_by, modified_by, created_at, modified_at) values (4, 'Suspendisse potenti. In eleifend quam a odio. In hac habitasse platea dictumst.', 'Darleen', 'Osmund', '2023-09-03 12:14:37', '2024-07-12 17:05:45');
insert into article_comment (article_id, content, created_by, modified_by, created_at, modified_at) values (5, 'Integer ac leo. Pellentesque ultrices mattis odio. Donec vitae nisi.', 'Cathi', 'Roslyn', '2023-08-15 05:00:03', '2024-03-16 20:41:53');
insert into article_comment (article_id, content, created_by, modified_by, created_at, modified_at) values (6, 'In hac habitasse platea dictumst. Etiam faucibus cursus urna. Ut tellus.', 'Thatch', 'Hyacinthie', '2024-03-15 15:47:17', '2023-12-21 14:24:43');
.
.
.
select 테스트가 통과했다. 이제 이런 포맷으로 계속해서 crud 테스트를 완성할 것이다.
package org.example.projectboard.repository;
import org.example.projectboard.config.JpaConfig;
import org.example.projectboard.domain.Article;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
@DisplayName("JPA 연결 테스트")
@Import(JpaConfig.class)
@DataJpaTest
class JpaRepositoryTest {
private final ArticleRepository articleRepository;
private final ArticleCommentRepository articleCommentRepository;
public JpaRepositoryTest(
@Autowired ArticleRepository articleRepository,
@Autowired ArticleCommentRepository articleCommentRepository
) {
this.articleRepository = articleRepository;
this.articleCommentRepository = articleCommentRepository;
}
@DisplayName("select 테스트")
@Test
void givenTestData_whenSelecting_thenWorksFine() {
// given
// when
List<Article> articles = articleRepository.findAll();
// then
assertThat(articles)
.isNotNull()
.hasSize(123);
}
@DisplayName("insert 테스트")
@Test
void givenTestData_whenInserting_thenWorksFine() {
// given
long previousCount = articleRepository.count();
// when
Article savedArticle = articleRepository.save(Article.of("new article", "new content", "#spring"));
// then
assertThat(articleRepository.count()).isEqualTo(previousCount + 1);
}
@DisplayName("update 테스트")
@Test
void givenTestData_whenUpdating_thenWorksFine() {
// given
Article article = articleRepository.findById(1L).orElseThrow();
String updatedHashtag = "#springboot";
article.setHashtag(updatedHashtag);
// when
Article savedArticle = articleRepository.saveAndFlush(article);
articleRepository.flush();
// then
assertThat(savedArticle).hasFieldOrPropertyWithValue("hashtag", updatedHashtag);
}
@DisplayName("delete 테스트")
@Test
void givenTestData_whenDeleting_thenWorksFine() {
// given
Article article = articleRepository.findById(1L).orElseThrow();
long previousArticleCount = articleRepository.count();
long previousArticleCommentCount = articleCommentRepository.count();
int deletedCommentSize = article.getArticleComments().size();
// when
articleRepository.delete(article);
// then
assertThat(articleRepository.count()).isEqualTo(previousArticleCount - 1);
assertThat(articleCommentRepository.count()).isEqualTo(previousArticleCommentCount - deletedCommentSize);
}
}
select 테스트:
- 데이터베이스에서 모든 Article을 조회한다.
- 조회된 Article 리스트가 null이 아니고, 크기가 123인지 확인한다.
insert 테스트:
- 기존 Article 수를 카운트한다.
- 새로운 Article을 저장하고, 저장 후의 Article 수가 기존 수보다 1 증가했는지 확인한다.
update 테스트:
- ID가 1인 Article을 조회한다.
- 해당 Article의 해시태그를 #springboot으로 업데이트하고 저장한다.
- 저장된 Article의 해시태그가 #springboot인지 확인한다.
delete 테스트:
- ID가 1인 Article을 조회한다.
- 삭제 전 Article과 ArticleComment의 수를 카운트한다.
- 해당 Article을 삭제하고, 삭제 후 Article 수가 1 감소했는지, 관련 ArticleComment 수가 정확히 감소했는지 확인한다.
CRUD 모두 정상적으로 통과된 것을 확인할 수 있다.
이번 테스트가 끝났다.
깃크라켄에서 푸쉬를 하고 깃허브에서 확인하고 프로젝트 카드 이슈도 닫아준다.
이 과정을 통해 데이터베이스 접근 로직 테스트를 성공적으로 마무리했다. 이제 데이터베이스 연동이 잘 이루어졌고, CRUD 테스트가 성공적으로 수행됨을 확인했다. 앞으로도 이런 방식으로 테스트를 계속 진행하며 프로젝트를 발전시킬 계획이다.
'BackEnd > Project' 카테고리의 다른 글
[Board] Ch02. API 테스트 정의 (0) | 2024.08.07 |
---|---|
[Board] Ch02. 데이터베이스 접근 로직 구현 (0) | 2024.08.07 |
[Board] Ch02. 데이터베이스 접근 로직 테스트 정의(1) (0) | 2024.08.06 |
[Board] Ch02. 도메인 설계 (0) | 2024.08.06 |
[Board] Ch02. 스프링 부트 프로젝트 (0) | 2024.08.06 |