list.html에 정렬을 위한 코드 추가
<!-- 정렬을 위한 코드 -->
<div class="sort">
<select id="order_type" name="order_type">
<option value="1" th:selected="${searchDto.order_type == 1}">최신순</option>
<option value="2" th:selected="${searchDto.order_type == 2}">오래된순</option>
</select>
</div>
SearchDto에 정렬 정보 추가
필드에 private int order_type; 추가
BoardService에서 Sort 객체 구성
Order_type에 따른 오름차순/내림차순 정렬
// 정렬 관련 코드, Pageable을 사용할 때는 주석 처리 한다.
Sort sort = Sort.by("regDate").descending();
if(searchDto.getOrder_type() == 2) {
sort = Sort.by("regDate").ascending();
}
구현 순서
1. Controller에서 Pageable 매개변수로 설정
2. PageRequest를 통해 Pageable 객체 생성(Calendar.instanceOf 썼던 것 처럼!)
3. Repository에서 findAll에 매개변수로 Pageable 추가
4. Service에서 전달 받은 데이터 파싱
PageRequest
Pageable pageable
= PageRequest.of(페이지 번호, 페이지당 데이터 개수[, 정렬조건]);
실제 구현 순서
1. BoardRepository에서 findAll의 매개변수로 Pageable 추가한다. 그리고 반환 형태를 Page Board(=Entity 형태)로 자료형을 변경한다.
2. BoardService의 코드 추가
public Page<Board> selectBoardAll(SearchDto searchDto, PageDto pageDto){
// Pageable!!(시작하는 데이터 번호, 한 페이지에 몇개씩 보여줄 것인가, Sorting 관련 코드)
// nowPage - 1 하는 이유. JPA Paging은 0번으로 시작한다고 생각 함. 하지만 사용자 입장에서는 1번이 시작이다. 그 차이를 메꾸는 작업
Pageable pageable = PageRequest.of(pageDto.getNowPage()-1, pageDto.getNumPerPage(), Sort.by("regDate").descending());
if(searchDto.getOrder_type() == 2) {
pageable = PageRequest.of(pageDto.getNowPage()-1, pageDto.getNumPerPage(), Sort.by("regDate").ascending());
}
// 3 방법 + 페이징시 추가 import 받아야한다.
Specification<Board> spec = (root, query, criteriaBuilder) -> null;
if(searchDto.getSearch_type() == 1) {
spec = spec.and(BoardSpecification.boardTitleContains(searchDto.getSearch_text()));
} else if(searchDto.getSearch_type() == 2) {
spec = spec.and(BoardSpecification.boardContentContains(searchDto.getSearch_text()));
} else if(searchDto.getSearch_type() == 3) {
spec = spec.and(BoardSpecification.boardTitleContains(searchDto.getSearch_text()))
.or(BoardSpecification.boardContentContains(searchDto.getSearch_text()));
}
// List<Board> list = repository.findAll(spec, sort);
// 페이징 추가!
Page<Board> list = repository.findAll(spec, pageable);
return list;
}
}
// 게시판 들어갈 때 + 검색
@GetMapping("/board")
// public String selectBoardAll(Model model, SearchDto searchDto) {
// 페이징 관련 파라미터 추가!
// public String selectBoardAll(Model model, SearchDto searchDto, @RequestParam(name="nowPage", defaultValue="1") int nowPage) {
// PageDto사용하자!
public String selectBoardAll(Model model, SearchDto searchDto, PageDto pageDto) {
// PageDto 추가!
if(pageDto.getNowPage() == 0) {
pageDto.setNowPage(1);
}
// 1. DB에서 목록 SELECT
// 페이징 사용!
// Page<Board> resultList = service.selectBoardAll(searchDto);
// 페이징 파라미터 추가!
Page<Board> resultList = service.selectBoardAll(searchDto, pageDto);
// 와 아래 부분 코드 생각 해봐야함.
pageDto.setTotalPage(resultList.getTotalPages());
// PageDto 추가했을때 추가로 보내줘야함
model.addAttribute("pageDto", pageDto);
// 2. 목록 Model에 등록
model.addAttribute("boardList", resultList);
model.addAttribute("searchDto", searchDto);
// 3. list.html에 데이터 셋팅
return "board/list";
}
한 페이지의 노출되는 데이터 개수 조절 완료
PagingBar 구현 방법
0. thymeleaf에서 특정 파라미터를 포함하여 get방식으로 요청할 때는 다음과 같은 양식을 사용
<a th:href="@{요청주소(파라미터명=파라미터값)}">
컨텐츠
</a>
<!-- 페이징 위치! -->
<div class="center">
<div class="pagination">
<a th:if="${pageDto.prev}"
th:href="@{/board(nowPage=${pageDto.pageBarStart-1}, search_type=${searchDto.search_type}, search_text=${searchDto.search_text}, order_type=${searchDto.order_type})}"
>
«
</a>
<!-- 이 부분 복잡하니 잘 봐야함 -->
<a th:each="num : ${#numbers.sequence(pageDto.pageBarStart, pageDto.pageBarEnd)}"
th:text="${num}"
th:href="@{/board(nowPage=${num}, search_type=${searchDto.search_type}, search_text=${searchDto.search_text}, order_type=${searchDto.order_type})}"
class = "page-link"
th:classappend="${pageDto.nowPage == num} ? 'active' "
>
<!-- th:classappend 일정 조건이 됐을 때 클래스를 추가해주는 것 -->
번호
</a>
<a th:if="${pageDto.next}"
th:href="@{/board(nowPage=${pageDto.pageBarEnd+1},search_type=${searchDto.search_type},search_text=${searchDto.search_text}, order_type=${searchDto.order_type})}"
>
»
</a>
</div>
</div>
package com.gn.mvc.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor
@AllArgsConstructor
@Getter
// setter는 우리가 커스텀하는 부분이 필요해서 Lombok 안 썼다.
public class PageDto {
// 한 페이지당 보이는 데이터 개수
private int numPerPage = 2;
// 현재 페이지, 전에는 1로 세팅 했었나?
private int nowPage;
// 페이징바
private int pageBarSize = 3;
private int pageBarStart;
private int pageBarEnd;
// 이전 , 다음 여부
private boolean prev = true;
private boolean next = true;
// nowPage 전달 받을 거다
public void setNowPage(int nowPage) {
this.nowPage = nowPage;
}
private int totalPage;
// totalPage 전달 받을 거다
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
calcPaging();
}
// 우리가 로직 써야하는 부분
private void calcPaging() {
// 페이징바 시작 번호 계산
// 내가 있는 페이지가 6이면 6 - 1 = 5 -> 5 /5 = 1 -> 5 + 1 = 6 시작 번호는 6
pageBarStart = ((nowPage - 1) / pageBarSize) * pageBarSize + 1;
// 페이징바 끝 번호 계산
pageBarEnd = pageBarStart + pageBarSize - 1;
if(pageBarEnd > totalPage) {
pageBarEnd = totalPage;
}
// 이전, 이후 버튼이 보이는지 여부
if(pageBarStart == 1) {
prev = false;
}
if(pageBarEnd >= totalPage) {
next = false;
}
}
}
@GetMapping("/board")
public String selectBoardAll(Model model, SearchDto searchDto, PageDto pageDto) {
if(pageDto.getNowPage() == 0) {
pageDto.setNowPage(1);
}
// 페이징 파라미터 추가!
Page<Board> resultList = service.selectBoardAll(searchDto, pageDto);
// 와 아래 부분 코드 생각 해봐야함.
pageDto.setTotalPage(resultList.getTotalPages());
// PageDto 추가했을때 추가로 보내줘야함
model.addAttribute("pageDto", pageDto);
// 2. 목록 Model에 등록
model.addAttribute("boardList", resultList);
model.addAttribute("searchDto", searchDto);
// 3. list.html에 데이터 셋팅
return "board/list";
}
public Page<Board> selectBoardAll(SearchDto searchDto,PageDto pageDto){
Specification<Board> spec = (root,query,criteriaBuilder) -> null;
// Pageable!!(시작하는 데이터 번호, 한 페이지에 몇개씩 보여줄 것인가, Sorting 관련 코드)
// nowPage - 1 하는 이유. JPA Paging은 0번으로 시작한다고 생각 함. 하지만 사용자 입장에서는 1번이 시작이다. 그 차이를 메꾸는 작업
Pageable pageable = PageRequest.of(pageDto.getNowPage()-1, pageDto.getNumPerPage(), Sort.by("regDate").descending());
// 3 방법 + 페이징시 추가 import 받아야한다.
Specification<Board> spec = (root, query, criteriaBuilder) -> null;
if(searchDto.getOrder_type() == 2) {
pageable = PageRequest.of(pageDto.getNowPage()-1, pageDto.getNumPerPage(), Sort.by("regDate").ascending());
}
if(searchDto.getSearch_type() == 1) {
spec = spec.and(BoardSpecification.boardTitleContains(searchDto.getSearch_text()));
}else if(searchDto.getSearch_type() == 2) {
spec = spec.and(BoardSpecification.boardContentContains(searchDto.getSearch_text()));
} else if(searchDto.getSearch_type() == 3) {
spec = spec.and(BoardSpecification.boardTitleContains(searchDto.getSearch_text()))
.or(BoardSpecification.boardContentContains(searchDto.getSearch_text()));
}
return boardRepository.findAll(spec,pageable);
}
<div class="center">
<div class="pagination">
<!-- paging 만들기 전 임시 코드 -->
<!-- <a th:href="@{/board(nowPage=1)}">1</a>
<a th:href="@{/board(nowPage=2)}">2</a>
<a th:href="@{/board(nowPage=3)}">3</a> -->
<a th:if="${pageDto.prev}"
th:href="@{/board(nowPage=${pageDto.pageBarStart-1}, search_type=${searchDto.search_type}, search_text=${searchDto.search_text}, order_type=${searchDto.order_type})}"
>
«
</a>
<!-- 이 부분 복잡하니 잘 봐야함 -->
<a th:each="num : ${#numbers.sequence(pageDto.pageBarStart, pageDto.pageBarEnd)}"
th:text="${num}"
th:href="@{/board(nowPage=${num}, search_type=${searchDto.search_type}, search_text=${searchDto.search_text}, order_type=${searchDto.order_type})}"
class = "page-link"
th:classappend="${pageDto.nowPage == num} ? 'active' "
>
<!-- th:classappend 일정 조건이 됐을 때 클래스를 추가해주는 것 -->
번호
</a>
<a th:if="${pageDto.next}"
th:href="@{/board(nowPage=${pageDto.pageBarEnd+1},search_type=${searchDto.search_type},search_text=${searchDto.search_text}, order_type=${searchDto.order_type})}"
>
»
</a>
</div>
</div>
<tr th:if="${!#lists.isEmpty(boardList)}"
th:each="board, boardStatus : ${boardList}">
<td th:text="${boardStatus.count}">번호</td>
...
</tr>
@PathVariable
// 상세 페이지 이동하기!
@GetMapping("/board/{idBoardNo01}")
public String selectBoardOne(@PathVariable("idBoardNo01") Long idBoardNo01, Model model) {
// logger.info("게시글 단일 조회 : " + idBoardNo01);
Board result = service.selectBoardOne(idBoardNo01);
model.addAttribute("board", result);
return "board/detail";
}
상세 페이지 이동 구현 방법
1. list.html에서 tr에 onClick 이벤트를 작성
<script>
$(function(){
/* tr인 애 중에 exist인 애 < 라고 설정해야 섬세한 방법! 이거 방법은 따로 찾아보자 */
$('.board_list tbody tr').on('click', function(){
const boardNo = $(this).data('board-no');
location.href="/board/"+boardNo;
});
})
</script>
// 상세 페이지 이동하기!
@GetMapping("/board/{idBoardNo01}")
public String selectBoardOne(@PathVariable("idBoardNo01") Long idBoardNo01, Model model) {
// logger.info("게시글 단일 조회 : " + idBoardNo01);
Board result = service.selectBoardOne(idBoardNo01);
model.addAttribute("board", result);
return "board/detail";
}
<a th:href="@{/board/{id}/update(id=${board.boardNo})}">수정</a>
// 수정화면 전환 코드
@GetMapping("/board/{id}/update")
public String updateBoardView(@PathVariable("id") Long id, Model model) {
Board board = service.selectBoardOne(id);
model.addAttribute("board", board);
return "board/update";
}
<input type="hidden" name="board_writer" th:value="${board.member.memberNo}">
<script>
const form = document.update_board_form;
form.addEventListener('submit', function(e){
e.preventDefault();
let vali_check = false;
let vali_text='';
if(form.board_title.value == ''){
vali_text += '제목을 입력하세요.';
form.board_title.focus();
} else if(form.board_content.value == ''){
vali_text += '내용을 입력하세요.';
form.board_content.focus();
} else{
vali_check = true;
}
if(vali_check == false){
alert(vali_text);
} else{
const payload = new FormData(form);
fetch('/board/'+form.board_no.value+"/update",{
method : 'post',
body : payload
})
.then(response => response.json())
.then(data => {
alert(data.res_msg);
if(data.res_code == 200){
location.href = '/board';
}
});
}
});
</script>
// 수정완료
@PostMapping("/board/{id}/update")
@ResponseBody
public Map<String, String> updateBoardApi(BoardDto param) {
// 1. BoardDto 출력(전달 확인)ok
// 2. BoardService -> BoardRepository 게시글 수정
// 3. 수정 결과 Entity가 null이 아니면 성공 / 그 외에는 실패
// System.out.println("BoardDto check : " + param);
Map<String, String> resultMap = new HashMap<String, String>();
resultMap.put("res_code", "500");
resultMap.put("res_msg", "수정 실패했습니다.");
// Board는 Entity다! 세심하게 하려면 Dto로 해야한다.
Board result = service.updateBoard(param);
// System.out.println("result 확인 : " + result);
if(result != null) {
resultMap.put("res_code", "200");
resultMap.put("res_msg", "수정 완료했습니다.");
}
// System.out.println("result : " + result);
return resultMap;
}
public Board updateBoard(BoardDto param) {
Board result = null;
// 1. @Id를 쓴 필드를 기준으로 타겟 조회
Board target = repository.findById(param.getBoard_no()).orElse(null);
// 2. 타겟이 존재하는 경우 업데이트
if(target != null) {
result = repository.save(param.toEntity());
}
return result;
}
화면 전환
<a th:onclick="|javascript:boardDelete('${board.boardNo}')|">삭제</a>
<script>
const boardDelete = function(boardNo){
console.log(boardNo);
// 1. confirm 함수 사용 -> 게시글 삭제 여부 확인 ok
// 2. 동의 -> fetch 사용 삭제
// (1) url : /board/1\
// (2) method : 'delete'
// (3) 응답 데이터 출력 -> 수정 동일
// 3. 컨트롤러 요청 받는 메소드
const check = confirm('삭제하시겠습니까?');
if(check){
fetch('/board/'+boardNo,{
method : 'delete'
})
.then(response => response.json())
.then(data => {
alert(data.res_msg);
if(data.res_code == 200){
location.href = '/board';
}
});
} else{
alert('삭제 취소');
}
}
</script>
// 삭제
@DeleteMapping("/board/{id}")
@ResponseBody
public Map<String, String> deleteBoardApi(@PathVariable("id") Long id){
Map<String, String> resultMap = new HashMap<String, String>();
resultMap.put("res_code", "500");
resultMap.put("res_msg", "삭제 실패했습니다.");
int result = service.deleteBoard(id);
if(result == 1) {
resultMap.put("res_code", "200");
resultMap.put("res_msg", "삭제 성공했습니다.");
}
return resultMap;
}
public int deleteBoard(Long id) {
// repository.deleteById(id);
// return repository.findById(id).orElse(null);
// 위는 내가 짠 코드인데 누군가가 이미 삭제했다면 오류 뜰 가능성이 높음
// 아래처럼 해야 예외 상황 막고 효율적인 코드가 된다.
int result = 0;
try {
Board target = repository.findById(id).orElse(null);
if(target != null) {
repository.deleteById(id);
}
result = 1;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}