▼ Backend/└ 게시판 만들기

Spring Boot (게시판) - 2 | 데이터베이스(MariaDB) 연동 및 JPA CRUD

Valar 2021. 10. 28. 10:29
반응형

 

[스프링 부트 (Spring Boot)/게시판 만들기] - 1 | 스프링 부트 프로젝트 만들기

[데이터베이스 (DATA BASE)/MariaDB] - MariaDB | 윈도우 MariaDB 설치 및 접속하기

 

구성환경

SpringBoot, Gradle, Thymeleaf, Jpa(JPQL), Jar, MariaDB

 

지난번 스프링 부트 프로젝트를 생성하였고, 이번 포스팅에서는 DBMS중 하나인 MariaDB에 스키마 생성 및 연동하는 부분을 진행한다.

 

MariaDB 설치가 안되어있는 경우 링크를 통해 설치 후 진행한다.

 

프로젝트내 static/database/script.sql이 있으니 테이블 생성 시 참고한다.
script.sql 다운로드

 

 

데이터베이스 생성하기

 

CREATE DATABASE IF NOT EXISTS `board_study`

 

 

테이블 생성하기

 

CREATE TABLE IF NOT EXISTS `board` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'PK',
  `title` varchar(200) NOT NULL COMMENT '제목',
  `content` text NOT NULL COMMENT '내용',
  `read_cnt` int(11) NOT NULL DEFAULT 0 COMMENT '조회수',
  `register_id` VARCHAR(100) NOT NULL COMMENT '작성자',
  `register_time` DATETIME NULL DEFAULT NULL COMMENT '작성일',
  `update_time` DATETIME NULL DEFAULT NULL COMMENT '수정일',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='게시판';

 

Dependency 추가

프로젝트로 돌아와 JPA(JPA란?)를 사용하기 위한 Dependency 추가와 데이터베이스 설정을 한다.

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

Gradle 적용

이클립스 프로젝트 우 클릭    Gradle    Refresh Gradle Project
인텔리제이 build.gradle에서 Load Gradle Changes (Ctrl+Shift+O)
또는 우측 Gradle 탭에서 Reload All Gradle Projects

 

데이터베이스 설정(application.properties)

spring.datasource.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/데이터베이스명
spring.datasource.username=계정
spring.datasource.password=비밀번호

 

lombok(https://projectlombok.org/download) 설치

lombok (Getter, Setter 등의 반복 메서드를 자동으로 연결하고 도구를 빌드하여 Java를 향상하는 라이브러리)

lombok.jar를 다운로드 받은 후 실행한다.
Specify location..를 통해 이클립스가 설치된 경로를 선택한다.
install / update
Quit installer

 

 

 

설치가 완료되면 이클립스 설치 경로에 lombok.jar가 생성되고 eclipse.ini에 설정이 추가된다.
*STS의 경우 SpringToolSuite4.ini

이제 이클립스를 재시작한다.

재시작 후에 이클립스 상단 탭 Project -> Clean -> 해당 프로젝트 Clean

 

 

 

이제 JPA를 사용하기 위한 설정이 끝났으니, 서비스를 구현해본다.
Service까지 구현하는 패키지 구조는 아래와 같다.

 

 

이름 설명
BoardRequestDto.java 게시판 요청 데이터를 담당
BoardResponseDto.java 게시판 응답 데이터를 담당
BaseTimeEntity.java 반복되는 날짜 데이터의 공통처리를 담당
Board.java board 테이블의 @Entity
BoardRepository.java JpaRepository 구현체
BoardService.java 게시판 @Service

 

BoardRequestDto.java

게시물 등록, 게시물 수정, 게시물 상세 조회에 필요한 필드를 정의한다.
toEntity() 메서드는 Board Entity를 builder 하여 사용한다.

 

package com.board.study.dto.board;

import com.board.study.entity.board.Board;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class BoardRequestDto {
    private Long id;
    private String title;
    private String content;
    private String registerId;

    public Board toEntity() {
        return Board.builder()
            .title(title)
            .content(content)
            .registerId(registerId)
            .build();
    }
}

 

BoardResponseDto.java

게시물 목록, 게시물 상세 조회에 필요한 필드를 정의한다.
Board Entity를 BoardResponseDto에 맞게 변환하는 생성자를 생성한다.

 

package com.board.study.dto.board;

import com.board.study.entity.board.Board;
import java.time.LocalDateTime;
import lombok.Getter;

@Getter
public class BoardResponseDto {
    private Long id;
    private String title;
    private String content;
    private int readCnt;
    private String registerId;
    private LocalDateTime registerTime;

    public BoardResponseDto(Board entity) {
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.content = entity.getContent();
        this.readCnt = entity.getReadCnt();
        this.registerId = entity.getRegisterId();
        this.registerTime = entity.getRegisterTime();
    }

    @Override
    public String toString() {
        return "BoardListDto [id=" + id + ", title=" + title + ", content=" + content + ", readCnt=" + readCnt +
            ", registerId=" + registerId + ", registerTime=" + registerTime + "]";
    }
}

 

BaseTimeEntity.java

Entity에서 공통적으로 사용될 날짜 필드를 관리할 클래스를 정의한다.
꼭 날짜가 아니더라도 공통적으로 반복되는 필드를 정의하여 사용해도 된다.

 

package com.board.study.entity;

import java.time.LocalDateTime;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import lombok.Getter;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {

    @CreatedDate
    private LocalDateTime registerTime;

    @LastModifiedDate
    private LocalDateTime updateTime;
}

 

Board.java

Entity를 정의한다.
테이블의 모든 필드와 Builder 생성자를 구현한다.
혹시나 테이블명이 Class명과 다를 경우 @Entity(name = "테이블명") 을 설정한다.

 

package com.board.study.entity.board;

import com.board.study.entity.BaseTimeEntity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String content;
    private int readCnt;
    private String registerId;

    @Builder
    public Board(Long id, String title, String content, int readCnt, String registerId) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.readCnt = readCnt;
        this.registerId = registerId;
    }
}

 

BoardRepository.java

JpaRepository를 상속받아 CRUD의 기능을 담당하는 인터페이스를 생성한다.

그리고 @Query을 사용한 JPQL 방식의 updateBoard() 메서드도 구현해본다.
이 방식으로 쿼리를 직접 작성하여 사용할 수도 있다.

 

package com.board.study.entity.board;

import com.board.study.dto.board.BoardRequestDto;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;

public interface BoardRepository extends JpaRepository < Board, Long > {

    String UPDATE_BOARD = "UPDATE Board " +
      "SET TITLE = :#{#boardRequestDto.title}, " +
      "CONTENT = :#{#boardRequestDto.content}, " +
      "UPDATE_TIME = NOW() " +
      "WHERE ID = :#{#boardRequestDto.id}";

    @Transactional
    @Modifying
    @Query(value = UPDATE_BOARD, nativeQuery = true)
    public int updateBoard(@Param("boardRequestDto") BoardRequestDto boardRequestDto);
}

 

BoardService.java

게시판 기능을 담당할 Service 클래스로 나중에 파일 및 페이징 처리를 추가할 예정이다.
지금은 간단한 CRUD 메서드만 작성한다.

 

package com.board.study.service;

import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.board.study.dto.board.BoardResponseDto;
import com.board.study.dto.board.BoardRequestDto;
import com.board.study.entity.board.BoardRepository;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class BoardService {

    private final BoardRepository boardRepository;

    @Transactional
    public Long save(BoardRequestDto boardSaveDto) {
        return boardRepository.save(boardSaveDto.toEntity()).getId();
    }

    @Transactional(readOnly = true)
    public List < BoardResponseDto > findAll() {
        return boardRepository.findAll().stream().map(BoardResponseDto::new).collect(Collectors.toList());
    }

    public BoardResponseDto findById(Long id) {
        return new BoardResponseDto(boardRepository.findById(id).get());
    }

    public int updateBoard(BoardRequestDto boardRequestDto) {
        return boardRepository.updateBoard(boardRequestDto);
    }

    public void deleteById(Long id) {
        boardRepository.deleteById(id);
    }
}

 

BoardStudyApplication

Application 클래스에 @EnableJpaAuditing을 추가해 Auditing 기능을 활성화한다.
package com.board.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing // JPA Auditing
@SpringBootApplication
public class BoardStudyApplication {
	public static void main(String[] args) {
		SpringApplication.run(BoardStudyApplication.class, args);
	}
}

 

어노테이션에 대한 설명

이름 설명
@NoArgsConstructor 파라미터가 없는 생성자를 생성한다.
@AllArgsConstructor 모든 인자를 가진 생성자를 생성한다.
@RequiredArgsConstructor 초기화 되지 않은 모든 final 필드, @NonNull로 마크돼있는 모든 필드들에 대한 생성자를 자동으로 생성해준다.
@Getter Class 내 모든 필드의 Getter method를 자동 생성한다.
@Setter Class 내 모든 필드의 Setter method를 자동 생성한다.
@Entity 실제 DB의 테이블과 매칭될 Class임을 명시한다.
즉, 테이블과 링크될 클래스임을 나타낸다.
@MappedSuperclass 이 클래스를 상속하는 엔티티에 매핑되는 테이블에 생성한다.
@EntityListeners(AuditingEntityListener.class) JPA 내부에서 엔티티 객체가 생성/변경되는 것을 감지하는 역할을 한다.
@CreateDate JPA에서 엔티티의 생성 시간을 처리한다.
@LastModifiedDate 최종 수정 시간을 자동으로 처리한다.
@Id 해당 테이블의 PK 필드를 나타낸다.
@GeneratedValue(strategy = GenerationType.IDENTITY) PK의 생성 규칙을 나타낸다.
@Builder 어느 필드에 어떤 값을 채워야 할지 명확하게 정하여 생성 시점에 값을 채워준다.
@Transactional 선언적 트랜잭션을 사용한다.
@Modifying @Query Annotation으로 작성 된 변경, 삭제 쿼리를 사용할때 사용한다.
@Query SQL을 JPQL로 작성할 수 있고, nativeQuery=true 옵션으로 네이티브 쿼리도 사용 가능하게 한다.

 

테스트 코드 작성

화면을 구성하기 전, 기능들이 정상적으로 작동하는지 확인하기 위해 JUnit(테스트 프레임워크)를 이용해 테스트를 진행한다.

프로젝트 생성 당시 설명했었던 것처럼 src/test/java에 Test Class가 생성되는데, 추가로 생성해서 진행해도 되지만 여기서는 생성되어있는 BoardStudyApplicationTests.java를 사용한다.

 

 

package com.board.study;

import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.board.study.dto.board.BoardResponseDto;
import com.board.study.dto.board.BoardRequestDto;
import com.board.study.service.BoardService;

@SpringBootTest
class BoardStudyApplicationTests {

    @Autowired
    private BoardService boardService;

    @Test
    void save() {
        BoardRequestDto boardSaveDto = new BoardRequestDto();

        boardSaveDto.setTitle("제목입니다.");
        boardSaveDto.setContent("내용입니다.");
        boardSaveDto.setRegisterId("작성자");

        Long result = boardService.save(boardSaveDto);

        if (result > 0) {
            System.out.println("# Success save() ~");
            findAll();
            findById(result);
        } else {
            System.out.println("# Fail Save() ~");
        }
    }

    void findAll() {
        List < BoardResponseDto > list = boardService.findAll();

        if (list != null) {
            System.out.println("# Success findAll() : " + list.toString());
        } else {
            System.out.println("# Fail findAll() ~");
        }
    }

    void findById(Long id) {
        BoardResponseDto info = boardService.findById(id);

        if (info != null) {
            System.out.println("# Success findById() : " + info.toString());
            updateBoard(id);
        } else {
            System.out.println("# Fail findById() ~");
        }
    }

    void updateBoard(Long id) {

        BoardRequestDto boardRequestDto = new BoardRequestDto();

        boardRequestDto.setId(id);
        boardRequestDto.setTitle("업데이트 제목");
        boardRequestDto.setContent("업데이트 내용");
        boardRequestDto.setRegisterId("작성자");

        int result = boardService.updateBoard(boardRequestDto);

        if (result > 0) {
            System.out.println("# Success updateBoard() ~");
        } else {
            System.out.println("# Fail updateBoard() ~");
        }
    }
}

 

테스트 코드 실행

2가지 방법 중 선택하여 진행한다.
실행 시 @Test 어노테이션이 있는 메서드가 실행된다.


방법①
프로젝트 우 클릭  →  Run As  →  JUnit Test

방법②
소스파일에서 우 클릭  →  Run As 
→  JUnit Test

 

방법①
방법②

 

 

JUnit 탭에서 진행 결과를 확인할 수 있다.

 

 

 

성공 로그가 출력되었고, 데이터베이스에서도 직접 확인해본다.

 

 

프로젝트 Import

압축을 풀고, 이클립스 import  →  General  →  Existing Projects into Workspace

board_study.zip
0.09MB

 

Spring Boot (게시판) - 1 |  스프링 부트 프로젝트 만들기

Spring Boot (게시판) - 2 |  데이터베이스(MariaDB) 연동 및 JPA CRUD

Spring Boot (게시판) - 3 |  등록, 상세, 리스트 페이지 구현하기

Spring Boot (게시판) - 4 |  수정, 삭제 구현하기

 

반응형