티스토리 뷰
1. properties 부분
board.pageSize=10 // 한 페이지당 10개
board.linkSize=2 // 페이지 표시 수 2개
board.uploadPath=c:/java/upload // 파일 저장 경로
2. HTML 부분
<!DOCTYPE html>
<html lang="en" xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>게시판</title>
<!-- CSS 파일 링크 -->
<link rel="stylesheet" th:href="@{/base/css/list.css}">
<!-- jQuery 파일 링크 -->
<script th:src="@{/base/jq/jquery-3.7.1.min.js}"></script>
<!-- JavaScript 파일 링크 -->
<script th:src="@{/base/js/listJS.js}"></script>
<!-- 페이지 이동 스크립트 -->
<script>
function pagingFormSubmit(currentPage) {
$('#page').val(currentPage);
$('#pagingForm').submit();
}
</script>
</head>
<body>
<h1>[ 게시판 ]</h1>
<!-- 글 목록 출력 영역 -->
<table>
<tr>
<!-- 전체 글 수 출력 -->
<td>
전체글 수: <span th:text="${boardPage.totalElements}"></span>
</td>
<!-- 현재 페이지 및 전체 페이지 수 출력 -->
<td colspan="2">페이지 <span th:text="${page}"></span> of <span th:text="${boardPage.getTotalPages()}"></span></td>
<!-- 글쓰기 링크, 인증된 사용자만 볼 수 있음 -->
<td><a sec:authorize="isAuthenticated()" th:href="@{/board/write}">글쓰기</a></td>
<!-- 홈으로 돌아가는 링크 -->
<td><a th:href="@{/}">HOME</a></td>
</tr>
<tr>
<!-- 테이블 헤더 -->
<th>번호</th>
<th style="width:300px;">제목</th>
<th>작성자</th>
<th>조회수</th>
<th>작성일</th>
</tr>
<!-- 게시글 목록을 반복하며 출력 -->
<tr th:each="board, status : ${boardPage}">
<!-- 게시글 번호 -->
<td th:text="${board.boardNum}" class="center"></td>
<!-- 게시글 제목 및 링크 -->
<td>
<a th:text="${board.title}" th:href="@{/board/read(boardNum=${board.boardNum})}"></a>
</td>
<!-- 작성자 이름 -->
<td th:text="${board.memberName}" class="center"></td>
<!-- 조회수 -->
<td th:text="${board.viewCount}" class="center"></td>
<!-- 작성일 -->
<td th:text="${#temporals.format(board.createDate, 'yy.MM.dd HH:mm')}"></td>
</tr>
</table>
<!-- 페이지 네비게이션 영역 -->
<div class="pagination">
<!-- 앞 뒤로 2페이지 씩 표기하기 -->
<span
th:if="${boardPage.getTotalPages() > 0}"
th:each="counter : ${#numbers.sequence((page - linkSize < 1 ? 1 : page - linkSize),
(page + linkSize > boardPage.getTotalPages() ? boardPage.getTotalPages() : page + linkSize))}">
<!-- 현재 페이지 -->
<th:block th:if="${counter == page}"></th:block>
<a th:text="${counter}" th:href="|javascript:pagingFormSubmit(${counter})|"></a>
<th:block th:if="${counter == page}"></th:block>
</span>
<br><br>
<!-- 검색 폼 -->
<form id="pagingForm" method="get" th:action="@{/board/list}">
<!-- 현재 페이지를 담는 히든 필드 -->
<input type="hidden" name="page" id="page" />
<!-- 검색 타입 선택 -->
<select id="type" name="searchType">
<option value="title" th:selected="${searchType == 'title'}">제목</option>
<option value="contents" th:selected="${searchType == 'contents'}">본문</option>
<option value="memberName" th:selected="${searchType == 'memberName'}">작성자</option>
</select>
<!-- 검색어 입력 필드 -->
<input type="text" name="searchWord" th:value="${searchWord}">
<!-- 검색 버튼 -->
<input type="submit" onclick="pagingFormSubmit(1)" value="검색">
</form>
</div>
</body>
</html>
3. Controller 부분
package net.datasa.web5.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.datasa.web5.domain.dto.BoardDTO;
import net.datasa.web5.security.AuthenticatedUser;
import net.datasa.web5.service.BoardService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
@Slf4j
@RequestMapping("board")
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
/* 게시물을 10개씩 보여주기
2번이상 쓰는 상수는 선언해놓는게 좋다
*/
// int pageSize = 10; → 타임리프와 @Value 안쓰는 경우
@Value("${board.pageSize}") // 타임리프를 통하여 괄호안에 한 페이지당 게시글 갯수가 정해짐. 위에 변수를 직접 선언한 것과 같은 효과
int pageSize;
@Value("${board.linkSize}") // 페이지 이동 링크 수
int linkSize;
@Value("${board.uploadPath}") // 첨부파일 저장 경로
String uploadPath;
/**
* 글 목록 보기
* @param model
* @return 글 목록 출력 HTML 파일
*/
// 게시글을 조회하는 기능 + 게시글 검색 기능
@GetMapping("list")
public String list(Model model,
@RequestParam(name="page", defaultValue = "1") int page,
@RequestParam(name="searchType", defaultValue = "") String searchType,
@RequestParam(name="searchWord", defaultValue = "") String searchWord) {
log.debug("properties 값 : pageSize={} linkSize={} uploadPath={}", pageSize, linkSize, uploadPath);
log.debug("요청 파라미터 : page={} searchType={} searchWord={}", page, searchType, searchWord);
// 현재페이지, 페이지당 글수, 검색대상, 검색어
Page<BoardDTO> boardPage = boardService.getList(page, pageSize, searchType, searchWord);
log.debug("목록정보 getContent() : {}", boardPage.getContent());
log.debug("현재페이지 getNumber() : {}", boardPage.getNumber());
log.debug("전체 글 개수 getTotalElements() : {}", boardPage.getTotalElements());
log.debug("전체 페이지수 getTotalPages() : {}", boardPage.getTotalPages());
log.debug("한 페이지당 글 수 getSize() : {}", boardPage.getSize());
log.debug("이전페이지 존재 여부 hasPrevious() : {}", boardPage.hasPrevious());
log.debug("다음페이지 존재 여부 hasNext() : {}", boardPage.hasNext());
// 검색부분 보여줄 것들
model.addAttribute("page", page); // 현재 페이지
model.addAttribute("linkSize", linkSize); // 페이지 이동링크 수
model.addAttribute("searchType", searchType); // 검색기준
model.addAttribute("searchWord", searchWord); // 검색어
// 글 목록부분 보여줄 것
model.addAttribute("boardPage", boardPage); // 출력할 글정보
return "boardView/list";
}
/**
* 글쓰기 폼으로 이동
* @return 글쓰기폼을 출력하는 HTML파일
*/
@GetMapping("write")
public String write() {
return "boardView/writeForm";
}
/**
* 게시글 저장
*/
@PostMapping("write")
public String writesave(@ModelAttribute BoardDTO boardDTO, @AuthenticationPrincipal AuthenticatedUser user) {
boardDTO.setMemberId(user.getId());
log.debug("저장할 정보 {}", boardDTO);
boardService.write(boardDTO);
return "redirect:/";
}
}
더보기
[글 목록 요청 예]
/board/list : 게시글 전체 목록
/board/list?searchType=title&searchWord=테스트 : 전체 내용중 제목에 '테스트'가 들어간 게시글 목록
/board/list?page=5 : 게시글 5페이지
/board/list?searchType=title&searchWord=&page=3 : 전체 내용 중 3페이지
/board/list?searchType=title&searchWord=테스트&page=1 : 제목에 '테스트'가 들어간 게시글 중 1페이지
[글 본문 읽기]
/board/read?boardNum=1
4. Service 부분
package net.datasa.web5.service;
import jakarta.persistence.EntityNotFoundException;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.datasa.web5.domain.dto.BoardDTO;
import net.datasa.web5.domain.entity.BoardEntity;
import net.datasa.web5.domain.entity.MemberEntity;
import net.datasa.web5.repository.BoardRepository;
import net.datasa.web5.repository.MemberRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.ArrayList;
import java.util.List;
@Service
@Transactional
@RequiredArgsConstructor
@Slf4j
public class BoardService {
private final BoardRepository boardRepository;
private final MemberRepository memberRepository;
/**
* 게시판 글 저장
*
* @param dto 저장할 글 정보
*/
public void write(BoardDTO dto) {
log.info("Writing a new board entry: {}", dto);
// 글 작성자 정보 조회
MemberEntity memberEntity = memberRepository.findById(dto.getMemberId())
.orElseThrow(() -> new EntityNotFoundException("Member not found"));
BoardEntity entity = BoardEntity.builder()
.member(memberEntity)
.title(dto.getTitle())
.contents(dto.getContents())
.originalName(dto.getOriginalName())
.fileName(dto.getFileName())
.build();
boardRepository.save(entity);
log.debug("저장되는 엔티티 : {}", entity);
// todo : 첨부파일 처리할 것
}
/**
* 게시글 목록 조회
*
* @param page 현재 페이지
* @param pageSize 한 페이지당 글 수
* @param searchType 검색 대상 (제목 : titel, 내용 : contents, 작성자 : memberName)
* @param searchWord 검색어
* @return 글정보가 한 페이지 분량 저장된 Page객체
*/
public Page<BoardDTO> getList(int page, int pageSize, String searchType, String searchWord) {
// Pageable : 한페이지를 끊어오기 위한 정보
// Sort.Direction.DESC, "boardNum" : 정렬 객체
// Spring에서는 첫 페이지가 '0'이므로, 가독성을 위해 page-1해줘야 '1' 페이지부터 시작
// p : 한 페이지당 10개씩 boardNum기준으로 내림차순하여 덩어리로 만들어 놓음
Pageable p = PageRequest.of(page - 1, pageSize, Sort.Direction.DESC, "boardNum");
// Pageable 객체로 정의된 변수 p에 담긴 정보를 findAll(오버로딩)로 가져오면
// Page<BoardEntity>타입(객체)의 변수 entityPage에 담음
Page<BoardEntity> entityPage = null;
if (searchType.equals("title")) {
entityPage = boardRepository.findByTitleContaining(searchWord, p);
} else if (searchType.equals("contents")) {
entityPage = boardRepository.findByContentsContaining(searchWord, p);
} else if (searchType.equals("memberName")) {
entityPage = boardRepository.findByMember_MemberName(searchWord, p);
} else {
entityPage = boardRepository.findAll(p);
}
// 리턴받은 entityPage의 정보를 바탕으로 출력할 정보만 가져갈 것이다.
// 그래서 entityPage에 담겨있는 정보를 바탕으로 BoardDTO가 저장된 Page객체를 생성해서 리턴
// → BoardDTO가 여러개 들어있는 dtoPage 완성
// map 메서드 : 안에 있는 요소들을 순회하는 반복문 역할
Page<BoardDTO> dtoPage = entityPage.map(this::convertToDTO);
return dtoPage;
}
/**
* BoardEntity 객체를 전달받아 BoardDTO객체로 변환하여 리턴
* @param entity DB에서 읽은 정보를 담은 엔티티 객체
* @return 출력용 정보를 담은 DTO객체
*/
private BoardDTO convertToDTO(BoardEntity entity) {
return BoardDTO.builder() // 다른 곳에서 쓰기 위해 만든 것
.boardNum(entity.getBoardNum())
.memberId(entity.getMember().getMemberId())
.memberName(entity.getMember().getMemberName())
.title(entity.getTitle())
.contents(entity.getContents())
.viewCount(entity.getViewCount())
.likeCount(entity.getLikeCount())
.dislikeCount(entity.getDislikeCount())
.originalName(entity.getOriginalName())
.fileName(entity.getFileName())
.createDate(entity.getCreateDate())
.updateDate(entity.getUpdateDate())
.build();
}
}
5. Repository 부분
package net.datasa.web5.repository;
import net.datasa.web5.domain.entity.BoardEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface BoardRepository extends JpaRepository<BoardEntity, Integer> {
// 메서드 이름에 find를 붙이면 DB의 select 구문 기능을 레포지토리(JPA)가 알아서 만들어줌
// → 결국, DB에서 원하는 조건을 메서드 이름에 다 집어넣으면 JPA가 자동으로 생성
// List<BoardEntity> findByTitleContaining(String s, Sort sort);
// JPA에 정의되어 있는 쿼리 메서드 이름들 찾는 법
// Spring.io 접속→ 화면 상단 Projects → Spring Data → Spring Data JPA → LEARN 블럭 →
// 버전(3.3.2)에 맞는 Reference Doc. 클릭 → 회면 왼쪽 JPA 클릭 → JPA Query Methods
// 전달된 문자열을 제목에서 검색한 후 지정한 한페이지 분량 리턴
Page<BoardEntity> findByTitleContaining(String s, Pageable pageable);
// 전달된 문자열을 작성자 이름에서 검색 후 지정한 페이지 분량 리턴
// Member : entity
// MemberName : Member 엔티티 안에 있는 작성자 이름
// Member와 MemberName사이에 '_' 필요
Page<BoardEntity> findByMember_MemberName(String s, Pageable pageable);
// 전달된 문자열을 내용에서 검색 후 지정한 페이지 분량 리턴
Page<BoardEntity> findByContentsContaining(String s, Pageable pageable);
}
'SCIT > 8월' 카테고리의 다른 글
8/7 [게시판] 게시글 첨부파일 다운로드 (0) | 2024.08.07 |
---|---|
8.6[게시판] 파일 첨부 및 삭제 기능 추가 (0) | 2024.08.06 |
8.5 [게시판] 댓글 저장 및 삭제 기능 추가 (0) | 2024.08.05 |
8/5 [프로젝트] 프로젝트 이름 바꾸기 (0) | 2024.08.05 |
8.2 [게시판] 게시글 리플달기 (0) | 2024.08.02 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 백준
- 가계부만들기
- springboot
- 조건문
- javascript
- Spring
- Intellij idea
- data science academy
- MySQL
- JPA
- Linux
- Modal
- 오븐시계
- 반복문
- Spring boot
- if문
- java
- html
- ajax
- DB
- 2739번
- css
- setting
- 2480
- backjoon
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
글 보관함