Today
-
Yesterday
-
Total
-

ABOUT ME

-

  • Spring Boot (게시판) - 3 | 등록, 상세, 리스트 페이지 구현하기
    ▼ Backend/└ 게시판 만들기 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 |  수정, 삭제 구현하기

     

     

     

    반응형

    댓글

Designed by Tistory.