[Spring JPA] 8. 체크박스 및 삭제 기능

YB·2023년 8월 1일
0

JPA

목록 보기
9/12

1. list.html 체크박스 및 삭제 만들기

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방식으로 api /delete로 전송

2. 체크박스 삭제 기능 Controller 및 Service

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 javax.transaction.Transactional;

@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로 변경

BoardController.java

package jpa.board.controller;

import jpa.board.dto.BoardDto;
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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
@RequiredArgsConstructor
public class BoardController {

    private final CustomBoardRepository customBoardRepository;
    private final BoardService boardService;

    @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";
    }

    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());
    }

    @GetMapping("/write")
    public String write() {
        return "board/write";
    }

    @GetMapping("/update")
    public String upadte() {
        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형태로 넘어온 게시판을 for문을 통해 업데이트

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;

@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;
    }
}

delete 생성자 추가
delYn parameter 추가

BoardRepositoryImpl.java

package jpa.board.repositoryImpl;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jpa.board.dto.BoardDto;
import jpa.board.dto.QBoardDto;
import jpa.board.repository.CustomBoardRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

import java.util.List;

import static jpa.board.entity.QBoard.board;
import static jpa.board.entity.QMember.member;

@Repository
public class BoardRepositoryImpl implements CustomBoardRepository {

    private final JPAQueryFactory jpaQueryFactory;

    public BoardRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
        this.jpaQueryFactory = jpaQueryFactory;
    }

    @Override
    public Page<BoardDto> selectBoardList(String searchVal, Pageable pageable) {
        List<BoardDto> content = getBoardMemberDtos(searchVal, pageable);
        Long count = getCount(searchVal);
        return new PageImpl<>(content, pageable, count);
    }

    private Long getCount(String searchVal) {
        Long count = jpaQueryFactory
                .select(board.count())
                .from(board)
                //.leftjoin(board.member, member) //검색조건 최적화
                .fetchOne();
        return count;
    }

    private List<BoardDto> getBoardMemberDtos(String searchVal, Pageable pageable) {
        List<BoardDto> content = jpaQueryFactory
                .select(new QBoardDto(
                         board.id
                        ,board.title
                        ,board.content
                        ,board.regDate
                        ,board.uptDate
                        ,board.viewCount
                        ,member.username))
                .from(board)
                .leftJoin(board.member, member)
                .where(containsSearch(searchVal))
                .where(board.delYn.eq("N"))
                .orderBy(board.id.desc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();
        return content;
    }

    private BooleanExpression containsSearch(String searchVal) {
        return searchVal != null ? board.title.contains(searchVal) : null;
    }
}

delYn이 N인 경우에만 나타나도록 where절 추가

3. 결과 화면

profile
개인이 공부한걸 작성하는 블로그입니다..

0개의 댓글