정렬

  • Specification 개념을 배웠다면 Sort 배우기 쉬울 것.
  • 조회 + ORDER BY
  • 사용자 편의 기능

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

페이징

  • 한 페이지 내에서 보여주는 데이터의 양 조절
  • 사용자 편의 기능
  • JPA의 Pageable 인터페이스 사용해서 페이징 구현
  • 기존 Java만 사용해서 구현하는 경우 Pasing 객체를 만들어서 사용했었다.

구현 순서
1. Controller에서 Pageable 매개변수로 설정
2. PageRequest를 통해 Pageable 객체 생성(Calendar.instanceOf 썼던 것 처럼!)
3. Repository에서 findAll에 매개변수로 Pageable 추가
4. Service에서 전달 받은 데이터 파싱

PageRequest

  • JPA에서 제공하는 Pageable의 구현체
  • 정렬하는 기능 Sort를 썼다면 Sort를 없애고 Pageble의 정렬 조건을 사용해야한다.
  • 예시 코드
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;
		
	}
}
  1. BoardController의 코드 추가
//	게시판 들어갈 때 + 검색
	@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>
  1. list.html의 section 아래 코드 추가
  • 페이징 이동시 검색어 및 정렬을 유지하기 위해 키=${밸류} 형태로 작성
<!-- 페이징 위치! -->
		<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})}"
					>
					&laquo;
				</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})}"
					>
					&raquo;
				</a>
			</div>
		</div>
  1. 페이징바 구성하는 PageDto 클래스 생성
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;
		}
		
	}
	
	
}
  1. BoardController 요청 데이터 변경
	@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";
  }
  1. BoardService 코드 수정
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);
}
  1. board/list.html에 페이징 관련된 코드 수정
		<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})}"
					>
					&laquo;
				</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})}"
					>
					&raquo;
				</a>
			</div>
		</div>

게시글 단일 조회

  1. JSTL과 마찬가지로 thymeleaf에도 status가 있음
<tr th:if="${!#lists.isEmpty(boardList)}"
	th:each="board, boardStatus : ${boardList}">
	<td th:text="${boardStatus.count}">번호</td>
  ...
</tr>

@PathVariable

  • {idBoardNo01} : URL에서 변하는 값 PK
  • @PathVariable("idBoardNo01") Long idBoardNo01 : {idBoardNo01}값을 id변수에 할당
//	상세 페이지 이동하기!
	@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>
  1. BoardController에 요청을 받을 수 있는 메소드 생성.
//	상세 페이지 이동하기!
	@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. board/detail.html 작성

게시글 수정(1)

  1. /board/detail.html에 수정 버튼 생성
<a th:href="@{/board/{id}/update(id=${board.boardNo})}">수정</a>
  1. BoardController에 화면 전환 메소드 생성
//	수정화면 전환 코드
	@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";
	}
  1. /board/update.html 파일 생성

게시글 수정(2)

  1. board/update.html에 태그 추가
<input type="hidden" name="board_writer" th:value="${board.member.memberNo}">
  1. 수정 요청 로직(fetch) 작성
	<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>
  1. BoardController에 메소드 생성
//	수정완료
	@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;
	}
  1. BoardService에 수정 메소드 생성
	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;
	}

게시글 삭제

화면 전환

  1. /board/detail.html에 삭제 버튼 생성
<a th:onclick="|javascript:boardDelete('${board.boardNo}')|">삭제</a>			
  1. 삭제 요청 로직(fetch) 작성
<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>
  1. BoardController에 삭제 메소드 생성
//	삭제
	@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;
	}
  1. Service에 pk기준으로 엔티티 조회
  2. 조회된 엔티티가 존재하는 경우 해당 엔티티 삭제
		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;
		}
profile
함께 공부해요!

0개의 댓글