Published 2022. 8. 3. 13:33
이전글 보기
https://aamoos.tistory.com/677
목표
- 이번장에서 변경하는 부분은 체크박스 선택해서 삭제시 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">««</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">«</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">»</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">»»</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
다음장에는 글쓰기후 저장을 해보겠습니다.
다음글 보기
https://aamoos.tistory.com/679