게시글 작성과 게시글 목록을 출력하는것 까지 했습니다 이제 게시글을 들어가 볼수 있게 상세 조회 를 만들어야합니다.
첫번째 일단 컨트롤러에서 처리를 해줘야합니다.
BoardController
package cho.boardplus.controller;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
@RequestMapping("/board")
@Controller
public class BoardController {
private final BoardService boardService;
//메인페이지
@GetMapping("/index")
public String index() {
return "index";
}
//글 작성 페이지
@GetMapping("/save")
public String writeForm() {
return "save";
}
// 글작성 컨트롤러
@PostMapping("save")
public String save(@ModelAttribute BoardDTO boardDTO) {
System.out.println("boardDTO = " + boardDTO);
boardService.save(boardDTO);
return "index"; //index 로 변경
}
// 게시글 목록
@GetMapping("/")
public String findAll(Model model) {
// DB에서 전체 게시글 데이터를 가져와서 list.html에 보여준다.
List<BoardDTO> boardDTOList = boardService.findAll();
model.addAttribute("boardList", boardDTOList);
return "list";
}
//게시글 조회
@GetMapping("/{id}")
public String findById(@PathVariable Long id, Model model){//경로상에 있는 값을 가져올때는 @PathVariable 을 사용해야합니다.
/*
해당 게시글의 조회수를 하나 올리고
게시글 데이터를 가져와서 detail.html 에 출력
*/
boardService.updateHits(id);
BoardDTO boardDTO = boardService.findById(id);
model.addAttribute("board",boardDTO);
return "detail"; //2번의 호출이 있고 디테일로 리턴값을 준다.
}
}
- 경로상에 있는 값을 가져올때는 기본적으로 @PathVariable 을 사용해야합니다.
- boardservice 에 updateHits라는 조회수 관련 메소드를 작성합니다. BoardDTO 에 값을 가져올 boardService.finById 작성합니다
- 모델에 파라미터를 담아서 리턴값으로 datail 로 값을 넘겨줍니다.
2번의 호출을 발생한다는걸 기억 하시면 좋습니다.
이제 서비스클래스에서 조회수 증가 메소드를 작성해야합니다
JPA가 제공해주는 메소드 들은 select 조건이라던지 정렬이라던지 이런것들은 jpa에서 제공하는 메소드 규칙을따라가면 쿼리 방언을 알아서 jpa가 작성해줍니다. 조회수 증가같은 특수한 목적을 가진 쿼리들은 jpa가 제공을 잘 안해줘서 별도의 메소드를 정의할 필요가 있습니다.
BoardRepository
package cho.boardplus.repository;
import cho.boardplus.entity.BoardEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
public interface BoardRepository extends JpaRepository<BoardEntity,Long> {
// update board_table set board_hits=board_hits+1 where id=?
@Transactional
@Modifying
@Query(value = "update BoardEntity b set b.boardHits=b.boardHits+1 where b.id=:id")
void updateHits(@Param("id") Long id);
}
- @Query()는 jpa 에서 제공하는 어노테이션 입니다.
- 쿼리를 주는 방식은 2가지 가있는대 해당 예시는 Entity를 기준으로 쿼리를 작성하는 것이고 나중에 실제 DB기준으로 네이티브 쿼리문을 작성할수 있습니다. 나중에nativeQuery = true 라는 옵션을 주면 네이티브 쿼리문을 사용할수 있습니다.
- update 옆에 테이블 이름이 보통 옵니다 우리는 Entity 기준으로 하기 때문에 Entity 이름입니다. 그리고 Entity 를 기준으로 했을때는 옆에 b 처럼 약어를 쓰는게 필수 입니다. boardEntity 를 b 라고 약칭을 했습니다.
- Entity 의 정의한 컬럼 boardHits 접근을 한다 라는 의미 가 담겨저 있고 b.id 는 boardEntity 안에 있는 id 에 접근한다라는 뜻입니다.
- b.id =id =id 이부분은 계속 바뀌는 자리입니다 예를들어 아이디값이 5면 id id5 이렇게 바뀝니다.
- update 나 delete 같은 쿼리를 작성할때는 무조건 필수적으로 @Modifying 어노테이션을 사용해야합니다.
이제 레포지토리에 작성했던 설정들을 가지고 서비스단에서 작업을 해줘야합니다.
BoardService
package cho.boardplus.service;
import cho.boardplus.repository.BoardRepository;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.entity.BoardEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
//DTO -> Entity (Entity Class)
//Entity -> DTO (DTO Class)
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
//작성
public void save(BoardDTO boardDTO) {
BoardEntity boardEntity = BoardEntity.toSaveEntity(boardDTO);
boardRepository.save(boardEntity);
}
public List<BoardDTO> findAll() {//파인드 올은 대부분 엔티티 한태서 온다.
List<BoardEntity> boardEntityList = boardRepository.findAll();
List<BoardDTO> boardDTOList = new ArrayList<>();
for (BoardEntity boardEntity: boardEntityList) {
boardDTOList.add(BoardDTO.toBoardDTO(boardEntity));
}
return boardDTOList;
}
@Transactional
public void updateHits(Long id) { //추가
boardRepository.updateHits(id);
}
}
- jpa 에서 제공하는 메서드가 아니라 별도로 추가된 메소드 같은 경우는 @Transactional 어노테이션을 추가해야합니다. 안붙이면 에러 납니다.
조회수 관련 코드 작성은 끝났습니다.
이제 이어서 컨트롤러에서 오류를 발생시키고 있는 findById 를 마저 작성해야합니다.
boardService
package cho.boardplus.service;
import cho.boardplus.repository.BoardRepository;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.entity.BoardEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
//DTO -> Entity (Entity Class)
//Entity -> DTO (DTO Class)
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
//작성
public void save(BoardDTO boardDTO) {
BoardEntity boardEntity = BoardEntity.toSaveEntity(boardDTO);
boardRepository.save(boardEntity);
}
public List<BoardDTO> findAll() {//파인드 올은 대부분 엔티티 한태서 온다.
List<BoardEntity> boardEntityList = boardRepository.findAll();
List<BoardDTO> boardDTOList = new ArrayList<>();
for (BoardEntity boardEntity: boardEntityList) {
boardDTOList.add(BoardDTO.toBoardDTO(boardEntity));
}
return boardDTOList;
}
// 게시글 조회수
@Transactional
public void updateHits(Long id) {
boardRepository.updateHits(id);
}
public BoardDTO findById(Long id) { //추가
Optional<BoardEntity> optionalBoardEntity = boardRepository.findById(id);
if (optionalBoardEntity.isPresent()){
BoardEntity boardEntity =optionalBoardEntity.get();
BoardDTO boardDTO = BoardDTO.toBoardDTO(boardEntity);
return boardDTO;
}else {
return null;
}
}
}
- boardRepository를 사용하여 주어진 id를 가진 BoardEntity 객체를 찾습니다.
- BoardEntity 개체가 발견되면 정적 메소드 BoardDTO,toBoardDTO()를 사용하여 BoardDTO개체로 변환됩니다.
- 리턴값으로 BoardDTO 개체를 반환합니다.
- BoardEntity 개체가 없으면 null을 반환하도록 설정했습니다.
고유한 id를 기반으로 데이터베이스에서 단일 board 게시물을 나타내기 위해서 BoardDTO 개체를 검색하는 방도로 작성했습니다.
이제 프론트 단에서 게시판 상세 페이지를 작성해야합니다.
detail.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>detail</title>
<!-- jquery cdn -->
<script src="https://code.jquery.com/jquery-3.6.3.min.js" integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=" crossorigin="anonymous"></script>
</head>
<body>
<table>
<tr>
<th>id</th>
<td th:text="${board.id}"></td>
</tr>
<tr>
<th>title</th>
<td th:text="${board.boardTitle}"></td>
</tr>
<tr>
<th>writer</th>
<td th:text="${board.boardWriter}"></td>
</tr>
<tr>
<th>date</th>
<td th:text="${board.boardCreatedTime}"></td>
</tr>
<tr>
<th>hits</th>
<td th:text="${board.boardHits}"></td>
</tr>
<tr>
<th>contents</th>
<td th:text="${board.boardContents}"></td>
</tr>
</table>
<button onclick="listReq()">목록</button>
<button onclick="updateReq()">수정</button>
<button onclick="deleteReq()">삭제</button>
</body>
<script th:inline="javascript">
const listReq = () => {
console.log("목록 요청");
const page = [[${page}]];
location.href = "/board/paging?page="+page;
}
const updateReq = () => {
console.log("수정 요청");
const id = [[${board.id}]];
location.href = "/board/update/" + id;
}
const deleteReq = () => {
console.log("삭제 요청");
const id = [[${board.id}]];
location.href = "/board/delete/" + id;
}
</script>
</html>
나중에 수정,삭제 다시 리스트로 돌아가야 하기때문에 목록 까지 미리 프론트 단에 만들어 놨습니다.
'Spring > 게시판 CRUD + 추가기능들' 카테고리의 다른 글
게시판 삭제 (0) | 2023.03.29 |
---|---|
게시글 수정 (0) | 2023.03.29 |
게시글 목록 (0) | 2023.03.28 |
게시글 작성 (0) | 2023.03.27 |
게시판 댓글 기능 추가 (ajax) (0) | 2023.02.17 |