GuestBook 프로젝트 (7) (Join 처리, JPQL 적용, DTO, Service, ServcieImpl)
GuestBook 프로젝트 (7) (Join 처리, JPQL 적용, DTO, Service, ServcieImpl)
GuestBook 프로젝트 (6) (Member / Board / Reply 생성, 연관 관계 추가, 지연(Lazy) / 즉시(Eager) 로딩, Fetch) GuestBook 프로젝트 (6) (Member / Board / Reply 생성, 연관 관계 추가, 지연(Lazy) / 즉시(Eager) 로딩, Fetch) GuestB
soohykeee.tistory.com
게시물 조회 처리
게시물 조회의 경우는 board의 키값인 bno를 파라미터로 받아와서 boardDTO로 리턴되도록 처리하면된다.
package com.example.guestbookdemo.service;
import com.example.guestbookdemo.dto.BoardDTO;
import com.example.guestbookdemo.dto.PageRequestDTO;
import com.example.guestbookdemo.dto.PageResultDTO;
import com.example.guestbookdemo.entity.Board;
import com.example.guestbookdemo.entity.Member;
public interface BoardService {
Long register(BoardDTO dto);
PageResultDTO<BoardDTO, Object[]> getList(PageRequestDTO pageRequestDTO);
BoardDTO get(Long bno);
//...생략
}
package com.example.guestbookdemo.service;
import com.example.guestbookdemo.dto.BoardDTO;
import com.example.guestbookdemo.dto.PageRequestDTO;
import com.example.guestbookdemo.dto.PageResultDTO;
import com.example.guestbookdemo.entity.Board;
import com.example.guestbookdemo.entity.Member;
import com.example.guestbookdemo.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import java.util.function.Function;
@Service
@RequiredArgsConstructor
@Log4j2
public class BoardServiceImpl implements BoardService {
private final BoardRepository boardRepository;
//...생략
@Override
public BoardDTO get(Long bno) {
Object result = boardRepository.getBoardByBno(bno);
Object[] arr = (Object[]) result;
return entityToDTO((Board) arr[0], (Member) arr[1], (Long) arr[2]);
}
}
해당 get 메소드가 정상적으로 조회가 되는지 테스트 해보기 위해 테스트 코드를 작성했다.
콘솔창에 정상적으로 조회가 된것을 확인할 수 있다.
@Test
public void testGet() {
Long bno = 100L;
BoardDTO boardDTO = boardService.get(100L);
System.out.println(boardDTO);
}
게시물 삭제 처리
실제 실무에서 삭제 처리는 신중하고, 조심스럽게 처리해야한다. 이러한 게시물 프로그램에서 게시글을 삭제할 때 만약 댓글이 존재하는 게시물을 삭제하게 되면, 해당 댓글도 동의 없이 삭제가 되기 때문이다. 따라서 실무에서는 바로 삭제하지 않고 상태 state, deleteAt 컬럼을 두어 상태를 변경하는것이 일반적이다.
게시물을 삭제할 경우 해당 board에서만 데이터를 지우는것이 아니라, FK로 게시물을 참조하고 있는 reply 테이블에서도 삭제하는 게시물을 참조하는 데이터를 삭제 시켜줘야 한다.
- 삭제하려는 게시물을 참조하는 댓글을 모두 삭제
- 해당 게시물을 삭제
위와 같은 순서로 작업을 처리해야 에러없이 실행이 된다. 또한 삭제나 수정의 경우 하나의 transaction으로 처리가 되어야한다. 그렇지 않으면 삭제하는 사이 다른 작업이 처리되면 프로그램의 큰 오류가 생길 수 있기 때문이다.
Reply에서 bno를 참조하는 것들을 지워야하므로 replyRepository에 JPQL을 이용하여 삭제하는 메소드를 작성해야 한다.
해당 deleteByBno메소드에서 @Modifying 어노테이션은 @Query 어노테이션에서 수정, 삭제 쿼리 메소드를 사용할 때 작성해주는 어노테이션이다. 조회쿼리를 제외하고 데이터에 변경이 일어나는 INSERT, UPDATE, DELETE를 할때 작성한다.
package com.example.guestbookdemo.repository;
import com.example.guestbookdemo.entity.Reply;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
public interface ReplyRepository extends JpaRepository<Reply, Long> {
@Modifying
@Query("delete from Reply r where r.board.bno=:bno")
void deleteByBno(Long bno);
}
boardServcie, boardServiceImpl 에 삭제하려는 bno의 reply를 삭제하고, board를 삭제하는 메소드를 removeWithReplies를 추가한다.
package com.example.guestbookdemo.service;
import com.example.guestbookdemo.dto.BoardDTO;
import com.example.guestbookdemo.dto.PageRequestDTO;
import com.example.guestbookdemo.dto.PageResultDTO;
import com.example.guestbookdemo.entity.Board;
import com.example.guestbookdemo.entity.Member;
public interface BoardService {
//... 생략
void removeWithReplies(Long bno);
//... 생략
}
package com.example.guestbookdemo.service;
import com.example.guestbookdemo.dto.BoardDTO;
import com.example.guestbookdemo.dto.PageRequestDTO;
import com.example.guestbookdemo.dto.PageResultDTO;
import com.example.guestbookdemo.entity.Board;
import com.example.guestbookdemo.entity.Member;
import com.example.guestbookdemo.repository.BoardRepository;
import com.example.guestbookdemo.repository.ReplyRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.function.Function;
@Service
@RequiredArgsConstructor
@Log4j2
public class BoardServiceImpl implements BoardService {
private final BoardRepository boardRepository;
private final ReplyRepository replyRepository;
//... 생략
@Transactional
@Override
public void removeWithReplies(Long bno) {
replyRepository.deleteByBno(bno);
boardRepository.deleteById(bno);
}
}
이제 작성한 removeWithReplies가 정상적으로 삭제가 되는지 테스트 코드를 통해 알아보겠다.
현재 bno가 4인 게시물에 4개의 댓글이 존재한다. bno가 4인 board를 삭제할 경우 해당 reply들과 board 모두 삭제가 되면 코드가 정상적으로 작성이 된것이다.
@Test
public void testRemove() {
Long bno = 4L;
boardService.removeWithReplies(bno);
}
위의 테스트 코드를 실행하면 아래와 같이 Reply 테이블에서 delete되고, Board 테이블에서 delete 되는 것을 볼 수 있고, 해당 테이블에서도 정상적으로 데이터들이 삭제된 것을 확인할 수 있다.
게시물 수정 처리
수정의 경우는 select를 통해 값이 존재하면 내용을 변경 후 다시 save 하는 방식이다. 따라서 게시물의 제목과 내용이 수정이 가능하도록 Board 엔티티에 제목과 내용 수정이 가능하도록 하는 메소드를 작성해야 한다.
package com.example.guestbookdemo.entity;
import lombok.*;
import javax.persistence.*;
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString(exclude = "writer")
public class Board extends BaseEntity {
//...생략
public void changeTitle(String title) {
this.title = title;
}
public void changeContent(String content) {
this.content = content;
}
}
boardService, boardServiceImpl 에도 수정하는 modify() 메소드를 작성한다.
package com.example.guestbookdemo.service;
import com.example.guestbookdemo.dto.BoardDTO;
import com.example.guestbookdemo.dto.PageRequestDTO;
import com.example.guestbookdemo.dto.PageResultDTO;
import com.example.guestbookdemo.entity.Board;
import com.example.guestbookdemo.entity.Member;
public interface BoardService {
//... 생략
void modify(BoardDTO boardDTO);
//... 생략
}
package com.example.guestbookdemo.service;
import com.example.guestbookdemo.dto.BoardDTO;
import com.example.guestbookdemo.dto.PageRequestDTO;
import com.example.guestbookdemo.dto.PageResultDTO;
import com.example.guestbookdemo.entity.Board;
import com.example.guestbookdemo.entity.Member;
import com.example.guestbookdemo.repository.BoardRepository;
import com.example.guestbookdemo.repository.ReplyRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.function.Function;
@Service
@RequiredArgsConstructor
@Log4j2
public class BoardServiceImpl implements BoardService {
//... 생략
@Transactional
@Override
public void modify(BoardDTO boardDTO) {
Board board = boardRepository.getReferenceById(boardDTO.getBno());
if (board != null) {
board.changeTitle(boardDTO.getTitle());
board.changeContent(boardDTO.getContent());
boardRepository.save(board);
}
}
}
TEST
@Test
public void testModify() {
BoardDTO boardDTO = BoardDTO.builder()
.bno(2L)
.title("제목 변경테스트")
.content("내용 변경테스트")
.build();
boardService.modify(boardDTO);
}
위의 테스트 코드를 실행해보면 아래와 같이 값이 수정된것을 확인할 수 있다.
Controller 화면 처리
package com.example.guestbookdemo.controller;
import com.example.guestbookdemo.dto.PageRequestDTO;
import com.example.guestbookdemo.service.BoardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/board/")
@Log4j2
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
@GetMapping("/list")
public void list(PageRequestDTO pageRequestDTO, Model model) {
model.addAttribute("result", boardService.getList(pageRequestDTO));
}
}
controller 하위 디렉토리에 BoardController 클래스를 생성한 후에 위와 같이 작성해 준다.
templates/board 디렉토리를 생성해주고 그 하위에 list.html 파일을 생성해준 후, 아래와 같이 작성해준다.
http://localhost:8080/board/list 로 접속하게 되면 아래와 같이 정상적으로 목록이 출력된다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content} )}">
<th:block th:fragment="content">
<h1 class="mt-4">
Board List Page
<span>
<a th:href="@{/board/register}">
<button type="button" class="btn btn-outline-primary">REGISTER</button>
</a>
</span>
</h1>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Title</th>
<th scope="col">Writer</th>
<th scope="col">Regdate</th>
</tr>
</thead>
<tbody>
<tr th:each="dto:${result.dtoList}">
<th scope="row">
<a th:href="@{/board/read(bno=${dto.bno},
page=${result.page},
type=${pageRequestDTO.type},
keyword=${pageRequestDTO.keyword})}">
[[${dto.bno}]]
</a>
</th>
<td>[[${dto.title}]] ------------- [<b th:text="${dto.replyCount}"></b>]</td>
<td>[[${dto.writerName}]] <small>[[${dto.writerEmail}]]</small></td>
<td>[[${#temporals.format(dto.regDate, 'yyyy/MM/dd')}]]</td>
</tr>
</tbody>
</table>
<ul class="pagination h-100 justify-content-center align-items-center">
<li class="page-item" th:if="${result.prev}">
<a class="page-link" th:href="@{/board/list(page=${result.start-1},
type=${pageRequestDTO.type},
keyword=${pageRequestDTO.keyword})}" tabindex="-1">Previous</a>
</li>
<li class=" 'page-item' + ${result.page == page?'active':''}" th:each="page:${result.pageList}">
<a class="page-link" th:href="@{/board/list(page=${page},
type=${pageRequestDTO.type},
keyword=${pageRequestDTO.keyword})}">[[${page}]]</a>
</li>
<li class="page-item" th:if="${result.next}">
<a class="page-link" th:href="@{/board/list(page=${result.end+1},
type=${pageRequestDTO.type},
keyword=${pageRequestDTO.keyword})}">Next</a>
</li>
</ul>
</th:block>
</th:block>
</html>
게시물의 등록, 수정, 삭제 처리도 앞서 작성했었던 것을 참고해서 작성해주면 된다.
아래 게시글을 참조
GuestBook 프로젝트 (4) (guestbook 등록, 조회, 수정, 삭제)
GuestBook 프로젝트 (4) (guestbook 등록, 조회, 수정, 삭제)
GuestBook 프로젝트 (3) (목록처리, 페이징, Controller) GuestBook 프로젝트 (3) (목록처리, 페이징, Controller) GuestBook 프로젝트 (2) (Querydsl Test Code, DTO, Service, ServiceImpl) GuestBook 프로젝트 (2) (Querydsl Test Code, DTO
soohykeee.tistory.com