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