게시판 조회 및 글 작성 기능 추가

뚜우웅이·2023년 2월 6일
0

SpringBoot웹

목록 보기
3/23

MariaDB 다운로드


HeidiSQl을 실행한다.
왼쪽 하단에 있는 신규 버튼을 누르고 세션 이름을 설정해줍니다.

저장 후 열기를 눌러주면 아래와 같은 창이 나옵니다.

mydb라는 이름의 새로운 데이터베이스를 생성해줍니다.
데이터베이스 생성 후 사용자 아이콘을 새로운 사용자를 만든 후 객체 추가 버튼을 눌러 방금 만든 데이터베이스를 적용해준 뒤 전체 권한을 줍니다.

LocalDB를 연결 해제한 후 새로 만든 사용자(myadmin)으로 로그인 해준 뒤
게시판에 사용할 테이블을 만들어줍니다.

JPA와 DB사용을 위한 설정

JPA와 MariaDB 사용을 위해 pom.xml에 의존성을 추가해줍니다.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>org.mariadb.jdbc</groupId>
	<artifactId>mariadb-java-client</artifactId>
	<version>3.1.2</version>
</dependency>

현재 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.project</groupId>
	<artifactId>myhome</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>myhome</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mariadb.jdbc</groupId>
			<artifactId>mariadb-java-client</artifactId>
			<version>3.1.2</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
			<version>3.0.2</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

application.properties에 DB사용을 위한 코드를 추가합니다.
데이터베이스 이름, 사용자, 비밀번호, 드라이버

spring.datasource.url=jdbc:mariadb://localhost:3306/mydb
spring.datasource.username=myadmin
spring.datasource.password=(비밀번호)
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver

테이블의 정보를 가져오기 위한 작업

com.project.myhome 밑에 model 패키지를 생성한 후 Board라는 클래스를 생성해줍니다.

package com.project.myhome.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;

@Entity
@Data
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String title;
    private String content;
}

JPA사용을 위해 com.project.myhome 밑에 repository 폴더를 생성한 후 BoardRepository 인터페이스를 생성합니다.

package com.project.myhome.repository;

import com.project.myhome.model.Board;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BoardRepository extends JpaRepository<Board, Long> {

}

BoardRepository을 이용해서 테이블의 정보를 가져오기 위해 BoardController를 수정해줍니다.

package com.project.myhome.controller;

import com.project.myhome.model.Board;
import com.project.myhome.repository.BoardRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
@RequestMapping("/board")
public class BoardController {
    @Autowired
    private BoardRepository boardRepository;
    @GetMapping("/list")
    public String list(Model model){
        List<Board> boards =  boardRepository.findAll();
        model.addAttribute("boards", boards);
        return "board/list";
    }
}

list.html을 수정해서 JPA가 잘 동작하는지 확인을 해줍니다.

<div class="container">
    <h2>게시판</h2>
    <div>총 건수 : <span th:text="${#lists.size(boards)}"></span></div>
</div>

테이블에 값 추가해서 작동 확인

board 테이블에서 데이터를 클릭 후 우클릭을 해서 행삽입을 눌러줍니다.

title에 제목 content에 내용을 적고 적용을 눌러줍니다.
테이블에 적용을 누르게 되면 게시판 창에 총 건수가 0에서 1로 바뀌게 됩니다.

게시판 디자인

bootstrap 홈페이지에서 table을 검색한 후 샘플 코드를 가져옵니다.
가져온 코드를 list.html에 붙여넣고 반복문으로 테이블에서 값을 가져오기 위해 th:each를 사용합니다.

<div class="container">
    <h2>게시판</h2>
    <div>총 건수 : <span th:text="${#lists.size(boards)}"></span></div>
    <table class="table table-striped">
        <thead>
        <tr>
            <th scope="col">번호</th>
            <th scope="col">제목</th>
            <th scope="col">작성자</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="board : ${boards}">
            <td th:text="${board.id}"></td>
            <td th:text="${board.title}"></td>
            <td>홍길동</td>
        </tr>
        </tbody>
    </table>
</div>

화면에 제대로 표시되는 것을 알 수 있습니다.

글 쓰기 버튼 활성화

글 쓰기 버튼을 만들기 위해 bootstrap에서 버튼 스타일을 가져와 list.html에 붙여 넣고 우측 정렬을 해줍니다.

<div class="text-right">
     <button type="button" class="btn btn-primary">글 작성</button>
</div>

글 작성 버튼을 동작하게 하기 위해 form.html 파일을 생성합니다.
(bootstrap에 있는 textarea 코드를 가져온다.)

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/common :: head('게시판')">
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport">

    <!-- Bootstrap CSS -->
    <link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
          integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" rel="stylesheet">
    <link href="starter-template.css" rel="stylesheet" th:href="@{/starter-template.css}">
    <title>게시판</title>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top" th:replace="fragments/common :: menu('board')">
    <a class="navbar-brand" href="#">Spring Boot Tutorial</a>
    <button aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler"
            data-target="#navbarsExampleDefault" data-toggle="collapse" type="button">
        <span class="navbar-toggler-icon"></span>
    </button>

    <div class="collapse navbar-collapse" id="navbarsExampleDefault">
        <ul class="navbar-nav mr-auto">
            <li class="nav-item active">
                <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#">게시판</a>
            </li>
        </ul>
    </div>
</nav>

<div class="container">
    <h2>게시판</h2>
    <form action="#" th:action="@{/board/form}" th:object="${board}" method="post">
        <div class="form-group">
            <label for="title">제목</label>
            <input type="text" class="form-control" id="title"  th:field="*{title}" placeholder="제목을 입력하세요.">
        </div>
        <div class="form-group">
            <label for="content">내용</label>
            <textarea class="form-control" id="content" rows="3" th:field="*{content}"></textarea>
        </div>
        <div class="text-right">
            <button type="submit" class="btn btn-primary">확인</button>
            <a type="button" class="btn btn-primary" th:href="@{/board/list}">취소</a>
        </div>
    </form>
    </div>

</div>

<script crossorigin="anonymous"
        integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
        src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
<script crossorigin="anonymous"
        integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct"
        src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>

</body>
</html>

BoardController 수정

확인을 누르면 데이터베이스에 저장되면서 다시 게시판 창으로 돌아오는 기능을 추가합니다.

    @GetMapping("/form")
    public String form(Model model){
        model.addAttribute("board", new Board());
        return "board/form";
    }

    @PostMapping("/form")
    public String form(@ModelAttribute Board board){
        boardRepository.save(board);
        return "redirect:/board/list";
    }

list.html 수정

<div class="text-right">
      <a type="button" class="btn btn-primary" th:href="@{/board/form}">글 작성</a>
 </div>

작성한 글이 DB에 제대로 전달 되고 다시 list.html창으로 돌아가는 것을 확인할 수 있습니다.

게시판에 있는 글을 클릭했을 때 이동 및 글 수정

list.html td태그에 href 속성을 추가해줍니다.

<tr th:each="board : ${boards}">
            <td th:text="${board.id}">Mark</td>
            <td><a th:text="${board.title}" th:href="@{/board/form(id=${board.id})}">Otto</a></td>
            <td>홍길동</td>
        </tr>

BoardController 수정
required 속성을 사용하여 필수인지 확인
새글을 작성할 때는 parameter가 필요 없다.

제대로 값을 저장했는지 어노테이션을 통해 확인할 수 있습니다.

    @GetMapping("/form")
    public String form(Model model, @RequestParam(required = false) Long id){
        if(id == null){
            model.addAttribute("board", new Board());
        }
        else {
            Board board = boardRepository.findById(id).orElse(null);
            model.addAttribute("board", board);
        }
        return "board/form";
    }

    @PostMapping("/form")
    public String form(@ModelAttribute Board board){
        boardRepository.save(board);
        return "redirect:/board/list";
    }

Parameter 검증을 위한 Validation

Board.java에 paramer 검증을 위한 어노테이션을 추가합니다.

package com.project.myhome.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;


@Entity
@Data
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @NotNull
    @Size(min=2,max=50)
    private String title;
    @NotNull
    @Size(min = 1, max = 15000)
    private String content;
}

BoardController에 parameter 검증을 위한 @Vaild를 추가해줍니다.

    @PostMapping("/form")
    public String form(@Valid Board board, BindingResult bindingResult){
        if (bindingResult.hasErrors()) {
            return "board/form";
        }
        boardRepository.save(board);
        return "redirect:/board/list";
    }

어노테이션으로 정의한 값에 부합하지 않는 parameter가 들어오면 인터페이스에서 오류가 났다고 알려주기 위해 form.html을 수정합니다.
bootstrap에서

<div class="invalid-feedback">
        Please provide a valid city.
      </div>

위의 코드를 가져오고 spring.io 가이드에서 가져온 코드를 사용합니다.

th:if="${#fields.hasErrors('name')}" th:errors="*{name}"
<div class="form-group">
            <label for="title">제목</label>
            <input type="text" class="form-control"
                   th:classappend="${#fields.hasErrors('title')} ? 'is-invalid'" id="title"
                   th:field="*{title}" placeholder="제목을 입력하세요.">
            <div class="invalid-feedback" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">
                제목은 2글자 이상 50글자 이하로 작성해야 합니다.
            </div>
        </div>
        <div class="form-group">
            <label for="content">내용</label>
            <textarea class="form-control" id="content"
                      th:classappend="${#fields.hasErrors('content')} ? 'is-invalid'"
                      rows="3" th:field="*{content}"></textarea>
            <div class="invalid-feedback" th:if="${#fields.hasErrors('content')}" th:errors="*{content}">
                내용은 1글자 이상 15000글자 이하로 작성해야 합니다.
            </div>
        </div>


제목에 한글자만 입력시 에러가 뜨는 것을 확인할 수 있습니다.

어노테이션으로 값을 제한하는 것은 자유도의 제약이 있기 때문에 자바 클래스로 값을 제한을 하기 위해 validator 패지키를 생성 한 후 패키지 아래에 BoardValidator라는 클래스를 생성합니다.

content의 값이 비어있을 경우 작동하게 만드는 코드입니다.

package com.project.myhome.validator;

import com.project.myhome.model.Board;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.thymeleaf.util.StringUtils;

@Component
public class BoardValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return Board.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors errors) {
        Board b = (Board) obj;
        if(StringUtils.isEmpty(b.getContent())){
            errors.rejectValue("content", "key", "내용을 입력하세요");
        }
    }
}

BoardController도 수정을 해줍니다.
validate 함수를 호출해서 값을 확인합니다.

package com.project.myhome.controller;

import com.project.myhome.model.Board;
import com.project.myhome.repository.BoardRepository;
import com.project.myhome.validator.BoardValidator;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
@RequestMapping("/board")
public class BoardController {
    @Autowired
    private BoardRepository boardRepository;
    @Autowired
    private BoardValidator boardValidator;

    @GetMapping("/list")
    public String list(Model model){
        List<Board> boards =  boardRepository.findAll();
        model.addAttribute("boards", boards);
        return "board/list";
    }

    @GetMapping("/form")
    public String form(Model model, @RequestParam(required = false) Long id){
        if(id == null){
            model.addAttribute("board", new Board());
        }
        else {
            Board board = boardRepository.findById(id).orElse(null);
            model.addAttribute("board", board);
        }
        return "board/form";
    }

    @PostMapping("/form")
    public String form(@Valid Board board, BindingResult bindingResult){
        boardValidator.validate(board, bindingResult);
        if (bindingResult.hasErrors()) {
            return "board/form";
        }
        boardRepository.save(board);
        return "redirect:/board/list";
    }
}

BindingResult는 스프링이 제공하는 검증 오류를 보관하는 객체입니다.

제대로 작동하는지 확인하기 위해 Board클래스도 수정을 해줍니다.

@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @NotNull
    @Size(min=2,max=50, message = "제목은 2자 이상 5자 이하입니다.")
    private String title;
    private String content;


제목에 있는 에러메시지는 어노테이션을 통해 만들어진 메시지이고 내용에 있는 에러 메시지는 validate 함수에 있는 메시지입니다.

profile
공부하는 초보 개발자

0개의 댓글