▼ Backend/└ 게시판 만들기

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

Valar 2021. 10. 29. 16:21
반응형

[스프링부트 (Spring Boot)/게시판 만들기] - 2 | 데이터베이스(MariaDB) 연동 및 JPA CRUD

 

구성환경

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

 

기본적인 CRUD 테스트가 끝났고, 이제는 (BootStrap(Guide link) + Thymeleaf)를 사용하여 간단한 화면을 구성하고 등록 및 상세, 목록, 페이징 처리까지 진행해본다.

 

컨트롤러(Controller)

BoardController.java

 

목록, 등록, 상세 화면 매핑, 등록 액션 메서드를 생성해주었고, 목록에는 페이징 처리를 위한 파라미터를 받는다.

 

package com.board.study.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.board.study.dto.board.BoardRequestDto;
import com.board.study.service.BoardService;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class BoardController {

    private final BoardService boardService;

    @GetMapping("/board/list")
    public String getBoardListPage(Model model, @RequestParam(required = false, defaultValue = "0") Integer page, @RequestParam(required = false, defaultValue = "5") Integer size) throws Exception {

        try {
            model.addAttribute("resultMap", boardService.findAll(page, size));
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }

        return "/board/list";
    }

    @GetMapping("/board/write")
    public String getBoardWritePage(Model model, BoardRequestDto boardRequestDto) {
        return "/board/write";
    }

    @GetMapping("/board/view")
    public String getBoardViewPage(Model model, BoardRequestDto boardRequestDto) throws Exception {

        try {
            if (boardRequestDto.getId() != null) {
                model.addAttribute("info", boardService.findById(boardRequestDto.getId()));
            }
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }

        return "/board/view";
    }

    @PostMapping("/board/write/action")
    public String boardWriteAction(Model model, BoardRequestDto boardRequestDto) throws Exception {

        try {
            Long result = boardService.save(boardRequestDto);

            if (result < 0) {
                throw new Exception("#Exception boardWriteAction!");
            }
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }

        return "redirect:/board/list";
    }
}

 

 

서비스(Service)

BoardService.java

 

기존에 작성했던 서비스에서 페이징 처리를 위한 설정을 추가했다.

Spring Data JPA 에서는 페이징 처리를 위한 PageRequest 객체를 지원하므로 간단하게 페이징 처리를 할 수 있다.

 

package com.board.study.service;

import java.util.HashMap;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
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.Board;
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();
    }

    /*
    	트랜잭션에 readOnly=true 옵션을 주면 스프링 프레임워크가 하이버네이트 세션 플러시 모드를 MANUAL로 설정한다.
    	이렇게 하면 강제로 플러시를 호출하지 않는 한 플러시가 일어나지 않는다.
    	따라서 트랜잭션을 커밋하더라도 영속성 컨텍스트가 플러시 되지 않아서 엔티티의 등록, 수정, 삭제이 동작하지 않고,
    	또한 읽기 전용으로, 영속성 컨텍스트는 변경 감지를 위한 스냅샷을 보관하지 않으므로 성능이 향상된다.
    */
    @Transactional(readOnly = true)
    public HashMap < String, Object > findAll(Integer page, Integer size) {

        HashMap < String, Object > resultMap = new HashMap < String, Object > ();

        Page < Board > list = boardRepository.findAll(PageRequest.of(page, size));

        resultMap.put("list", list.stream().map(BoardResponseDto::new).collect(Collectors.toList()));
        resultMap.put("paging", list.getPageable());
        resultMap.put("totalCnt", list.getTotalElements());
        resultMap.put("totalPage", list.getTotalPages());

        return resultMap;
    }

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

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

    public int updateBoardReadCntInc(Long id) {
        return boardRepository.updateBoardReadCntInc(id);
    }

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

 

 

목록 HTML

list.html (/src/main/resources/templates/board)

 

Thymeleaf를 이용하여 데이터 처리

사용방법은 Denpendency (프로젝트 생성 당시 추가함) 추가와 html 태그에 xmlns:th="http://www.thymeleaf.org"를  추가 하면 사용할 수 있다.

 

코드를 보면 속성 앞에 th:Thymeleaf 표현 방식이다.

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Board List</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <!--부트스트랩 css 추가-->
        <link rel="stylesheet" href="/css/lib/bootstrap.min.css">
    </head>
    <body>
        <div id="wrapper">
            <div class="container">
                <div class="col-md-12">
                    <table class="table table-striped table-horizontal table-bordered mt-3">
                        <thead class="thead-strong">
                            <tr>
                                <th width="10%">게시글번호</th>
                                <th width="">제목</th>
                                <th width="20%">작성자</th>
                                <th width="20%">작성일</th>
                            </tr>
                        </thead>
                        <tbody id="tbody">
                            <tr th:each="list,index : ${resultMap.list}" th:with="paging=${resultMap.paging}">
                                <td>
                                    <span th:text="${(resultMap.totalCnt - index.index) - (paging.pageNumber * paging.pageSize)}"></span>
                                </td>
                                <td>
                                    <a th:href="@{./view(id=${list.id})}">
                                        <span th:text="${list.title}"></span>
                                    </a>
                                </td>
                                <td>
                                    <span th:text="${list.registerId}"></span>
                                </td>
                                <td>
                                    <span th:text="${list.registerTime}"></span>
                                </td>
                            </tr>
                        </tbody>
                    </table>
                    <div class="row">
                        <div class="col">
                            <ul class="pagination">
                                <li class="page-item" th:each="index : ${#numbers.sequence(1, resultMap.totalPage)}" th:with="paging=${resultMap.paging}">
                                    <a class="page-link" th:href="@{./list(page=${index - 1},page=${paging.pageSize})}">
                                        <span th:text="${index}"></span>
                                    </a>
                                </li>
                            </ul>
                        </div>
                    </div>
                    <button type="button" class="btn btn-danger" onclick="fnDelete()">Delete</button>
                    <button type="button" class="btn btn-primary" onclick="javascript:location.href='/board/write'">Register</button>
                </div>
            </div>
        </div>
        <!--부트스트랩 js, jquery 추가-->
        <script src="/js/lib/jquery.min.js"></script>
        <script src="/js/lib/bootstrap.min.js"></script>
    </body>
</html>

 

등록 HTML

write.html (/src/main/resources/templates/board)

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Board List</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <!--부트스트랩 css 추가-->
        <link rel="stylesheet" href="/css/lib/bootstrap.min.css">
    </head>
    <body>
        <div class="container">
            <h1>Board Register.</h1>
            <form action="/board/write/action" method="post">
                <div class="mb-3">
                    <label class="form-label">Title.</label>
                    <input type="text" class="form-control" name="title">
                </div>
                <div class="mb-3">
                    <label class="form-label">Content</label>
                    <textarea class="form-control" rows="5" name="content"></textarea>
                </div>
                <div class="mb-3">
                    <label class="form-label">Writer.</label>
                    <input type="text" class="form-control" name="registerId">
                </div>
                <button type="button" class="btn btn-success" onclick="javascript:location.href='/board/list'">Previous</button>
                <button type="submit" class="btn btn-primary">Submit</button>
            </form>
        </div>
        <!--부트스트랩 js, jquery 추가-->
        <script src="/js/lib/jquery.min.js"></script>
        <script src="/js/lib/bootstrap.min.js"></script>
    </body>
</html>

 

 

상세 HTML

view.html (/src/main/resources/templates/board)

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Board List</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <!--부트스트랩 css 추가-->
        <link rel="stylesheet" href="/css/lib/bootstrap.min.css">
    </head>
    <body>
        <div class="container">
            <h1>Board View.</h1>
            <div class="mb-3">
                <label class="form-label">Title.</label>
                <input type="text" class="form-control" name="" th:value="${info.title}">
            </div>
            <div class="mb-3">
                <label class="form-label">Content</label>
                <textarea class="form-control" rows="5" name="" th:text="${info.content}"></textarea>
            </div>
            <div class="mb-3">
                <label class="form-label">Writer.</label>
                <input type="text" class="form-control" name="" th:value="${info.registerId}">
            </div>
            <button type="button" class="btn btn-success" onclick="javascript:location.href='/board/list'">Previous</button>
        </div>
        <!--부트스트랩 js, jquery 추가-->
        <script src="/js/lib/jquery.min.js"></script>
        <script src="/js/lib/bootstrap.min.js"></script>
    </body>
</html>

 

사용된 Thymeleaf 속성

이름 설명
th:each 반복 하려는 html 엘리먼트에 사용하여 콜렉션(Collection) 을 반복
th:each="콜렉션 변수명, status 변수명 : ${리스트}"
th:with 변수형태의 값을 재정의
th:with = 변수명=${data}
th:text document 객체에 텍스트를 삽입
th:text = ${data}
th:href href에 값을 삽입할 때 사용
th:href = @{ URL (파라미터명=${data} } 
th:value value에 값을 삽입할 때 사용
th:value = ${data}

 

테스트

 

 

 

 

application.properties

재시작 없이 HTML 적용하기

 

Thymeleaf 템플릿 결과는 캐싱하는 것이 디폴트 값이다.
따라서 개발할 때 화면을 수정하고 브라우저를 새로고침하면 바로 반영이 되지 않는다.
개발할때는 계속적으로 새로고침으로 화면을 확인해야할 일이 많기 때문에 false로 해준다.

 

spring.thymeleaf.cache=false

 

프로젝트 Import

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

board_study.zip
0.22MB

 

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

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

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

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

 

 

 

반응형