이전글 보기

https://aamoos.tistory.com/677

 

[Spring Jpa] 8. 게시판 만들기 - 리스트 화면 체크박스 checked 제어 개발

목표 1. 최상단의 전체 체크 박스가 체크되었을때 하위 체크박스들이 전부 체크가 되고 체크가 안되면 하위 체크박스들이 체크가 안되게 수정 2. 하위 체크박스가 전부 체크되면 전체 체크박스 c

aamoos.tistory.com

 

목표

- 이번장에서 변경하는 부분은 체크박스 선택해서 삭제시 post로 form 전송하는부분, 삭제시 del_yn 플래그를 Y로 업데이트, 이전장에만든 querydsl select 부분에 where절에 del_yn을 Y 조건을 추가하는것입니다.

 

list.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"  xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="layout/default_layout">
    <div layout:fragment="content" class="content">
        <form th:action th:object="${form}" method="get">
            <nav class="container">
                <br>
                <div class="input-group">
                    <input type="text" name="searchVal" th:value="${searchVal}" class="form-control" placeholder="제목을 입력해주세요.">
                    <button type="submit" class="btn btn-secondary">검색</button>
                </div>
                <br>
                <table class="table table-hover">
                    <colgroup>
                        <col width="2%" />
                        <col width="5%" />
                        <col width="20%" />
                        <col width="5%" />
                        <col width="5%" />
                        <col width="5%" />
                    </colgroup>
                    <thead>
                        <tr>
                            <th>
                                <label class="checkbox-inline">
                                <input type="checkbox" id="allCheckBox" onclick="allChecked()">
                                </label>
                            </th>
                            <th>번호</th>
                            <th>제목</th>
                            <th>작성자</th>
                            <th>날짜</th>
                            <th>조회수</th>
                        </tr>
                    </thead>

                    <tbody>
                        <tr th:each="list, index : ${list}">
                            <td>
                                <label class="checkbox-inline">
                                    <input type="checkbox" name="chk" class="chk"onclick="chkClicked()" th:value="${list.id}">
                                </label>
                            <td th:text="${totalCount - (size * number) - index.index}"></td>
                            <td><a th:text="${list.title}" href=""></a></td>
                            <td th:text="${list.username}"></td>
                            <td th:text="${#temporals.format(list.regDate, 'yyyy-MM-dd')}"></td>
                            <td th:text="${list.viewCount}"></td>
                        </tr>
                    </tbody>
                </table>
                <br>

                <div class="d-flex justify-content-end">
                    <a href='javascript:boardDelete();' class="btn btn-danger">글삭제</a>
                    <a href="/write" class="btn btn-primary">글쓰기</a>
                </div>
                <br>
                <nav class="container d-flex align-items-center justify-content-center" aria-label="Page navigation example"
                     th:with="start=${(list.number/maxPage)*maxPage + 1},
                      end=(${(list.totalPages == 0) ? 1 : (start + (maxPage - 1) < list.totalPages ? start + (maxPage - 1) : list.totalPages)})">
                    <ul class="pagination">

                        <li th:if="${start > 1}" class="page-item">
                            <a th:href="@{/?(page=0, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Previous">
                                <span aria-hidden="true">&laquo;&laquo;</span>
                            </a>
                        </li>

                        <li th:if="${start > 1}" class="page-item">
                            <a th:href="@{/?(page=${start - maxPage-1}, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Previous">
                                <span aria-hidden="true">&laquo;</span>
                            </a>
                        </li>

                        <li th:each="page: ${#numbers.sequence(start, end)}" class="page-item" th:classappend="${list.number+1 == page} ? active">
                            <a th:href="@{/?(page=${page-1}, searchVal=${searchVal})}" th:text="${page}" class="page-link" href="#">1</a>
                        </li>

                        <li th:if="${end < list.totalPages}" class="page-item">
                            <a th:href="@{/?(page=${start + maxPage -1}, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Next">
                                <span aria-hidden="true">&raquo;</span>
                            </a>
                        </li>

                        <li th:if="${end < list.totalPages}" class="page-item">
                            <a th:href="@{/?(page=${list.totalPages-1}, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Next">
                                <span aria-hidden="true">&raquo;&raquo;</span>
                            </a>
                        </li>
                    </ul>
                </nav>
            </nav>
        </form>
    </div>
</html>

<script>

    //체크박스 전체 선택 클릭 이벤트
    function allChecked(target){

        //전체 체크박스 버튼
        const checkbox = document.getElementById('allCheckBox');

        //전체 체크박스 버튼 체크 여부
        const is_checked = checkbox.checked;

        //전체 체크박스 제외한 모든 체크박스
        if(is_checked){
            //체크박스 전체 체크
            chkAllChecked()
        }

        else{
            //체크박스 전체 해제
            chkAllUnChecked()
        }
    }

    //자식 체크박스 클릭 이벤트
    function chkClicked(){

        //체크박스 전체개수
        const allCount = document.querySelectorAll(".chk").length;

        //체크된 체크박스 전체개수
        const query = 'input[name="chk"]:checked'
        const selectedElements = document.querySelectorAll(query)
        const selectedElementsCnt = selectedElements.length;

        //체크박스 전체개수와 체크된 체크박스 전체개수가 같으면 전체 체크박스 체크
        if(allCount == selectedElementsCnt){
             document.getElementById('allCheckBox').checked = true;
        }

        //같지않으면 전체 체크박스 해제
        else{
            document.getElementById('allCheckBox').checked = false;
        }
    }

    //체크박스 전체 체크
    function chkAllChecked(){
        document.querySelectorAll(".chk").forEach(function(v, i) {
            v.checked = true;
        });
    }

    //체크박스 전체 체크 해제
    function chkAllUnChecked(){
        document.querySelectorAll(".chk").forEach(function(v, i) {
            v.checked = false;
        });
    }

    //글삭제
    function boardDelete(){
        //체크박스 체크된 항목
        const query = 'input[name="chk"]:checked'
        const selectedElements = document.querySelectorAll(query)

        //체크박스 체크된 항목의 개수
        const selectedElementsCnt = selectedElements.length;

        if(selectedElementsCnt == 0){
            alert("삭제할 항목을 선택해주세요.");
            return false;
        }

        else{
            if (confirm("정말로 삭제하시겠습니까?")) {
                //배열생성
                const arr = new Array(selectedElementsCnt);

                document.querySelectorAll('input[name="chk"]:checked').forEach(function(v, i) {
                    arr[i] = v.value;
                });

                const form = document.createElement('form');
                form.setAttribute('method', 'post');        //Post 메소드 적용
                form.setAttribute('action', '/delete');

                var input1 = document.createElement('input');
                input1.setAttribute("type", "hidden");
                input1.setAttribute("name", "boardIds");
                input1.setAttribute("value", arr);
                form.appendChild(input1);
                console.log(form);
                document.body.appendChild(form);
                form.submit();
            }
        }
    }

</script>

 

- 이전장과 비교했을때 boardDelete함수를 새로 생성하였습니다. 글삭제 버튼 클릭시 해당 함수가 동작하며 post 방식으로 체크된 체크박스의 시퀀스를 /delete api로 전송합니다.

 

BoardController.java


package jpa.board.controller;

import jpa.board.dto.BoardDto;
import jpa.board.entity.Board;
import jpa.board.repository.BoardRepository;
import jpa.board.repository.CustomBoardRepository;
import jpa.board.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;

/**
 * packageName    : jpa.board.controller
 * fileName       : BoardController
 * author         : 김재성
 * date           : 2022-08-01
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-01        김재성       최초 생성
 */
@Controller
@RequiredArgsConstructor
public class BoardController {

    private final CustomBoardRepository customBoardRepository;
    private final BoardService boardService;

    /**
    * @methodName : list
    * @date : 2022-08-02 오후 2:07
    * @author : 김재성
    * @Description: 게시판 목록화면
    **/
    @GetMapping("/")
    public String list(String searchVal, Pageable pageable, Model model){
        Page<BoardDto> results = customBoardRepository.selectBoardList(searchVal, pageable);
        model.addAttribute("list", results);
        model.addAttribute("maxPage", 5);
        model.addAttribute("searchVal", searchVal);

        pageModelPut(results, model);
        return "board/list";
    }

    /**
    * @methodName : pageModelPut
    * @date : 2022-08-02 오후 4:36
    * @author : 김재성
    * @Description: pagenation 관련 값 model 넣기
    **/
    private void pageModelPut(Page<BoardDto> results, Model model){
        model.addAttribute("totalCount", results.getTotalElements());
        model.addAttribute("size",  results.getPageable().getPageSize());
        model.addAttribute("number",  results.getPageable().getPageNumber());
    }

    /**
    * @methodName : write
    * @date : 2022-08-02 오후 2:07
    * @author : 김재성
    * @Description: 게시판 글쓰기화면
    **/
    @GetMapping("/write")
    public String write(){
        return "board/write";
    }

    /**
    * @methodName : update
    * @date : 2022-08-02 오후 2:07
    * @author : 김재성
    * @Description: 게시판 수정화면
    **/
    @GetMapping("/update")
    public String update(){
        return "board/update";
    }

    @PostMapping("/delete")
    public String delete(@RequestParam List<String> boardIds){

        for(int i=0; i<boardIds.size(); i++){
            Long id = Long.valueOf(boardIds.get(i));
            boardService.deleteBoard(id);
        }

        return "redirect:/";
    }
}
- 맨하단에 delete 메소드를 추가하였습니다. List<String>으로 넘어온 게시판 시퀀스들을 for문으로 돌려서 업데이트 해주는식으로 개발하였습니다.

 

boardService.java

service 패키지를 만들고 BoardService.java를 생성합니다.

 

boardService.java

package jpa.board.service;

import jpa.board.entity.Board;
import jpa.board.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * packageName    : jpa.board.service
 * fileName       : BoardService
 * author         : 김재성
 * date           : 2022-08-03
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-03        김재성       최초 생성
 */

@Service
@RequiredArgsConstructor
public class BoardService {

    private final BoardRepository boardRepository;

    @Transactional
    public Board deleteBoard(Long id){
        Board board = boardRepository.findById(id).get();

        //플래그값이 Y이면 논리삭제
        board.delete("Y");
        return board;
    }

}
Spring data jpa를 이용하여 시퀀스 id로 게시판 항목을 가져옵니다. 이후 board에 delete 생성자를 호출해서 값을 Y로 변경해줍니다. 

 

Board.java

package jpa.board.entity;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.time.LocalDateTime;

/**
 * packageName    : jpa.board.entity
 * fileName       : Board
 * author         : 김재성
 * date           : 2022-08-01
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-01        김재성       최초 생성
 */

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EntityListeners(AuditingEntityListener.class)
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "board_id")
    private Long id;            //번호

    private String title;       //제목
    private String content;     //내용

    @CreatedDate
    private LocalDateTime regDate;     //등록 날짜

    @LastModifiedDate
    private LocalDateTime uptDate;     //수정 날짜

    private Long viewCount;     //조회수
    private String delYn;       //삭제여부

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    public Board update(String title, String content){
        this.title = title;
        this.content = content;
        return this;
    }

    public Board delete(String delYn){
        this.delYn = delYn;
        return this;
    }

    @Builder
    public Board(String title, String content, Long viewCount, String delYn, Member member){
        this.title = title;
        this.content = content;
        this.viewCount = 0L;
        this.delYn = "N";
        this.member = member;
    }

}
- Board.java 엔티티에 delete 생성자를 추가하였습니다.

 

결과화면

삭제하기전

타이틀 95, 타이틀 94를 삭제해보겠습니다.

 

삭제한후

 

 

db로 조회해봤을때 3번째 셀이 DEL_YN 컬럼인데 Y로 변경된것을 볼수있습니다.

 

코드에 repository.save가 없는데 업데이트 되는이유

- Spring jpa의 변경감지 기능을 사용하였습니다.

 

변경감지란?

https://aamoos.tistory.com/664

 

[JPA] 변경감지 기능, merge

JPA 준영속 엔티티를 수정할때 방법 JPA에서는 업데이트를 진행할때 2가지 방법이 있습니다. 1. 변경감지 기능 2. merge 변경감지 기능이란? - id를 기반으로 실제 DB에 있는 영속 상태의 엔티티 findItem

aamoos.tistory.com

 

다음장에는 글쓰기후 저장을 해보겠습니다.

 

다음글 보기

https://aamoos.tistory.com/679

 

[Spring Jpa] 10. 게시판 만들기 - 게시판 글 등록하기

이전글 보기 https://aamoos.tistory.com/678 [Spring Jpa] 9. 게시판 만들기 - 체크박스 선택삭제 기능 만들기 목표 - 이번장에서 변경하는 부분은 체크박스 선택해서 삭제시 post로 form 전송하는부분, 삭제시

aamoos.tistory.com

 

복사했습니다!