- BoardMapper.interface -
public List<BoardVO> getList(); // 게시글 목록
- BoardMpappger.xml -
<!-- 게시판 목록 -->
<select id="getList" resultType="BoardVO">
select * from board
</select>
- BoardMapperTests -
@MybatisTest
@AutoConfigureTestDatabase(replace = Replace.NONE) // 현재 연결된 실제DB로 테스트함을 의미
@Rollback(value = false) // 테스트시 롤백 안함
@Log
public class BoardMapperTests {
// Junit 5버전으로 테스트
@Autowired
private BoardMapper boardMapper;
@Test
public void testEnroll() {
// BoardVO vo = new BoardVO();
//
// vo.setTitle("제목_테스트");
// vo.setContent("내용_테스트");
// vo.setWriter("작성자_테스트");
//
// boardMapper.enroll(vo);
List<BoardVO> list = boardMapper.getList();
// for문으로 테스트
// for(int i=0; i < list.size(); i++) {
// log.info("" + list.get(i));
// }
// forEach문
// for(BoardVO vo :list) {
// log.info("" + vo);
// }
// foreach메서드와 람다식
list.forEach(board -> log.info(""+board));
}
}
앞서 테스트한 코드는 주석처리 후 반복문을 통해 테스트
JUnit Test로 실행 시 에러없이 성공해야 함.
모든 게시글을 불러온 것을 로그창에서 확인가능.
- BoardService -
public List<BoardVO> getList(); // 게시글 목록
- BoardServiceImpl -
@Override
public List<BoardVO> getList() {
return boardMapper.getList();
}
- BoardController -
@GetMapping("/list")
public String boardListGet(Model model) {
model.addAttribute("boardList", boardService.getList());
return "list";
}
boardList
에 모든 게시글을 담아 list페이지로 리턴.
앞서 list페이지 출력용으로 작성했던 boardListGet메서드는 삭제한다.
- list.html -
<a th:href="@{/board/enroll}" class="btn btn-primary">글쓰기</a>
<div class="card">
<div class="table-responsive">
<table class="table align-items-center mb-0">
<thead>
<tr>
<th class="text-center text-secondary text-xxs font-weight-bolder opacity-7">NO</th>
<th class="text-center text-secondary text-xxs font-weight-bolder opacity-7">TITLE</th>
<th class="text-center text-secondary text-xxs font-weight-bolder opacity-7">AUTHO</th>
<th class="text-center text-secondary text-xxs font-weight-bolder opacity-7">등록일</th>
<th class="text-center text-secondary text-xxs font-weight-bolder opacity-7">수정일</th>
<th class="text-center text-secondary text-xxs font-weight-bolder opacity-7">기타</th>
</tr>
</thead>
<tbody>
<tr th:each="board : ${boardList}">
<td class="align-middle text-center">
<span class="text-secondary text-sx" th:text="${board.bno}"></span>
</td>
<td class="align-middle text-center">
<span class="text-secondary text-sx" th:text="${board.title}"></span>
</td>
<td class="align-middle text-center">
<span class="text-secondary text-sx" th:text="${board.writer}"></span>
</td>
<td class="align-middle text-center">
<span class="text-secondary text-sx" th:text="${#temporals.format(board.regdate , 'yyyy-MM-dd a hh:mm:ss')}"></span>
</td>
<td class="align-middle text-center">
<span class="text-secondary text-xs" th:text="${#temporals.format(board.updateDate , 'yyyy-MM-dd a hh:mm:ss')}"></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
타임리프에서 String타입 -> Date타입으로 포멧
<div th:text="${#temporals.format(item.createDt, 'yyyy-MM-dd HH:mm:ss')}"></div>
타임리프에서 Date타입 -> Date타입으로 포멧
<div th:text="${#dates.format(item.createDt, 'yyyy-MM-dd HH:mm:ss')}"></div>
http://localhost:8080/board/list
모든 게시글을 list페이지에서 조회할 수 있음.
- BoardMapper.interface -
public BoardVO getPage(int bno); // 게시글 조회
- BoardMpappger.xml -
<!-- 게시글 조회 -->
<select id="getPage" resultType="BoardVO">
SELECT * FROM board WHERE bno = #{bno}
</select>
- BoardMapperTests -
// 게시글 조회
int bno = 1;
log.info("" + boardMapper.getPage(1));
앞선 테스트코드는 모두 주석처리 후 테스트.
로그창에 조회된 게시글의 데이터가 출력됨을 확인.
- BoardService -
public BoardVO getPage(int bno); // 게시글 조회
- BoardServiceImpl -
@Override
public BoardVO getPage(int bno) {
return boardMapper.getPage(bno);
}
- BoardController -
@GetMapping("/list")
public String boardListGet(Model model) {
model.addAttribute("boardList", boardService.getList());
return "list";
}
- get.html -
<div class="card-body bg-white">
<table class="table table-condensed">
<tr>
<th>번호</th>
<td th:text="${board.bno}"></td>
<th>작성일</th>
<td th:text="${#temporals.format(board.regdate, 'yyyy-MM-dd a hh:mm:ss')}"></td>
</tr>
<tr>
<th>작성자</th>
<td th:text="${board.writer}"></td>
<th>수정일</th>
<td th:text="${#temporals.format(board.updateDate, 'yyyy-MM-dd a hh:mm:ss')}"></td>
</tr>
<tr>
<th>제목</th>
<td colspan="3" th:text="${board.title}"></td>
</tr>
<tr>
<th>내용</th>
<td colspan="3" th:text="${board.content}"></td>
</tr>
</table>
</div>
<div class="card-footer bg-white text-center pt-0 px-lg-2 px-1">
<a th:href="@{/board/list}" class="btn btn-success">목록</a>
<a th:href="@{/board/update}" class="btn btn-secondary">수정</a>
</div>
-list.html- 의 board.title에 a태그를 추가
<a th:href="@{/board/get(bno=${board.bno})}"><span class="text-secondary text-xs" th:text="${board.title}"></span></a>
글제목 클릭시 http://localhost:8080/board/get?bno=2 형식의 url로 이동.
타임리프에서 parameter는 ()내부에 넣어줘야 함.
- BoardMapper.interface -
public int modify(BoardVO board); // 게시글 수정
- BoardMpappger.xml -
<!-- 게시글 수정 -->
<update id="modify">
UPDATE board SET title = #{title}, content = #{content}, updatedate = now() WHERE bno = #{bno}
</update>
오라클의 경우 updatedate = now()
대신 updateDate = sysdate
사용.
- BoardMapperTests -
@Test
public void testModify() {
BoardVO board = new BoardVO();
board.setBno(1);
board.setTitle("제목_수정");
board.setContent("내용_수정된 내용입니다");
int result = boardMapper.modify(board);
log.info("result : " + result);
}
Run As > JUnit Test로 실행
1번 게시글이 수정됨.
반환데이터형을 int로 지정해두었으므로 성공시 1을, 실패시 0을 반환함.
result:1
이 출력되었다면 수정에 성공한 것.
DB에서 updatedate가 적용되었는지 확인
- BoardService -
public int modify(BoardVO board); // 게시글 수정
- BoardServiceImpl -
@Override
public int modify(BoardVO board) {
return boardMapper.modify(board);
}
수정을 마친 후 성공하면 1, 실패하면 0을 리턴해야하므로 return
- BoardController -
// 수정페이지 출력
@GetMapping("/modify")
public String boardModifyGet(@RequestParam("bno") int bno, Model model) {
model.addAttribute("board", boardService.getPage(bno));
return "modify";
}
// 수정
@PostMapping("/modify")
public String boardModifyPost(BoardVO board, RedirectAttributes attr) {
boardService.modify(board); // modify페이지에서 수정된 내용으로 업데이트
attr.addFlashAttribute("message", "수정 성공!");
return "redirect:/board/list"; // post 후 새로고침하면 다시 post가 될 수 있으므로 새로운요청인 redirect로 처리
}
수정페이지를 출력하는 메서드(get)와 실제로 수정을 하는 메서드(post)를 따로 작성한다.
* redirect철자 자주 틀리니 주의!
-get.html- 의 수정버튼을 <a th:href="@{/board/modify(bno=${board.bno})}" class="btn btn-secondary">수정</a>
으로 수정하여 수정버튼을 눌렀을 때 해당 글데이터를 가지고 수정페이지로 이동하도록 함.
- modify.html -
<div class="card-header">
<h4 class="font-weight-bolder">게시글 수정</h4>
<p class="mb-0">게시글을 수정합니다</p>
</div>
<div class="card-body bg-white">
<form role="form" th:action="@{/board/modify}" method="post" th:object="${board}">
<input type="hidden" th:field="*{bno}" />
<div class="input-group input-group-outline mb-3 is-filled">
<label class="form-label">제목</label>
<input type="text" class="form-control" th:field="*{title}" required />
</div>
<div class="input-group input-group-outline mb-3">
<div class="input-group input-group-dynamic">
<textarea th:field="*{content}" class="form-control" rows="5" placeholder="내용을 적어주세요." spellcheck="false" required></textarea>
</div>
</div>
<div class="input-group input-group-outline mb-3 is-filled">
<label class="form-label">글쓴이</label>
<input type="text" class="form-control" th:field="*{writer}" readonly />
</div>
<div class="text-center">
<button type="submit" class="btn btn-success">수정 하기</button>
<a th:href="@{/board/list}" class="btn btn-danger">수정 취소</a>
</div>
</form>
</div>
<input type="hidden" th:field="*{bno}" />
이 없으면 bno
필드가 없으므로 새로운 게시글을 등록하게 됨.
실수하기 쉬운 부분이므로 주의.
글쓴이 필드는 읽기만 허용, 수정안됨.
수정완료 message가 alert창에 출력된 후 list페이지로 redirect됨.
- BoardMapper.interface -
public int delete(int bno); // 게시글 삭제
- BoardMpappger.xml -
<delete id="delete">
DELETE FROM board WHERE bno = #{bno}
</delete>
- BoardMapperTests -
@Test
public void testDelete() {
int result = boardMapper.delete(5);
log.info("result: " + result);
}
5번글이 삭제됨을 DB에서 확인가능.
- BoardService -
public int delete(int bno); // 게시글 삭제
- BoardServiceImpl -
@Override
public int delete(int bno) {
return boardMapper.delete(bno);
}
- BoardController -
@GetMapping("/delete")
public String boardDeleteGet(@RequestParam("bno") int bno, RedirectAttributes attr) {
boardService.delete(bno);
attr.addFlashAttribute(bno);
return "redirect:/board/list";
}
- get.html -
<button class="btn btn-danger" onclick="deleteConfirm();">삭제</button>
...생략...
<script>
function deleteConfirm() {
if (confirm('정말로 삭제할까요?')) {
location.href = '/board/delete?bno=' + '[[${board.bno}]]';
}
}
</script>
게시글 조회 페이지에 삭제버튼 추가.
삭제확인alert창에서 확인을 눌렀을때 삭제 후 redirect로 list페이지로 이동, 취소 시 함수가 그대로 종료됨.
삭제버튼 클릭 시 삭제확인 alert창이 뜨고 확인을 누르면 삭제됨.
addFlashAttribute와 addAttribute의 차이
실제로 데이터를 처리하는 작업은 BoardMapper.xml에서 함.
BoardMapper인터페이스의 메서드명과 BoardService의 id를 맞춰줘야 한다.
우선은 BoardMapper인터페이스의 sql을 BoardService인터페이스가 가져가 BoardMapper.xml을 통해 직접적으로 데이터를 다룸.
- 부족한 점
BoardMapper인터페이스와 BoardService인터페이스의 역할이 헷갈림.
나눈 이유는 스프링 프로젝트 계층구조인데, 이에 대해서는 좀 더 학습이 필요할 것 같음.
보충할 개념
spring의 model객체란?