
<tr th:each="dto: ${result.list}">
<td>[[${dto.bno}]]</td>
<td>
<td th:data-bno="${dto.bno}">
<img th:if="${dto.fileName != null}" th:src="'http://localhost:8081/attach/s_' + ${dto.fileName}">
[[${dto.title}]]
</td>
<td>[[${dto.writer}]]</td>
<td>[[${#temporals.format(dto.regDate, 'yyyy-MM-dd') }]]</td>
<td>[[${dto.viewCnt}]]</td>
</tr>
๐ th:data-bno=${dto.bno} ์ฒ๋ผ data-bno ๋ณ์์ dto.bno ๊ฐ์ ํ ๋นํ์ฌ scriptํ๊ทธ์์ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
script
const bno = target.getAttribute("data-bno")
๐ ์ฌ๊ธฐ์ getAttribute๋ html์์์์ ํน์ ์์ฑ๊ฐ์ ์ฝ์ด์ค๋ ์ญํ ์ธ๋ฐ, data-bno์ ์์ฑ๊ฐ์ ์ฝ์ด์์ bno๋ณ์์ ๋์ ํ๋๊ฒ.
๐ ๊ธฐ์กด์ ์๋ bno๊ฐ์ ์ ๋ ฅํ์๋๋ NoSuchElementException ํด๋น ์์ธ๊ฐ ๋ฐ์ํ๋๋ฐ, ์ค๋ฅํ์ด์ง๊ฐ ๋จ์ง์๊ณ ์ฌ์ฉ์๊ฐ ๋ง๋ 404.html๋ก ์ด๋์ํค๋ ๋ฐฉ๋ฒ.
package org.demo.springdemo.board.controller;
@ControllerAdvice
@Log4j2
public class BoardControllerAdvice { // ์กด์ฌํ์ง์๋ BNO๋ฅผ ์ฃผ์์ฐฝ์ ์
๋ ฅํ์๋
@ExceptionHandler(NoSuchElementException.class)
public String notFound(NoSuchElementException e) {
log.error("not found : "+e.getMessage());
return "redirect:/404.html";
}
}
package org.demo.springdemo.board.controller.interceptor;
@Log4j2
public class ViewCntInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle-------------");
int value = (int)(Math.random()*100);
log.info("-----------------" + value);
Cookie viewCookie = new Cookie("view", "123" + value);
viewCookie.setMaxAge(60*60*24);
viewCookie.setPath("/");
response.addCookie(viewCookie);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle-------------");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion-------------");
}
}
๐ preHandle : ์์ฒญ์ด ์ปจํธ๋กค๋ฌ์ ํธ๋ค๋ฌ ๋ฉ์๋์ ๋๋ฌํ๊ธฐ ์ ์ ํธ์ถ, ์์ฒญ์ฒ๋ฆฌ์ ์ ์ฌ์ ์์ ์ ์ํํ๋ฏ๋ก ํด๋น ๊ตฌ๊ฐ์์ cookie๋ฅผ ์์ฑํ๋ค.
@GetMapping("/read/{bno}")
public String read(@CookieValue("view")String viewValue, @PathVariable("bno") Long bno, Model model) {
log.info("Reading board: "+bno);
log.info("viewValue: "+viewValue);
boolean existed = false;
if(viewValue != null){
existed = Arrays.stream(viewValue.split("%")).anyMatch(str -> str.equals(bno+""));
}
if(!existed) {
log.info("View Count... update........................");
}
Optional<BoardReadDTO> result = boardService.get(bno);
BoardReadDTO boardReadDTO = result.orElseThrow();
model.addAttribute("board", boardReadDTO);
return "/board/read";
}
๐ @CookieValue("view")๋ฅผ ํตํด view๋ผ๋ ํค๋ฅผ ๊ฐ์ง cookie๊ธ ๊ฐ์ ์ฝ์ด๋ด๊ณ , viewValue๋ cookie๊ฐ % ๋ก ์ฐ๊ฒฐ๋์ด์๋ ํํ์ด๋ค.
๐ %๋ก ์ด์ด์ ธ์๋ ์ฟ ํค๋ฅผ ๋๋์ด ๊ฒ์๊ธ ๋ฒํธ์ ๋น๊ตํ์ฌ ์ด๋ฏธ ๋ณธ ๊ฒ์๊ธ์ธ์ง ํ๋จํ๊ณ , existed๊ฐ false์ธ๊ฒฝ์ฐ์ ์กฐํ์๋ฅผ ์ ๋ฐ์ดํธ ํด์ผํ๋ค.
๐ Optional์ธ result๊ฐ์ service์ get๋ฉ์๋๋ฅผ ๋ฃ์ ๊ฐ์ ํ ๋นํ๋ค์ ์ด result๊ฐ ์กด์ฌํ๋ result์ผ๋๋ง ์ค์์๊ธฐ ๋๋ฌธ์ orElseThorw๋ฅผ ์ฌ์ฉํ๊ฒ์ด๊ณ , BoardReadDTO์ ๋ฃ์ด์ฃผ๋ ์ด์ ๋ ๋์ค์ HTML์์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ Optional์ ํํ๋ก ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก DTO์ ํํ๋ก ๋ณํ์ ์์ผ์ฃผ๋๊ฒ์ด๋ค.
insert into tbl_reply (bno,reply, replyer)
values (100,'Reply...','user00')
;
๐ 100๋ฒ๊ฒ์๊ธ์ Reply...๋ผ๋ ๋๊ธ์ user00์ด๋ผ๋ ์ฌ๋์ด ๋ฌ์๋ค.
create table tbl_reply_favorite (
rno int not null,
mid varchar(50) not null,
regDate timestamp default now()
);
๐ tbl_reply_favorite ํ ์ด๋ธ์์ฑ
ALTER table tbl_reply_favorite
add constraint pk_reply_favorite
primary key (rno,mid)
;
๐ favoriteํ
์ด๋ธ์๋ ๋ณตํฉpk๋ฅผ ๊ฐ์ง๋๋ฐ, rno์mid์ด๋ค.
๐ ์ฌ๊ธฐ์ rno๋ ๋๊ธ์ ์์์ด๊ณ ,mid๋ ์ข์์๋ฅผ ๋๋ฅธ ์ฌ๋์ด๋ค.
insert into tbl_reply_favorite (rno,mid) values (1,'r1');
insert into tbl_reply_favorite (rno,mid) values (1,'r2');
insert into tbl_reply_favorite (rno,mid) values (1,'r3');
insert into tbl_reply_favorite (rno,mid) values (1,'r4');
insert into tbl_reply_favorite (rno,mid) values (2,'r1');
insert into tbl_reply_favorite (rno,mid) values (2,'r2');
๐ ๋๋ฏธ๋ฐ์ดํฐ๋ฅผ ํตํด์ 100๋ฒ๊ฒ์๊ธ์ ๋ํด์, 1๋ฒ๋๊ธ์๋ r1,r2,r3,r4์ธ์์ด ์ข์์๋ฅผ ๋๋ ๊ณ 2๋ฒ๋๊ธ์๋ r1,r2์ธ์์ด ์ข์์๋ฅผ ๋๋ฅธ์ํ์ด๋ค.
select rno, min(reply), min(replyer), count(fno), sum(choice)
from(
select reply.rno rno, reply, replyer, fa.rno fno,
if(fa.mid = 'r1',1,0) choice
from
tbl_reply reply left join tbl_reply_favorite fa on reply.rno = fa.rno
where
reply.bno = 100
) r1
group by rno
๐ ํ์ฌ ์ฃผ์ฒด๋ r1์ด ์ฃผ์ฒด๋ผ๊ณ ๊ฐ์ ํ๊ณ ๋ณธ๋ค.
๐ ์ด๋ ํด๋น sql์ ์ฌ์ฉํ๋ค๋ฉด, ์ฐ์ ๋๊ธ์ ์ ์ฒด์ธ rno, ๋๊ธ์ ๋ด์ฉ, ๋๊ธ์์ฑ์, ์ข์์๋ฅผ ๋๋ฅธ์ฌ๋์ ์ธ์์, ๋ณธ์ธ์ด ์ข์
์๋ฅผ ๋๋ ๋์ง ํ์ธ ์ ์ฒซ์งธ์ค select๋ก ์์์๋ค.
๐ ํท๊ฐ๋ฆด์์๋ ๋ถ๋ถ์ด sum(choice)์ธ๋ฐ, ์ฐ์ ๋ด๋ถ sql๋ฌธ์ ๋ณด๋ฉด if๋ฌธ์ด ๋ณด์ด๋๋ฐ, ๋ง์ฝ fa์ mid๊ฐ r1์ผ๋ 1์ด๊ณ r1์ด ์๋๋ฉด 0 ์ด๋ผ๊ณ ํ์, ๊ทธ๋, sum๊ฐ์ด 1 ์ด๋ผ๊ณ ํ๋ฉด r1์ด ํ๋ฒ์ ํฌํจ๋์ด์๋ค๋ ๋ง์ด๋๊น ํด๋น rno์๋ ๋ด๊ฐ ์ข์์๋ฅผ ๋๋ฅธ ํ์๊ฐ ์๋ค๋ ๋ง์ด ๋๋๊ฒ์ด๋ค.
๐ ์ด๋ ๊ฒ sum์ ์ด์ฉํด์ sql๋ฌธ์ ์ง ์ด์ ๋ group by ๋ฅผ ํตํด ํ๋์ ๋๊ธ์ ๋ํด ์ฌ๋ฌ ์ข์์๊ฐ ์๋๋ฐ, ๊ทธ๊ฒ์ ํ์ค๋ก ํตํฉ์ํค๊ธฐ ์ํด์ ์งํฉ์ ๋๋์ผ๋ก ๋ง๋ค์๊ธฐ ๋๋ฌธ์ด๋ค.
๐ ํ์ฌ listํ์ด์ง๋ ํฌ๊ฒ 4๊ฐ์ง๋ก ๊ตฌ๋ถ๋๋ค.
๐ ํ์ด์ง ๋ฒํผ์ ๋๋ฅด๋ pagination, ๊ฒ์๋ฒํผ์ ๋๋ฅด๋ searchBtn, ์ด๊ธฐํ๋ฒํผ์ธ claerBtn, ๊ฐ๊ฐ์ ๊ฒ์๋ฌผ๋ฒํผ์ธ table๋ก ๋๋์ด์ง๋ค
const {page, size, type, keyword} = [[${pageRequest}]]
console.log(page,size,type,keyword)
const selectTag = document.querySelector("select[name='type']")
const keywordInput = document.querySelector("input[name='keyword']")
if(type){
selectTag.value=type
}
const bno = [[${bno}]]
const result = [[${result}]]
const myModal = new bootstrap.Modal(document.querySelector('#exampleModal'), null)
if(bno){
myModal.show()
}
๐ ์ฒซ๋ฒ์งธ๋ ๊ตฌ์กฐํ ๋น์ ํตํด pageRequest์ ๊ฐ์ page, size, type, keyword๋ก ๊ฐ๊ฐ ๋ฐ๊ณ ์๋ค.
๐ selectTag์ keywordInput์ select์์ฑ์ค์ name๊ฐ์ด type์ธ๋ถ๋ถ์ ์ฐธ์กฐํ๋ selectTag์ด๊ณ , input์์ฑ์ค์ name๊ฐ์ด keyword์ธ ๋ถ๋ถ์ ์ฐธ์กฐํ๋ keywordInput์ด ์๋ค.
๐ ์ด๋ type์ด ์์๋ type์ select์ด๋ฏ๋ก ์ ํํ type์ ํ ๋น๋ฐ๊ณ ์๋ค.
๐ Mymodal์์๋ modal์ querySelect๋ฅผ ํตํด ์ฐธ์กฐํ๊ณ , ๋ฐ์์จ bno๊ฐ ์์ ๊ฒฝ์ฐ์ modal์ฐฝ์ ๋์ด๋ค
document.querySelector(".pagination").addEventListener("click", e=> {
e.preventDefault()
e.stopPropagation()
const target = e.target
const pageNum = target.getAttribute("href")
if(!pageNum){
return;
}
let formStr = `
<form id="tempForm" action="/board/list">
<input type="hidden" name="page" value='${pageNum}'>`
if(type){
formStr += `<input type="hidden" name="type" value='${type}'>`
}
if(keyword){
formStr += `<input type="hidden" name="keyword" value='${keyword}'>`
}
formStr += `</form>`
document.querySelector("#tempDiv").innerHTML = formStr
document.querySelector("#tempForm").submit()
},false)
๐ ์ฐ์ ํ์ด์ง ๋ฒํผ์ ๋๋ ์๋์ ํด๋นํ๋ ์ด๋ฒคํธ๋ฅผ ๊ฑธ์ด์ฃผ๊ณ , ๋ฒํผ์ ๋๋ ์๋ href์ ์์ฑ์ ๊ฐ๊ณ ์ค๊ฒ ๋๋ฉด pageNum์ ๊ฐ์ ธ์ค๊ฒ ๋๋ค.
๐ pageNum์ด ์์ผ๋ฉด return์ผ๋ก ๋๋๊ฒ ๋๊ณ , formํ๊ทธ๋ฅผ ํตํด page๋ฅผ hidden์ผ๋ก board/list์ ๋๊ธฐ๊ฒ ๋๋ค. ์ดํ์๋ ํ์ด์ง๋ฅผ ๋๊ฒผ์๋๋ ๊ธฐ์กด์ ๊ฒ์ํด๋๋ type๊ณผ keyword๋ฅผ ๋ฌผ๊ณ ์๊ธฐ ์ํด์ type๊ณผ keyword๊ฐ ์์๊ฒฝ์ฐ์ formStr๋ก ๋ฌถ์ด tempDiv์ innerHTML์ผ๋ก ๋ณด๋ด๊ฒ๋๋ค.
๐ ์ฌ๊ธฐ์ tempDiv๋ html์ ๊ฐ์ฅ ์ธ๋ถ์ ์กด์ฌํ๋ฏ๋ก ๋ค๋ฅธ modal์ ์ํฅ์ ์ฃผ์ง์๊ณ ๊ฐ์ ์ ๋ฌํ ์์๋ค.
๐ ์ดํ์ tempForm์ผ๋ก submit์ ํ๋๋ฐ ๊ฒฝ๋ก๋ ์๊น์ ๋๊ฐ์ board/list์ด๋ค.
document.querySelector(".searchBtn").addEventListener("click", e=> {
e.preventDefault()
let formStr = `
<form id="tempForm" action="/board/list">
<input type="hidden" name="page" value="1">` // ๊ฒ์์ ํญ์ 1ํ์ด์ง๋ก
if(keywordInput.value && selectTag.value){
formStr += `<input type="hidden" name="type" value='${selectTag.value}'>`
formStr += `<input type="hidden" name="keyword" value='${keywordInput.value}'>`
}
formStr+= `</form>`
document.querySelector("#tempDiv").innerHTML = formStr
document.querySelector("#tempForm").submit()
})
๐ ํด๋น ๋ถ๋ถ์ select์ธ type์ ์ ํ๊ณ keyword๋ฅผ ์ ๋ ฅํ ์ดํ์ search ๋ฒํผ์ ๋๋ฅด๊ฒ๋ ๊ฒฝ์ฐ์ด๋ค.
๐ ์ฐ์ form์ ๊ฒฝ๋ก๋ ์๊น์ ๋๊ฐ๊ณ , ๋ค๋ฅธ๋ถ๋ถ์ value๊ฐ 1 ์ด๋ผ๋๊ฒ์ธ๋ฐ, ์๊น๋ PageNum ์ด์์ง๋ง, ์ง๊ธ์ ๊ฒ์์ ํ๊ฒ๋๋ฉด ํญ์ 1๋ฒํ์ด์ง๋ก ๊ฐ์ผํ๋ฏ๋ก 1์ value๊ฐ์ผ๋ก ์ฃผ์๋ค.
๐ ๊ทธ๋ฆฌ๊ณ keyword์ selectTag๊ฐ ์์๊ฒฝ์ฐ์ hidden๊ฐ์ผ๋ก ๋ฃ์ด์ค์์๋ฐ.
document.querySelector(".clearBtn").addEventListener("click", e=> {
e.preventDefault();
self.location="/board/list"
})
๐ ํด๋น๋ฒํผ์ ๋๋ฅด๊ฒ๋๋ฉด /board/list๋ก ์๋ก๊ณ ์นจ์ ํ๊ธฐ๋๋ฌธ์ url์ ๋ด๊ฒจ์๋ page, keyword, type๋ฑ์ด ์์ด์ง๋ฏ๋ก ๋ฌผ๊ณ ์๋ ๊ฐ ์ญ์ ์์ด์ง๋ค.
document.querySelector(".table").addEventListener("click", e=>{
e.preventDefault()
e.stopPropagation()
const target = e.target
const bno = target.getAttribute("data-bno") // bno์ถ์ถ
if(!bno){return}
let formStr = `
<form id="tempForm" action="/board/read/${bno}">
<input type="hidden" name="page" value='${page}'>`
if(type){
formStr += `<input type="hidden" name="type" value='${type}'>`
}
if(keyword){
formStr += `<input type="hidden" name="keyword" value='${keyword}'>`
}
formStr+= `</form>`
document.querySelector("#tempDiv").innerHTML = formStr
document.querySelector("#tempForm").submit()
},false)
๐ ํด๋น ๋ถ๋ถ์ table๋ก ๊ฒ์๊ธ์ ์ ํํ์๋ ๊ฒ์๊ธ์ read๋ก ๋์ด๊ฐ๊ฒ ๋๋ค.
์ฐ์ getAttribute๋ฅผ ํตํด ์ค์ ํด๋ data-bno๊ฐ์ ์ถ์ถํ๊ณ bno๊ฐ ์์ผ๋ฉด return๋๋ค.
๐ bno๊ฐ ์์๊ฒฝ์ฐ์ /board/read/${bno}์ ๊ฒฝ๋ก๋ก ์ด๋๋๊ณ , type๊ณผ keyword๊ฐ ์์๊ฒฝ์ฐ์ ์ญ์ ๋ฌผ๊ณ ๊ฐ๋ค.
Filter : ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฒญ๊ณผ ์๋ต์ ๊ฐ๋ก์ฑ๋ ์ปดํฌ๋ํธ
Interceptor : spring MVC์ ๊ฐ์ ํ๋ ์์ํฌ์์ ์์ฒญ ์ฒ๋ฆฌ ํน์ ์์ ์ ๊ฐ์ ํ ์์๋ ๋งค์ปค๋์ฆ
AOP : ํต์ฌ ๋น์ง๋์ค ๋ก์ง๊ณผ ๋ณ๋๋ก ๊ณตํต์ ์ธ ๊ด์ฌ์ฌํญ(๋ก๊น , ํธ๋์ญ์ )
