티스토리 뷰
1. html 부분
<!DOCTYPE html>
<html lang="en" xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script th:src="@{/base/jq/jquery-3.7.1.min.js}"></script>
<link rel="stylesheet" href="../base/css/read.css">
</head>
<body>
<div class="container">
<h1 style="text-align: center">[ 글 읽기 ]</h1>
<!-- 글 내용 출력 부분 -->
<div class="post-info">
<div>
<label>글번호: </label>
<span th:text="${board.boardNum}"></span>
</div>
<div>
<label>작성자: </label>
<span th:text="${board.memberId}"></span>
</div>
<div>
<label>작성일: </label>
<span th:text="${#temporals.format(board.createDate, 'yyyy-MM-dd')}"></span>
</div>
<div>
<label>수정일: </label>
<span th:text="${#temporals.format(board.updateDate, 'yyyy-MM-dd')}"></span>
</div>
<div>
<label>조회수: </label>
<span th:text="${board.viewCount}"></span>
</div>
<div>
<label>좋아요: </label>
<span th:text="${board.likeCount}"></span>
</div>
<div>
<label>싫어요: </label>
<span th:text="${board.dislikeCount}"></span>
</div>
</div>
<hr>
<div class="content">
<p th:text="${board.contents}"></p>
</div>
<div class="attachments">
<label>파일첨부:</label>
<a th:text="${board.originalName}"></a>
</div>
<hr>
<!-- 리플 작성 폼(로그인한 사람한테만 보임) -->
<div sec:authorize="isAuthenticated()" class="comment">
<form>
<label>리플 달기: </label>
<input style="width: 500px" type="text">
<button>저장</button>
</form>
<!-- 리플 출력 폼(아무한테나 다 보임) -->
<form>
<p th:each="reply : ${board.replyDTOList}">
<!-- 리플목록 전체를 리스트형태로 담은 다음에 DTO 변환 및 리턴 형태를 거친 후 HTML 출력 -->
<span th:text="${reply.memberName}"></span>
<span th:text="${reply.contents}"></span>
<span th:text="${#temporals.format(reply.createTime, 'yyyy-MM-dd')}"></span>
</p>
</form>
</div>
</div>
</body>
</html>
2. 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.domain.dto.MemberDTO;
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 org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.List;
@Controller
@Slf4j
@RequestMapping("board")
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
// 8. 1(木)
/* 게시물을 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()); // 작성자 ID 설정
log.debug("저장할 정보 {}", boardDTO); // 저장할 정보 로그 출력
boardService.write(boardDTO); // 게시글 저장
return "redirect:/"; // 홈으로 리다이렉트
}
/**
* 선택한 게시글 보기
*
* @param model Model 객체로 뷰에 데이터를 전달
* @param boardNum 게시글 번호
* @return 게시글을 출력하는 HTML 파일 이름
*/
// 선생님 풀이
@GetMapping("read")
public String read(Model model, @RequestParam("boardNum") Integer boardNum) {
try {
BoardDTO boardDTO = boardService.getBoard(boardNum);
model.addAttribute("board", boardDTO); // 모델에 저장
return "boardView/read";
} catch (Exception e) {
e.printStackTrace(); // 문제가 생겼을 때 로그를 확인하기 위해서 남기는 것
return "redirect:list"; // 해당 URL로 리다이렉트
}
}
3. 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.dto.ReplyDTO;
import net.datasa.web5.domain.entity.BoardEntity;
import net.datasa.web5.domain.entity.MemberEntity;
import net.datasa.web5.domain.entity.ReplyEntity;
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 java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service // Spring에서 서비스 계층을 나타내는 어노테이션
@Transactional // 클래스 내의 모든 메서드가 트랜잭셔널(트랜잭션 범위 내에서 실행)하게 함
@RequiredArgsConstructor // Lombok 어노테이션으로, final로 선언된 모든 필드를 파라미터로 갖는 생성자를 자동으로 생성
@Slf4j // Lombok 어노테이션으로, 로깅을 위한 로거를 자동으로 생성
public class BoardService {
private final BoardRepository boardRepository; // 게시판 관련 DB 작업을 처리하는 Repository
private final MemberRepository memberRepository; // 회원 관련 DB 작업을 처리하는 Repository
/**
* 게시판 글 저장
*
* @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 검색 대상 (제목 : title, 내용 : 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 : 한 페이지당 pageSize개씩 boardNum기준으로 내림차순하여 페이지 정보 생성
Pageable p = PageRequest.of(page - 1, pageSize, Sort.Direction.DESC, "boardNum");
// 검색 타입에 따라 적절한 메서드 호출
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를 DTO로 변환하여 반환
Page<BoardDTO> dtoPage = entityPage.map(this::convertToDTO);
return dtoPage;
}
/**
* BoardEntity 객체를 전달받아 BoardDTO객체로 변환하여 리턴
* @param entity DB에서 읽은 정보를 담은 엔티티 객체
* @return 출력용 정보를 담은 DTO객체
*/
private BoardDTO convertToDTO(BoardEntity entity) {
return BoardDTO.builder() // Builder 패턴을 사용하여 BoardDTO 객체 생성
.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();
}
/**
* 게시글 클릭시 해당 게시글 띄우기
* @Param boardNum 글번호
* @return 글 정보 DTO
*/
// 선생님 풀이
/**
* 글 1개 조회
* @param boardNum 글 번호
* @return 글 정보 DTO
*/
public BoardDTO getBoard(Integer boardNum) {
// 글 번호로 BoardEntity 조회 없으면 예외 처리 있으면 BoardDTO로 변환하여 리턴
BoardEntity boardEntity = boardRepository.findById(boardNum).orElseThrow(()
-> new EntityNotFoundException("글이 없습니다."));
// 조회결과 출력 (순환참조)
log.debug("조회된 게시글 정보 : {}", boardEntity);
// 조회된 엔티티(글 정보)를 DTO로 변환하여 반환
BoardDTO boardDTO = convertToDTO(boardEntity);
// 리플 목록을 DTO로 변환하여 추가
List<ReplyDTO> replyDTOList = new ArrayList<>();
for (ReplyEntity replyEntity : boardEntity.getReplyEntityList()) {
ReplyDTO replyDTO = ReplyDTO.builder()
.replyNum(replyEntity.getReplyNum())
.boardNum(replyEntity.getBoard().getBoardNum())
.memberId(replyEntity.getMember().getMemberId())
.memberName(replyEntity.getMember().getMemberName())
.contents(replyEntity.getContents())
.createTime(replyEntity.getCreateDate())
.build();
replyDTOList.add(replyDTO);
}
boardDTO.setReplyDTOList(replyDTOList); // boardDTO에 리플 리스트를 담음
return boardDTO;
}
4. Entity 부분
package net.datasa.web5.domain.entity;
import jakarta.persistence.*; // JPA 관련 어노테이션을 가져옵니다.
import lombok.*; // Lombok 어노테이션을 가져옵니다.
import org.springframework.data.annotation.CreatedDate; // 생성 일자를 나타내기 위한 어노테이션을 가져옵니다.
import org.springframework.data.jpa.domain.support.AuditingEntityListener; // JPA 엔티티 리스너를 가져옵니다.
import java.time.LocalDateTime; // 날짜와 시간을 나타내기 위한 클래스를 가져옵니다.
@Data // Lombok 어노테이션으로, getter, setter, toString, equals, hashCode 메서드를 자동으로 생성
@ToString(exclude = "board") // @Data안에 있는 ToString 기능도 있으나, 여기서는 BoardEntity정보를 다시 불러오는 것을 금하기 위해 사용(순환참조에러 해결)
@Builder // Lombok 어노테이션으로, 빌더 패턴을 사용할 수 있게 합니다.
@NoArgsConstructor // Lombok 어노테이션으로, 매개변수가 없는 생성자를 자동으로 생성
@AllArgsConstructor // Lombok 어노테이션으로, 모든 필드를 매개변수로 가지는 생성자를 자동으로 생성
@Entity // JPA 어노테이션으로, 이 클래스가 엔티티임을 나타냄
@Table(name = "web5_reply") // JPA 어노테이션으로, 엔티티와 매핑되는 테이블의 이름을 지정
@EntityListeners(AuditingEntityListener.class) // JPA 어노테이션으로, 엔티티 리스너를 지정하여 생성 및 수정 일자를 자동으로 관리
public class ReplyEntity {
@Id // JPA 어노테이션으로, 기본 키(primary key) 필드를 나타냅니다.
@GeneratedValue(strategy = GenerationType.IDENTITY) // JPA 어노테이션으로, 기본 키의 생성 전략을 지정합니다.
@Column(name = "reply_num") // JPA 어노테이션으로, 컬럼의 속성을 지정합니다.
private Integer replyNum; // 리플 번호
// 게시글 정보 (외래키로 참조)
@ManyToOne(fetch = FetchType.LAZY) // Reply입장에서 다대일 관계 // (fetch~) : 필요할 때만 가져와 달라는 뜻
@JoinColumn(name = "board_num") // web5_board 테이블 조인
private BoardEntity board; // 리플이 달린 게시글과 게시글의 제목까지도 알 수 있음
// 작성자 정보 (외래키로 참조)
@ManyToOne(fetch = FetchType.LAZY) // Board입장에서 다대일 관계 // (fetch~) : 필요할 때만 가져와 달라는 뜻
@JoinColumn(name = "member_id", referencedColumnName = "member_id") // web5_Member 테이블 조인
private MemberEntity member;
// 리플 내용
@Column(name = "contents", nullable = false, length = 2000, columnDefinition = "text") // JPA 어노테이션으로, 컬럼의 속성을 지정합니다.
private String contents; // 리플 내용
// 리플 작성 시간
@CreatedDate // 스프링 데이터 어노테이션으로, 엔티티가 생성될 때의 일자를 자동으로 설정합니다.
@Column(name = "create_date", columnDefinition = "timestamp default current_timestamp") // JPA 어노테이션으로, 컬럼의 속성을 지정합니다.
private LocalDateTime createDate; // 생성 일자
}
5. Repository부분
package net.datasa.web5.repository;
import net.datasa.web5.domain.entity.ReplyEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/*
리플 관련 repository
*/
@Repository // 안붙이면 bin 오류 발생
public interface ReplyRepository extends JpaRepository<ReplyEntity, Integer> {
// 어떤 게시글 하나에 달린 리플 목록 전체 조회(리플번호 순으로 정렬)
// select * from web5_reply where board_num = 게시글 번호 order by reply_num;
List<ReplyEntity> findByBoard_BoardNumOrderByReplyNum(Integer boradNum);
// = List<ReplyEntity> findByBoard_BoardNum(Integer boradNum ,Sort sort);
}
'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.1 [게시판] 게시글 10개씩 보여주기 (0) | 2024.08.01 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- setting
- html
- 백준
- JPA
- data science academy
- 조건문
- 2739번
- MySQL
- javascript
- Spring
- if문
- backjoon
- Intellij idea
- springboot
- Modal
- DB
- ajax
- java
- 2480
- 오븐시계
- Spring boot
- 가계부만들기
- 반복문
- css
- Linux
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함