2024.06.05 - [만들자] - [게시판]spring boot + mybatis + mysql 로 게시판 만들기 -4(화면연동)
**이전글**
조회
지난시간에는 insert문을 사용해 글을 작성하여 DB에 저장하고 화면과 연동해 보았다. 이번 시간에는 작성한 글의 목록과 그 상세 내역을 조회하는 페이지를 작성해 보겠다.
1) 목록조회 기능
작성한 글들의 목록을 조회하는 기능이다. 기존 구조에서 findAll()이라는 메서드를 추가하고 매퍼에도 반영해 준다. 그다음 목록을 보여주는 화면을 구성하기 위해 list.html파일을 추가해 주었다.
#BoardController
@Controller
@RequiredArgsConstructor
@Slf4j
public class BoardController {
private final BoardService boardService;
@GetMapping("/save")
public String save(){
return "save";
}
@PostMapping("/save")
public String save(BoardDTO boardDTO){
boardService.save(boardDTO);
return "redirect:/list"; //지난시간에는 글을 작성하면 리다이렉션이 되지 않았다.
//반환타입을 String으로 변경하고 /list url로 리다이렉션 해준다.
//글작성이 성공하면 localhost:8080/list로 페이지가 넘어간다.
}
@GetMapping("/list")
public String findAll(Model model){
List<BoardDTO> boardDTOList = boardService.findAll();
model.addAttribute("boardList", boardDTOList);
return "list";
}
}
반환된 boardDTO가 담긴 리스트 형태의 자료구조를 "boardDTOList"라는 이름으로 모델에 담아서 반환한다. Model객체는 파라미터에 선언하면 스프링이 자동으로 생성해 준다.
DB에서 값을 성공적으로 반환받으면 list페이지로 화면을 전환한다.
#BoardService
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
public void save(BoardDTO boardDTO){
boardRepository.save(boardDTO);
}
public List<BoardDTO> findAll() {
return boardRepository.findAll();
}
}
사실 현재까지는 Service계층에서 하는 일이 없다. 기능이 복잡해지면 비즈니스 로직이 들어가면서 Service계층에서 할 일이 생긴다. 비록 기능은 없지만 MVC구조를 지키기 위해서 Service를 거쳐서 가도록 한다.
#BoardRepositoty
@Repository
@RequiredArgsConstructor
public class BoardRepository {
private final SqlSessionTemplate sql;
public void save(BoardDTO boardDTO) {
sql.insert("Board.save", boardDTO);
}
public List<BoardDTO> findAll() {
System.out.println("findAll");
return sql.selectList("Board.findAll");
}
}
Board라는 namespace를 갖는 매퍼에서 id가 findAll인 sql을 찾아가도록 한다. "findAll"이라고 쳐야 하는데 "findALl"이라고 대소문자를 잘못 기입하여 한참 동안 오류를 찾았다. 이런 별것 아닌 문제에 시간 쏟는 게 제일 아깝다.
#board-mapper.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Board">
<insert id="save" parameterType="board">
insert into board_table(boardTitle, boardWriter, boardPass, boardContents)
values(#{boardTitle}, #{boardWriter}, #{boardPass}, #{boardContents})
</insert>
<select id="findAll" resultType="board">
select id, boardTitle, boardWriter, boardHits, date_format(createdAt, "%Y-%m-%d") as createdAt
from board_table
order by id desc
</select>
</mapper>
select요청으로 board_table에 있는 데이터 목록을 가져와서 BoardDTO객체에 담아서 반환한다. resultType이 BoardDTO라도 앞서 BoardRepository에서 sql.selectList() 메서드를 호출했다면 마이바티스가 자동으로 리스트 타입으로 담아서 반환해 준다.
목록조회에서는 글 내용이 필요 없기 때문에 그 외의 데이터만 받아오도록 한다. 지금처럼 데이터의 양이 적을 때는 전부 받아와도 크게 문제가 되지 않지만 실제 서비스에서는 글 내용의 용량이 매우 큰 경우도 있기 때문에 실제 보이지 않는 데이터를 주고받느라 서버나 DB에 부하가 걸릴 수 있기 때문이다.
id는 자동으로 시간순에 따라 자동으로 생성되게 DB테이블을 생성할 때 설정해 주었기 때문에 id가 높은 순서대로 내림차순(desc) 정렬하게 되면 최신순으로 데이터를 정렬해서 반환한다.
#list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
table, tr, td, th {
border: 1px solid black;
border-collapse: collapse;
}
th, td {
padding: 10px;
}
</style>
<body>
<a href="/save">글쓰기</a>
<table>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성시간</th>
<th>조회수</th>
</tr>
<tr th:each="board: ${boardList}">
<td th:text="${board.id}"></td>
<td>
<a th:text="${board.boardTitle}" th:href="@{|/{board.id}|}"></a>
</td>
<td th:text="${board.boardWriter}"></td>
<td th:text="${board.createdAt}"></td>
<td th:text="${board.boardHits}"></td>
</tr>
</table>
</body>
</html>
thymeleaf에서는 모델에서 받아온 값을 태그 내에서 바로 사용이 가능하다. 기존 jsp에서는 태그를 새로 생성해서 매우 귀찮고 지저분한 방식으로 forEach문을 구현해야 했지만, thymeleaf는 tr태그 내부에서 forEach문을 구현할 수 있다. 사용법도 기존의 Java나 javascript의 forEach문의 형태와 비슷하다. boardList는 컨트롤러에서 모델에 추가한 데이터이고 이를 board라는 변수로 각각 받아서 td태그에 사용할 수 있다.
또한 th:href라는 문법을 통해 동적으로 url로 리다이렉션 해줄 수 있다. 위 코드에서는 글의 번호인 id를 localhost:8080/id의 형태로 요청한다.
#thymleaf 공식문서 href사용법
<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html"
th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>
https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#link-urls
변수는 "${}"로 동적으로 삽입해 주지만 url은 "@{}"라는 방식으로 동적으로 삽입할 수 있다. 절대경로와 상대경로 모두 사용 가능하다.
이제 잘 작동하는지 테스트해보자
#글목록 조회 테스트
글목록 버튼을 누르니 목록이 잘 조회된다. 이전에 작성한 글 목록이 조회되는 것을 확인할 수 있다.
#새로운 글 작성 테스트
BoardController의 save메서드에서 글작성이 성공적으로 수행되면 목록으로 페이지 이동이 일어나도록 수정하였다. 글작성이 성공하자 목록페이지로 이동하는 것을 확인할 수 있다.
2) 상세조회 기능, 조회수
글목록에서 글을 눌러 상세조회를 하게 되면 조회수가 된다. 즉, 상세조회를 하는 select기능과 해당 글에 대한 조회수를 올려주는 update기능이 함께 고려되어야 한다.
#BoardController
@GetMapping("/{id}")
public String findById(@PathVariable("id") Long id, Model model) {
//조회수 처리
boardService.updateHits(id);
//상세내용 가져오기
BoardDTO boardDTO = boardService.findById(id);
model.addAttribute("board", boardDTO);
return "detail";
}
@PathVariable이라는 어노테이션을 사용하면 자동으로 url요청을 변수에 담을 수 있다. 클라이언트 측에서 localhost:8080/id 형태로 요청을 했으니 이 패턴을 읽고 id라는 변수에 담아준다.
일단 요청이 들어오면 updateHits()라는 메서드로 조회수를 올린다. 그다음 글 상세내용을 조회하는 메서드가 실행된다.
그 다음 요청이 성공적으로 마무리되면 Model객체에 "boardDTO"를 담아서 뷰로 보내고 /detail로 페이지 이동한다.
#BoardService
public void updateHits(Long id) {
boardRepository.updateHits(id);
}
public BoardDTO findById(Long id) {
return boardRepository.findById(id);
}
마찬가지로 아직은 별다른 기능이 없다. 컨트롤러의 요청을 레포지토리로 연결해 주는 작업만 수행한다.
#BoardRepository
public void updateHits(Long id) {
sql.update("Board.updateHits", id);
}
public BoardDTO findById(Long id) {
return sql.selectOne("Board.findById", id);
}
각각의 요청을 매퍼로 연결해 준다. 조회수기능은 반환값이 따로 필요 없지만 상세조회는 글내용에 대한 데이터를 반환해야 하기 때문에 sql.selectOne으로 데이터를 받아 BoardDTO객체에 담아 반환한다.
#board-mapper.xml
<update id="updateHits" parameterType="Long">
update board_table
set boardHits = boardHits+1 where id=#{id}
</update>
<select id="findById" parameterType="Long" resultType="board">
select id, boardTitle, boardWriter, boardPass, boardContents, boardHits,
date_format(createdAt, "%Y-%m-%d") as createdAt
from board_table
where id=#{id}
</select>
기본적인 update와 select sql문이다. update문은 boardHits의 카운트를 현재값에서 1 추가해 주는 sql문이고 select는 id값을 가지고 해당하는 글을 조회한다.
#detail.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>detail</title>
<style>
table, tr, td, th {
border: 1px solid black;
border-collapse: collapse;
}
th, td {
padding: 10px;
}
</style>
</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.createdAt}"></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 = () => {
location.href = "/list";
}
const updateReq = () => {
}
const deleteReq = () => {
}
</script>
</html>
상세조회한 데이터를 받아서 화면에 뿌려준다. 버튼이 세 개 있는데 목록화면으로 돌아가는 기능과 수정, 삭제버튼이 있다. 아직 수정기능과 삭제기능은 구현되지 않았기 때문에 함수만 버튼을 눌렀을 때 작동할 함수의 뼈대만 정의해 둔다.
#상세조회 테스트
url에 파라미터로 id=7이 잘 요청되었고 해당하는 데이터가 잘 불러와졌다. 글이 상세조회 됨에 따라서 조회수도 잘 증가하는 것을 확인할 수 있다.
이제 글작성과 조회기능은 마무리되었다. 다음 시간에는 수정과 삭제기능을 추가해 보도록 하자.
'만들자 > 게시판만들기' 카테고리의 다른 글
[게시판]spring boot + mybatis + mysql 로 게시판 만들기 -7(사진첨부, 전체코드) (0) | 2024.06.17 |
---|---|
[게시판]spring boot + mybatis + mysql 로 게시판 만들기 -6(수정,삭제) (0) | 2024.06.12 |
[게시판]spring boot + mybatis + mysql 로 게시판 만들기 -4(화면연동) (0) | 2024.06.05 |
[게시판]spring boot + mybatis + mysql 로 게시판 만들기 -3(DB연동) (1) | 2024.06.04 |
[게시판]spring boot + mybatis + mysql 로 게시판 만들기 -2(DB세팅) (0) | 2024.06.03 |