1) SpringBoot
1-1) 복습 예제
1-1-1) 게시판 글 목록 보기 기능 구현
1) SpringBoot
1-1) 복습 예제
1-1-1) 게시판 글 목록 보기 기능 구현
(1) [src/main/resources] - [templates] 안에 home.html 파일 생성 후 아래와 같이 작성
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>web5</title>
</head>
<body>
<h1>[ web5 ]</h1>
<p sec:authorize="isAuthenticated()">
<span th:text="${#authentication.name}"></span>
님 환영합니다~~!
</p>
<div sec:authorize="not isAuthenticated()">
<p>
<a th:href="@{/member/joinForm}">회원가입</a>
</p>
<p>
<a th:href="@{/member/loginForm}">로그인</a>
</p>
</div>
<div sec:authorize="isAuthenticated()">
<p>
<a th:href="@{/member/logout}">로그아웃</a>
</p>
<p>
<a th:href="@{/member/info}">개인정보 수정</a>
</p>
</div>
<p>
<a th:href="@{/board/list}">게시판</a>
</p>
</body>
</html>
(2) [src/main/java] - [net.datasa.web5.domain.entity] 안에 BoardEntity.java 파일 생성 후 아래와 같이 작성
package net.datasa.web5.domain.entity;
import java.time.LocalDateTime;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name="web5_board")
@EntityListeners(AuditingEntityListener.class)
public class BoardEntity {
/*
board_num int auto_increment primary key, -- 게시글 일련번호
member_id varchar(30), -- 작성자 ID(외래키 : web5_member 테이블 참조)
title varchar(1000) not null, -- 글제목
contents text not null, -- 글내용
view_count int default 0, -- 조회수
like_count int default 0, -- 추천수
original_name varchar(300), -- 첨부파일 원래 이름
file_name varchar(100), -- 첨부파일 저장된 이름
create_date timestamp default current_timestamp, -- 작성 시간
update_date timestamp default current_timestamp
on update current_timestamp, -- 수정 시간
constraint foreign key (member_id)
references web5_member(member_id) on delete set null
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="board_num")
private Integer boardNum;
// 작성자 아이디
// @Column(name="member_id", length = 30)
// private String memberId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="member_id", referencedColumnName = "member_id")
MemberEntity member;
// 글 제목
@Column(name="title", nullable = false, length = 1000)
private String title;
// 글 내용
@Column(name="contents", nullable = false, columnDefinition = "text")
private String contents;
// 조회수
@Column(name="view_count", columnDefinition = "int default 0")
private Integer viewCount;
// 추천수
@Column(name="like_count", columnDefinition = "int default 0")
private Integer likeCount;
// 첨부파일 원래 이름
@Column(name="original_name", length = 300)
private String originalName;
// 첨부파일 저장된 이름
@Column(name="file_name", length = 100)
private String fileName;
// 작성 시간
@CreatedDate
@Column(name="create_date", columnDefinition = "timestamp default current_timestamp")
private LocalDateTime createDate;
// 수정 시간
@LastModifiedDate
@Column(name="update_date", columnDefinition = "timestamp default current_timestamp on update current_timestamp")
private LocalDateTime updateDate;
}
(3) [src/main/java] - [net.datasa.web5.domain.dto] 안에 BoardDTO.java 파일 생성 후 아래와 같이 작성
package net.datasa.web5.domain.dto;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 게시글 정보 DTO
* */
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BoardDTO {
// Wrapper Class(Integer 등) : 기본 자료형을 포장해서 객체로 만들어 줌
private Integer boardNum; // 게시글 일련번호
private String memberId; // 작성자 아이디(외래키)
private String memberName; // 작성자 이름(외래키를 통해 가지고 온 내용 중 이름)
private String title; // 글 제목
private String contents; // 글 내용
private Integer viewCount; // 조회수
private Integer likeCount; // 추천수
private String originalName; // 첨부파일 원래 이름
private String fileName; // 첨부파일 저장된 이름
private LocalDateTime createDate ; // 작성 시간
private LocalDateTime updateDate; // 수정 시간
}
(4) [src/main/java] - [net.datasa.web5.repository] 안에 BoardRepository.java 파일 생성(반드시 Interface로 생성할 것!!) 후 아래와 같이 작성
package net.datasa.web5.repository;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import net.datasa.web5.domain.entity.BoardEntity;
@Repository
public interface BoardRepository extends JpaRepository<BoardEntity, Integer> {
List<BoardEntity> findByTitleContaining(String s, Sort sort);
List<BoardEntity> findTop3OByTitleContainingOrderByBoardNum(String s);
}
(5) [src/main/java] - [net.datasa.web5.controller] 안에 BoardController.java 파일 생성 후 아래와 같이 작성
package net.datasa.web5.controller;
import java.util.List;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
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;
@Slf4j
@RequestMapping("board")
@RequiredArgsConstructor
@Controller
public class BoardController {
private final BoardService boardService;
// 메인화면에서 "board/list" 경로를 클릭했을 때 처리하는 메소드
// templates/boardView/list.html 파일로 포워딩
// 로그인 안 한 상태에서 해당 페이지의 "게시판"이라는 제목이 보여야 함
/**
* 글 목록 보기
* @return 글 목록 출력 HTML 파일
* */
@GetMapping("list")
public String list(Model model,
@RequestParam(name="searchWord", defaultValue = "") String searchWord) {
// 서비스에서 전체 글 목록 전달받음
List<BoardDTO> boardList = boardService.getList(searchWord);
log.debug("전달된 글 목록 : {}", boardList);
// 글 목록을 모델에 저장하고 HTML로 포워딩하여 출력
model.addAttribute("boardList", boardList);
return "boardView/list";
}
@GetMapping("write")
public String write() {
return "boardView/writeForm";
}
@PostMapping("write")
public String join(@AuthenticationPrincipal AuthenticatedUser user,
@ModelAttribute BoardDTO boardDTO) {
log.debug("전달된 글정보 : {}", boardDTO);
boardDTO.setMemberId(user.getUsername());
log.debug("로그인한 아이디 추가한 글정보 : {}", boardDTO);
// 서비스로 전달하여 저장
boardService.saveWrite(boardDTO);
return "redirect:list";
}
}
(6) [src/main/java] - [net.datasa.web5.service] 안에 BoardService.java 파일 생성 후 아래와 같이 작성
package net.datasa.web5.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.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;
/**
* 게시판 관련 서비스
* */
@Slf4j
@RequiredArgsConstructor
@Service
// @Transactional : Commit, Rollback을 감지하는 역할을 하는 Annotation
@Transactional
public class BoardService {
private final BoardRepository boardRepository;
private final MemberRepository memberRepository;
/**
* 게시판 글 저장
* @param boardDTO 게시판 글 정보
* */
public void saveWrite(BoardDTO boardDTO) {
MemberEntity memberEntity = memberRepository.findById(boardDTO.getMemberId())
.orElseThrow(() -> new EntityNotFoundException("아이디가 없습니다."));
BoardEntity entity = BoardEntity.builder()
// DTO로 전달받은 값들을 Entity에 세팅
// .memberId(boardDTO.getMemberId())
.member(memberEntity)
.title(boardDTO.getTitle())
.contents(boardDTO.getContents())
// 기타 추가 데이터를 Entity에 세팅
.viewCount(0)
.likeCount(0)
.build();
log.debug("저장되는 entity : {}", entity);
// DB에 저장
boardRepository.save(entity);
}
/**
* 게시판의 글 목록 조회
* @return 글 목록 정보
* */
public List<BoardDTO> getList(String searchWord) {
// BoardRepository의 메소드를 호출하여 게시판의 모든 글 정보를 조회
// Entity의 개수만큼 반복하면서 Entity의 값을 BoardDTO 객체를 생성하여 저장
// 생성된 BoardDTO 객체를 ArrayList에 저장
// 최종 완성된 ArrayList 객체를 리턴함
// Sort : SQL의 order by 구문을 만들어주는 역할
Sort sort = Sort.by(Sort.Direction.DESC, "boardNum");
List<BoardEntity> entityList = boardRepository.findByTitleContaining(searchWord, sort);
// List<BoardEntity> entityList = boardRepository.findTop3ByTitleContainingOrderByBoardNum(searchWord);
// List<BoardEntity> entityList = boardRepository.findAll(sort);
List<BoardDTO> boardDTOList = new ArrayList<>();
// 반복문으로 Entity 객체를 DTO로 변환해서 ArrayList에 저장
for (BoardEntity entity : entityList) {
BoardDTO dto = BoardDTO.builder()
.boardNum(entity.getBoardNum())
.memberId(entity.getMember().getMemberId())
.memberName(entity.getMember().getMemberName())
.title(entity.getTitle())
.viewCount(entity.getViewCount())
.createDate(entity.getCreateDate())
.updateDate(entity.getUpdateDate())
.build();
boardDTOList.add(dto);
}
return boardDTOList;
}
}
(7) [src/main/resources] - [templates.boardView] 안에 list.html 파일 생성 후 아래와 같이 작성
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>게시판</title>
<style>
#listArea {
width: 1000px;
margin: 0 auto;
text-align: center;
}
h1 {
text-align: center;
}
#menu {
display: flex;
justify-content: space-between;
font-weight: bold;
}
#menu > #link {
display: inline-block;
margin-left: 830px;
}
#menu > #link > a {
text-decoration-line: none;
color: blue;
}
#menu > #link > #home {
display: inline-block;
margin-left: 20px;
}
table, th, tr, td {
border: 2px solid black;
border-collapse: collapse;
padding: 10px;
margin: 20px 0 15px 0;
text-align: center;
}
th {
background-color: grey;
color: white;
}
td {
width: 100px;
}
.title {
width: 300px;
}
.date {
width: 130px;
}
</style>
<script th:src="@{/js/jquery-3.7.1.min.js}"></script>
<script></script>
</head>
<body>
<div id="listArea">
<h1>[ 게시판 ]</h1>
<div id="menu">
<span th:each="board, num : ${boardList}" id="boardTotalNum">
<th:block th:if="${num.last}">
<span>전체 </span>
<span th:text="${num.size}"></span>
</th:block>
</span>
<span id="link">
<a th:href="@{/board/write}" id="write">글쓰기</a>
<a th:href="@{/}" id="home">Home</a>
</span>
</div>
<div id="list">
<table>
<tr>
<th>번호</th>
<th class="title">제목</th>
<th>작성자 ID</th>
<th>작성자명</th>
<th>조회수</th>
<th class="date">작성일</th>
<th class="date">수정일</th>
</tr>
<tr th:each="board, n : ${boardList}">
<td th:text="${board.boardNum}"></td>
<td th:text="${board.title}" class="title"></td>
<td th:text="${board.memberId}"></td>
<td th:text="${board.memberName}"></td>
<td th:text="${board.viewCount}"></td>
<!-- LocalDateTime 타입으로 가지고 온 값은 아래와 같이 "temporals.format()"을 써서 형식을 지정해야 함 -->
<td th:text="${#temporals.format(board.createDate, 'yyyy-MM-dd')}" class="date"></td>
<td th:text="${#temporals.format(board.updateDate, 'yyyy-MM-dd')}" class="date"></td>
</tr>
</table>
</div>
<div id="searchArea">
<form action="list" method="get" >
<input type="text" name="searchWord">
<input type="submit" value="검색">
</form>
</div>
</div>
</body>
</html>
(8) 결과 화면
첫 접속 화면 : 메인 페이지, 게시판 글 목록 페이지는 로그인 안해도 볼 수 있음(단, 글쓰기를 하려면 로그인해야 함)
게시판 글 목록 화면
- 글 검색 기능 : 검색한 키워드를 포함하고 있는 제목을 가진 글만 목록 화면에 출력해줌
(SQL의 LIKE 연산자 기능과 동일함)
※ 참고(Repository에 DB에 대한 SQL Query문 역할을 수행할 메소드 정의 시 규칙 확인하는 루트)
spring.io(https://spring.io/)
>> [ Projects ] - [ Spring Data ]
>> [ Spring Data JPA ] - [ LEARN ] - [ Reference Doc. ]
>> [ JPA ] - [ JPA Query Methods ]
<!-- LocalDateTime 타입으로 가지고 온 값은 아래와 같이 "temporals.format()"을 써서 형식을 지정해야 함 -->
<td th:text="${#temporals.format(board.createDate, 'yyyy-MM-dd')}" class="date"></td>
<td th:text="${#temporals.format(board.updateDate, 'yyyy-MM-dd')}" class="date"></td>
'SpringBoot' 카테고리의 다른 글
SpringBoot(28) - 복습 예제(글 읽기 기능 업데이트 / 글 수정 및 삭제 / 댓글 작성 및 삭제) (0) | 2024.08.03 |
---|---|
SpringBoot(27) - 복습 예제(게시판 글 목록 페이징 / 검색 선택 / 글 읽기) (0) | 2024.08.01 |
SpringBoot(25) - 복습 예제(글쓰기) (0) | 2024.07.30 |
SpringBoot(24) - 복습 예제(개인정보 수정), 추가 정리사항(AuthenticatedUser, @Transactional) (0) | 2024.07.29 |
SpringBoot(23) - 복습 예제(비밀번호 암호화 / 로그인 / 로그아웃), 추가 정리사항(경로) (0) | 2024.07.26 |