본문 바로가기
Spring Boot Project/Plming

[Plming] 게시판에 JPA 적용하기

by slchoi 2022. 3. 29.
728x90
SMALL

git을 잘못 사용해서 기존에 했던 작업물들이 모두 사라졌다..

github에 올려두었던 코드들을 다시 clone해올까도 했지만, 어차피 폴더 구조도 변경해야 하고, JPA와 REST API를 적용하도록 코드를 변경해야 해서 그냥 처음부터 다시 만들기로 결정했다.

그래서 다시 프로젝트를 생성하러 Spring Initializr에 들어갔다..

  • Project: Gradle Project
  • Language: Java
  • Spring Boot: 2.6.5
  • Project Metadata
    • Group: board
    • Artifact: plming
    • Name: plming
    • package name: plming
    • Packaging: Jar
  • Dependencies
    • Spring Boot DevTools
    • Lombok
    • Spring Configuration Processor
    • Spring Data JPA
    • MyBatis Framework
    • MySQL Driver
    • Thymeleaf
    • Spring Web

나머지 프로젝트 설정과 데이터베이스 연동과 관련된 설정은 이 포스팅을 참고하면 된다,

1. DB 설정

"main/resource"의 application.properties에 아래 코드를 추가한다. username과 password를 각자 설정에 맞게 수정한다.

# 데이터 소스 (Data Source)
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.jdbc-url=jdbc:mysql://localhost:3306/board?serverTimezone=Asia/Seoul&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.hikari.username=root
spring.datasource.hikari.password=1111

# Resource and Thymeleaf Refresh
spring.devtools.livereload.enabled=true
spring.thymeleaf.cache=false

# JPA Properties
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.open-in-view=false
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

그다음으로 데이터베이스 관련 Configuration 클래스를 생성한다. "main.java.plming" 패키지 밑에 "config" 패키지를 생성하고, DatabaseConfig 클래스를 추가한다. 클래스 생성이 완료되면 클래스에 아래 코드를 추가한다.

package plming.config;


import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;

@Configuration
@PropertySource("classpath:/application.properties")
public class DatabaseConfig {
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }
    
    @Bean
    public DataSource dataSource() {
        return new HikariDataSource(hikariConfig());
    }
}

 

2. Entity 클래스 생성하기

plming 패키지 아래 board 패키지를 생성하고 board 패키지 아래 entity 패키지를 추가한 다음 Board 클래스를 추가한다.

Board 클래스 생성이 완료되면 더보기 코드를 작성한다.

더보기
package plming.board.entity;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.time.LocalDateTime;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;    // PK

    @Column(columnDefinition = "varchar")
    private String user;    // 사용자

    @Column(columnDefinition = "enum")
    private String category;    // 카테고리

    @Column(columnDefinition = "enum")
    private String status;  // 모집 상태

    @Column(columnDefinition = "varchar")
    private String period;  // 진행 기간

    @Column(columnDefinition = "varchar")
    private String title;   // 제목

    @Column(columnDefinition = "varchar")
    private String content; // 내용

    @Column(columnDefinition = "Integer")
    private Integer participantNum = 0;

    @Column(columnDefinition = "bigint")
    private Long viewCnt = 0L;

    @Column(columnDefinition = "datetime")
    private LocalDateTime createDate = LocalDateTime.now();

    @Column(columnDefinition = "datetime")
    private LocalDateTime updateDate;

    @Column(columnDefinition = "enum")
    private char deleteYn = 'N';

    @Builder
    public Board(String user, String category, String status, String period, String title, String content) {
        this.user = user;
        this.category = category;
        this.status = status;
        this.period = period;
        this.title = title;
        this.content = content;
    }
}

 

애노테이션 설명
@Getter 해당 클래스에 포함된 멤버 변수의 모든 getter 메서드를 생성해주는 롬복
@NoArgsConstructor
(access = AccessLevel.PROTECTED)
해당 클래스의 기본 생성자를 생성해 주는 어노테이션이다.

access 속성을 이용해 동일한 패키지 내의 클래스에서만 객체를 생성할 수 있도록 제어한다.
@Entity 해당 클래스가 테이블과 매핑되는 JPA의 Entity 클래스임을 의미한다.

기본적으로 클래스명(Camel Case)을 테이블명(Snake Case)으로 매핑한다.

혹시라도 클래스명과 테이블명이 다를 수 밖에 없는 상황에서는 클래스 레벨에 @Table을 선언하고, @Table(name = "user")과 같이 name 속성을 이용해서 처리하면 된다.
@Id 해당 멤버가 Entity의 PK임을 읨미한다.

일반적으로 MySQL DB는 PK를 bigint 타입으로, Entity에서는 Long 타입으로 선언한다고 한다.
@GeneratedValue
(strategy = GenerationType.IDENTITY)
PK 생성 전략을 설정하는 어노테이션이다.

MySQL은 자동 증가(AUTO_INCREMENT)를 지원하는 DB이며, PK 자동 증가를 지원하는 DB는 해당 어노테이션을 선언해야 한다.

오라클과 같이 Sequence를 이용하는 DB는 GenerataionType.SEQUENCE를 이용해야 한다.

GenerationType.AUTO로 설정하게 되면 DB에서 제공하는 PK 생성 전략을 가져가게 된다.
@Builder 롬복에서 제공해주는 기능으로 생성자 대신 이용하는 패턴이다.
@Setter가 없음 Entity 클래스는 테이블 그 자체이미로, 각각의 멤버 변수는 해당 테이블의 컬럼이라는 의미가 되고, 컬럼에 대한 setter를 무작정 생성하는 경우, 객체의 값이 어느 시점에 변경되었는지 알 수 없기 때문에, Entity클래스에는 절대로 Set 메서드가 존재해서는 안된다.

 

3. JPA Repository 인터페이스 생성하기

"board.entity" 패키지에 BoardRepository 인터페이스를 추가한 뒤 JpaRepository 인터페이스를 상속받는다.

package plming.board.entity;

import org.springframework.data.jpa.repository.JpaRepository;

public interface BoardRepository extends JpaRepository<Board, Long> {

}

Repository는 MyBatis의 SQL Mapper와 유사한 퍼시스턴스 영역에 사용되는 인터페이스이다. Entity 클래스와 Repository 인터페이스는 반드시 같은 패키지에 위치해야 한다.

  • extends JpaRepository<Board, Long>
    • Repository 인터페이스에서 JpaRepository 인터페이스를 상속받을 때 Entity 클래스의 타입(Board)과 PK에 해당하는 데이터 타입(Long)을 선언하면 해당 Entity 클래스와 매핑되는 테이블인 board 테이블의 CRUD 기능을 사용할 수 있다.

 

4. CRUD 테스트하기

JUnit을 이용해서 테스트해볼 것이다.

"test.java" 디렉터리의 plming 패키지 아래 board 패키지와 BoardTest 클래스를 추가하고, 아래 더보기 코드를 작성한다.

더보기
package plming.board;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import plming.board.entity.Board;
import plming.board.entity.BoardRepository;

import java.util.List;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
public class BoardTest {

    @Autowired
    BoardRepository boardRepository;

    @Test
    void save() {
        // given
        Board post = Board.builder()
                .title("1번 게시글 제목")
                .category("스터디")
                .content("CRUD 테스트 입니다.")
                .period("하루")
                .status("모집 중")
                .user("사용자1")
                .build();

        // when
        boardRepository.save(post);

        // then
        Board postCp = boardRepository.findById(post.getId()).get();
        assertThat(postCp.getTitle()).isEqualTo(post.getTitle());
        assertThat(postCp.getContent()).isEqualTo(post.getContent());
        assertThat(postCp.getUser()).isEqualTo(post.getUser());
    }

    @Test
    void findAll() {
        // when
        List<Board> boardList = boardRepository.findAll();

        // then
        assertEquals(boardRepository.count(), boardList.size());
    }

    @Test
    void delete() {
        // given
        Board post = boardRepository.findById(1L).get();

        // when
        boardRepository.delete(post);

        // then
        assertEquals(0, boardRepository.count());
    }
}

 

테스트 메서드를 하나씩 돌려보면 모두 성공하는 것을 확인할 수 있다.

savd 메서드 테스트 결과
findAll 메서드 테스트 결과
delete 메서드 테스트 결과

코드를 살펴보면

@SpringBootTest 스프링 부트는 해당 어노테이션만 선언하면 테스팅이 가능하다.
boardRepository 스프링 컨테이너에 등록된 BoardRepository 객체(Bean)를 주입받는다.
save() 게시글 저장에 이용되는 post는 Builder 패턴을 통해 생성된 객체이다.

생성자와는 달리, Builder 패턴을 이용하면 어떤 멤버에 어떤 값을 세팅하는지 직관적으로 확인이 가능하다.

생성자의 경우 객체를 생성할 때 인자의 순서에 영향을 받지만, Builder 패턴은 인자의 순서에 관계없이 객체를 생성할 수 있다.
findAll() boardRepository의 count()와 findAll() 메서드를 이용해서 전체 게시글 수와 전체 게시글 리스트를 조회하는 쿼리를 실행한다.
delete() boardRepository의 findById() 메서드를 이용해서 해당 Entity를 조회한다.

findById()는 JPA에서 기본으로 제공해주는 메서드로, Entity의 PK를 기준으로 데이터를 조회한 다음 delete() 메서드를 실행해 게시글을 삭제한다.

findById()의 리턴 타입은 Optional<T>라는 클래스이다. Optional은 반복적인 NULL 처리를 피하기 위해 자바 8에서 최초로 도입된 클래스이다.

 

본 프로젝트는 아래 블로그를 참고해서 만들었습니다.
https://congsong.tistory.com/51?category=749196
728x90
LIST

댓글