본문 바로가기
프로그래밍/Springboot-토이프로젝트

【게시판-03】글작성

by 코이킹 2022. 9. 8.
반응형

안녕하세요 코이킹입니다.
이번 포스트에서는 글 작성 기능을 구현하는 과정에 대한 내용이 되겠습니다. 

 


1. 목표  

 - 스프링 부트를 사용해서 웹 어플리케이션을 만드는 흐름에 대해서 이해할 수 있다.  
 - 스프링 부트를 사용해서 폼 데이터를 수신하여 DB에 저장하는 웹 어플리케이션을 만들 수 있다.

 

2. 어떻게 구현할지에 대한 설명 

글 작성 기능의 핵심을 정의해보면  '유저로 부터 입력받은 데이터를 DB에 저장' 하는 것이 되겠습니다. 

'글 작성 기능의 핵심정의'(간략/추상화된 사양)를 바탕으로 해야 할 일(상세 사양)을 추려내는 것이 개발의 시작이라고 생각합니다. 

 

저의 경우 일단 생각나는대로 내 자신에게 질문을하는 것에서 부터 시작합니다.  질문에 대한 답변도 스스로 도출합니다. 

답변은 가능한한 여러개를 근거를 들어서 도출하는게 좋습니다. 

스스로 질문해보기 스스로 추려낸 답변
유저로 부터 어떻게 데이터를 입력받을 수 있을까? 데이터를 입력받을 페이지나 모달이 필요하다. 
글 작성 페이지는 어디에서 이동이 가능할까?  글 목록 페이지에서 데이터 입력창으로 이동하는 링크가 있어야겠다. 
글 작성 페이지에서 입력받은 데이터를 어떻게 백엔드로 보낼 수 있을 까? 폼 태그의 액션이나 자바스크립트를 사용해야겠다
유저로 부터의 글 작성 페이지 표시 요청과, 글 작성 처리 요청을 어떻게 수신해야 할까? 컨트롤러에 글 작성 페이지 표시 요청과 글 작성 처리 요청을 각각 매칭하는 메서드를 추가한다. 
글 작성 페이지 표시요청과, 글 작성 처리 요청은 어떤 Http메서드를 사용해야 할까? 페이지 표시 요청은 단순 페이지 표시 이므로 GET 요청,
글 작성 처리 요청은 민감한 정보가 있을 수 있으니 POST 요청

여담으로 저의 경우 실무에서 질문과 답변 작업이 끝나면 간략화된 사양을 언급한 관계자나 상급자에게 리뷰를 받아 사양을 확정합니다. 

 

위에서 말한 '글 작성 기능의 핵심정의'와 같은 요구사항이 문서나 회의 중에 몇 개씩 정해지는데,  

저 연차의 개발자는 위의 해야 할 일(상세 사양)을 그대로 구현하는 데 일단 집중하며, 경력이 쌓여 여유가 생긴다면 사양이 정의되는 회의 등의 커뮤니케이션의 장에서 사양의 부족한 점이 있거나 추가되면 좋을 사양을 타당한 근거를 가지고 제안하면 더 많은 성과를 낼 수 있을 거라고 생각합니다. 

 

설명은 여기서 마치고 지난 포스트에서 설명 드린대로 화면부터 구현한 후 백엔드 부분을 구현하도록 하겠습니다. 


3. 소스코드와 해석 

1) 글목록 템플릿 : /template-springboot/src/main/resources/templates/board/list.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, inital-scale=1.0"/>
</head>
<body>

	<div>
		<div class="nav justify-content-end px-5 pt-2">
	        <button class="btn btn-info text-white" id="write-board" type="button" onclick="location.href='/board/write'">
	        Contents Write
	        </button>
	    </div>
	    <hr/>
	    <div>
		    <table class="table" border="1">
		    <thead class="table-dark">
		    <tr>
		        <th>No</th>
		        <th>Contents Type</th>
		        <th>Title</th>
		        <th>CreatedTime</th>
		        <th>UpdatedTime</th>
		        <th>Likes</th>
		    </tr>
		    </thead>
		    <tbody>
		    <tr th:each="board: ${boardList}">
		        <td th:text="${board.boardNo}"></td>
		        <td th:text="${board.type}==1 ? 'Normal' : 'MemberShip'"></td>
		        <td th:text="'[' + ${board.title} + ']'"></td>
		        <td th:text="${board.createdTime}"></td>
		        <td th:text="${board.updatedTime}"></td>
		        <td th:text="${board.likes}"></td>
		    </tr>
		    </tbody>
		    </table>
	    </div>
	</div>

</body>
</html>

- 10~14행 : 글 작성 페이지로 이동할 수 있는 버튼을 추가했습니다. 

 

2) 글 작성 페이지 : /template-springboot/src/main/resources/templates/board/write.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, inital-scale=1.0"/>
<title>Board Write</title>
</head>
<body>
<div>

    <form method="post" th:action=@{/board/write} class="form-horizontal">
    <fieldset>
    	<legend class="text-center header">Write Board</legend>

    	<input th:if="${not #strings.isEmpty(session?.member?.memberId)}" type="hidden" name="memberId" th:value="${session.member.memberId}" />

      <div class="col-md-4">
        <h5><label> Type </label></h5>
	    <select class="form-select" name="type">
	        <option th:each="boardType : ${boardTypes}" th:value="${boardType.value}" th:inline="text">[[${boardType.name}]]</option>
	    </select>
	  </div>


    <div class="form-group py-2">
      <div class="col-md-8">
        <h5><label>Title</label></h5>
        <input id="title" name="title" type="text" placeholder="Input Title" class="form-control">
      </div>
    </div>

    <div class="form-group py-2">
      <div class="col-md-8">
        <h5><label>  Contents </label></h5>
        <textarea name="contents" placeholder="Input Contents" class="form-control" rows="6"></textarea>
      </div>
    </div>
    <hr/>
    <div class="form-group py-2">
       <input type="submit" value="Write Complete" class="btn btn-primary">
    </div>
    </fieldset>
    </form>

    <div>
        <button class="btn btn-info" type="button" onclick="location.href='/board/list'">Back to List</button>
    </div>
</div>

</body>
</html>

- 11행 : 폼 태그를 통해서 데이터를 백엔드로 전송할 때의, Http 메서드를 post로 설정, action에는 전송 시의 url정보를 설정.

- 15행 : 세션스코프에 들어있는 유저 정보를 가져오는 코드. 회원 기능을 구현 시에 다시 설명 예정.

- 19~21행 : 셀렉트 태그의 옵션 값을 boardTypes리스트의 데이터 수만큼 반복해서 추가한다. 

- 19, 28, 35행 : name속성의 값을 Form데이터를 담을 클래스의 필드명과 일치시켜야 한다.  그래야 정상적으로 데이터를 백엔드로 보낼 수 있다. 

 

3) 글의 타입을 정의한 enum : /template-springboot/src/main/java/com/sb/template/enums/BoardType.java

package com.sb.template.enums;

import java.util.ArrayList;
import java.util.List;

public enum BoardType {

	NORMAL(1, "Normal"), MEMBERSHIP(2, "MemberShip");

	public Integer value;
	public String name;
	private static List<BoardType> typeList = null;

	BoardType(Integer value, String name) {
		this.value = value;
		this.name = name;
	}

	public static List<BoardType> getBoardTypes() {
		if (typeList == null) {
			typeList = new ArrayList<>();
			typeList.add(NORMAL);
			typeList.add(MEMBERSHIP);
		}
		return typeList;
	}
}

- 8행 : 게시글의 타입을 상수로 정의해 둔다. 변하지 않는 값이 필요하다면 상수로 정의해 두어야 프로그램의 오작동을 방지할 수 있음.

 

4) 글 작성 처리 요청 시 폼 데이터가 들어갈 클래스 : /template-springboot/src/main/java/com/sb/template/forms/BoardForm.java

package com.sb.template.forms;

import com.sb.template.entity.Board;

import lombok.Data;

@Data
public class BoardForm {

	private Integer no;
	private Integer type;
	private String title;
	private String contents;
	private String memberId;

	public Board toEntity() {
		Board board = new Board();

		board.setType(this.type);
		board.setTitle(this.title);
		board.setContents(this.contents);
		board.setMemberId(((this.memberId == null || (this.memberId.equals(""))) ? "" : this.memberId));

		return board;
	}
}

- 10~14행 : 글 작성 페이지의 input 태그의 name속성과 값이 일치하면, 스프링 부트가 자동으로 데이터를 바인딩해준다. 

- 16~25행 : 유저로부터 입력받은 데이터를 사용하여 엔티티 객체를 생성하는 메서드

 

 

5) 컨트롤러 : /template-springboot/src/main/java/com/sb/template/controller/BoardController.java

package com.sb.template.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.sb.template.entity.Board;
import com.sb.template.enums.BoardType;
import com.sb.template.forms.BoardForm;
import com.sb.template.service.BoardService;

@Controller
@RequestMapping(path = "/board")
public class BoardController {

	@Autowired
	private BoardService boardService;

	@RequestMapping(method = RequestMethod.GET, path = "list")
	public String viewBoardList(Model model) {

		List<Board> boardList = boardService.getAllBoard();
		model.addAttribute("boardList", boardList);

		return "board/list";
	}


	@RequestMapping(method = RequestMethod.GET, path = "write")
	public String writeBoard(Model model) {

		model.addAttribute("boardTypes", BoardType.getBoardTypes());

		return "board/write";
	}


	@RequestMapping(method = RequestMethod.POST, path = "write")
	public String writeCompleteBoard(BoardForm form, Model model) {

		Integer boardNo = null;
		boardNo = boardService.createBoard(form.toEntity()).getBoardNo();

		return "redirect:/board/list";
	}

}

- 33~39행 : 글 작성 페이지 템플릿을 유저에게 표시해 주는 메서드. 게시글 타입 리스트 데이터를 리퀘스트 스코프에 넣어서 템플릿에 표시한다. 

- 42~49행 : 글 작성 처리 요청을 수신하는 메서드, 글 작성 처리 자체는 서비스에 위임한다. 리턴 값으로 boardNo변수에 값을 넣는 이유는 글 상세보기 구현 시에 사용하기 위해서이다.

 

6) 서비스 : /template-springboot/src/main/java/com/sb/template/service/BoardService.java

package com.sb.template.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.sb.template.entity.Board;
import com.sb.template.repo.BoardRepository;

@Service
public class BoardService {

	@Autowired
	private BoardRepository boardRepository;

	public List<Board> getAllBoard() {

		List<Board> res = boardRepository.findAll();
		if (res == null) return null;

		return res;
	}

	public Board createBoard(Board board) {

		return boardRepository.save(board);
	}
}

- 25~28행 : DB에 유저로부터 입력받은 게시글 데이터를 삽입한다. 

 

4. 동작확인 

 


5. 데이터 흐름

① 글 목록 페이지에서 글 작성 버튼을 클릭하여, 백엔드의 컨트롤러에 글 작성 페이지를 요청한다. 

② 컨트롤러는 글 작성 페이지 템플릿을 브라우저에 리턴한다.

③ 글 작성 페이지에서 유저로부터 입력받은 폼 데이터를 컨트롤러에서 수신하여, 서비스 컴포넌트에 글 작성 처리를 위임한다. 컨트롤러에서 서비스로 처리 위임시에 폼 데이터가 담긴 폼 클래스에서 엔티티의 형태로 데이터를 성형하여 서비스 메서드의 파라미터로 넘긴다. 

서비스에서는 게시글 데이터를 DB에 저장하는 처리를 리포지토리를 통해 수행한다. 

④ DB에 신규 게시글 데이터의 저장이 확인되면, 컨트롤러에서는 리다이렉트 방식으로 갱신된 글목록 페이지를 표시하는 것으로 글작성 처리를 마친다.  

※ 리다이렉트는 브라우저에서 리다이렉트로 지정된 경로로 다시 요청하는 방식으로 페이지를 새롭게 띄우는 것을 말함.

⑤ 리다이렉트로 게시글 목록 페이지 요청 발생하고, 컨트롤러는 이를 수신하여 게시글 목록 요청에 대한 일련의 처리를 실시한다.   

⑥ 일련의 처리가 끝난 후 컨트롤러는 갱신된 글목록 페이지 템플릿을 브라우저로 리턴한다.

6. 전체 소스코드 

https://github.com/leeyoungseung/template-springboot/tree/feature/03_basic_board_create

 

GitHub - leeyoungseung/template-springboot

Contribute to leeyoungseung/template-springboot development by creating an account on GitHub.

github.com


글 작성 구현은 이것으로 마치겠습니다.

다음 포스트는 글 상세보기 구현에 대한 내용이 되겠습니다.

반응형

댓글