23-02-08) spring 9강 - 게시판 구현 2 (수정, 삭제, 조회수-쿠키 , 이전글/다음글 기능) , MySQL 쿼리문 limit 기능, 페이징 구현(creteria 클래스)
# 컨트롤러에 요청(ex- /notice_view , notice_modify)은 다르게 오지만 처리결과는 같은 때 한번에 묶어서 처리하는 법
(그냥 이런것도 있구나 하고 넘어가기)
컨트롤러에서
아래와 같이 두 페이지에대한 처리가 똑같다면 한번에 묶어서 처리가 가능.
//상세화면
@RequestMapping("/notice_view")
public String notice_view(@RequestParam("tno") int tno,
Model model) {
//클릭한 글 번호에 대한 내용을 조회
TripVO vo = tripService.getContent(tno);
model.addAttribute("vo", vo);
return "trip/notice_view";
}
}
//수정화면
@RequestMapping("/notice_modify")
public String notice_modify(@RequestParam("tno") int tno,
Model model) {
//클릭한 글 번호에 대한 내용을 조회
TripVO vo = tripService.getContent(tno);
model.addAttribute("vo", vo);
return "trip/notice_modify";
}
메서드 반환부분이 void라는거는 들어온 경로 그대로 보내준다는 의미
//수정, 상세 화면이 완전 동일하다면
@RequestMapping({"notice_view", "notice_modify"})
public void notice_view(@RequestParam("tno") int tno,
Model model) {
TripVO vo = tripService.getContent(tno);
model.addAttribute("vo", vo);
}
# 글수정 기능
- 글을 수정하고자 하면 해당 글을 데이터베이스에서 식별하기 위해서 pk (데이터베이스에서 글의 번호)값이 필요.
화면에서는 이 pk값을 넘겨주어야 함.
-수정페이지에서 수정 버튼을 눌르고 글내용이 변경된 수정페이지로 다시 넘어갈 때에도
해당 글에대한 pk값( tno )이 꼭 넘어와야 함.
하지만 수정페이지에서 화면에는 이 tno 값이 보이면 안되기 때문에
<input type="hidden" name="tno" value="${vo.tno }"/>
이렇게 히든 태그를 이용해서 tno 값을 넘겨주어야 함.
( form 태그를 통해 post방식으로 값을 넘겼음)
*컨트롤러
//글수정
@RequestMapping(value="/modifyForm", method = RequestMethod.POST)
public String modifyFrom(TripVO vo,
RedirectAttributes ra) {
//업데이트작업 - 화면에서는 tno가 필요하기 때문에 hidden태그를 이용해서 넘겨주세요.
int result = tripService.noticeModify(vo);
String msg = result == 1 ? "문의사항이 수정되었습니다" : "수정에 실패했습니다";
ra.addFlashAttribute("msg", msg);
return "redirect:/trip/notice_view?tno=" + vo.getTno();
}
* 서비스 , mapper 부분
- 메서드 :
public int noticeModify(TripVO vo); //수정
- mapper 구현부 ( 쿼리문 )
<update id="noticeModify" parameterType="TripVO">
update trip
set tripdate = #{tripdate},
title = #{title},
content = #{content}
where tno = #{tno}
</update>
#글삭제 기능
*삭제는 반드시 post 방식으로 데이터(tno - 글의 pk값)를 전달해야됨.
*notice_view (상세화면 페이지)
- form 방식으로 tno를 전달하기 위해 form 태그로 내용들을 감싸줌.
★ 중요 포인트 : 자바스크립트 부분에서 form 태그를 직접 다루기 위해서 name 값을 주었음.
form 태그는 document객체의 자식태그로서 document객체를 통해서 접근가능 ! ★
- tno (글의 pk값)가 화면에 직접 보이면 안되기 때문에 hidden 태그로 값을 담아줌.
- 글 삭제 버튼(a태그) 를 클릭하면 자바스크립트에서 noticeDelete() 를 실행하게 함.
- 하단부에 script 태그에 자바스크립트 noticeDelete 메서드 구현
1. a 태그의 고유 특성인 누르면 화면을 바로 넘어가는 것을 강제로 실행하지 않게 처리해줌
event.preventDefault();
2. document.폼태그의name값.submit() ; 을 함으로써 데이터를 가지고 post방식으로 화면을 넘어가게 함.
이때 컨트롤러에 보내는 요청은 form 태그 action 속성의 값인 deleteForm
<!--
삭제시는 post로 동작하는데
hidden이용해서 삭제에 필요한 키값을 전달해줍니다.
js를 이용해서 form을 전송
-->
<form action="deleteForm" method="post" name="actionForm">
<input type="hidden" name="tno" value="${vo.tno }">
내용들
<p class="btn_line txt_right">
<a href="javascript:;" class="btn_bbs" onclick="noticeDelete()">글삭제</a>
</p>
내용들
</form>
<!-- //bodytext_area -->
<script>
function noticeDelete() {
//a링크 고유이벤트중지
event.preventDefault();
//폼형식으로 삭제 - document.form이름
if(confirm("정말 지울거에요?ㅠㅠ")) {
document.actionForm.submit();
}
}
</script>
* 구현할 메서드
public int noticeDelete(int tno); //삭제
삭제 성공시 반환값이 1이 나오는데 이를 반환하도록 함. (반환타입 int)
- mapper 에서 구현
<delete id="noticeDelete" parameterType="int">
delete from trip where tno =#{tno}
</delete>
*컨트롤러
//글삭제
@RequestMapping(value="deleteForm", method = RequestMethod.POST)
public String deleteForm(@RequestParam("tno") int tno,
RedirectAttributes ra) {
/*
* service, mapper 에는 noticeDelete 메서드로 삭제를 진행
* 삭제 이후에는 list화면으로 이동해주면 됩니다.
*/
int result = tripService.noticeDelete(tno);
String msg = result == 1 ? "삭제 되었습니다" : "삭제에 실패했습니다";
ra.addFlashAttribute("msg", msg);
return "redirect:/trip/notice_list";
}
# 조회수 기능 ( 쿠키 관련 내용도 있음)
* 메서드
public void upHit(int tno); //조회수
* mapper.xml 에서 메서드 구현
<update id="upHit">
update trip
set hit = hit + 1
where tno = #{tno}
</update>
*컨트롤러
- 상세페이지를 들어올 때 조회수가 오르게됨.
그러나 조회수가 중복해서 계속 올라가지 않는 방법도 생각해봐야함.
-쿠키를 통한 조회수 제어-
매개변수로 HttpServletResponse 객체와 HttpServletRequest 객체가 필요.
컨트롤러에서 만든 쿠키를 담아서 페이지로 보내기 위해 Response 이용
화면에서 넘어온 쿠키를 갖고 작업을 하기 위해 Request를 받는것.
HttpServlet 애들 참고 링크:
https://duckgugong.tistory.com/23
//상세화면
@RequestMapping("/notice_view")
public String notice_view(@RequestParam("tno") int tno,
Model model,
HttpServletResponse response,
HttpServletRequest request) {
//클릭한 글 번호에 대한 내용을 조회
TripVO vo = tripService.getContent(tno);
model.addAttribute("vo", vo);
//조회수 - Cookie or 세션 이용해서 조회수 중복 방지
tripService.upHit(tno);
Cookie cookie = new Cookie("key", "1");
cookie.setMaxAge(30);
response.addCookie(cookie);
//이전글 다음글
ArrayList<TripVO> list = tripService.getPrevNext(tno);
model.addAttribute("list", list);
return "trip/notice_view";
}
# 이전글 , 다음글 기능 추가
*메서드
public ArrayList<TripVO> getPrevNext(int tno); //이전글, 다음글
이전글과 다음글의 데이터를 찾을 기준을 잡기위해서 현재 글의 pk값인 tno 를 매개변수로 받음.
반환타입은 TripVO 타입을 담는 ArrayList -> 글 2개를 담아서 반환.
* 잠깐 보고가기
-MySQL 에서만 제공하는 limit 기능 ★★★
조건에 맞는 데이터 행들 중에서 지정한 숫자만큼의 데이터만 보이도록 해줌.
select tno from trip where tno < 3 order by tno desc limit 1;
select tno from trip where tno > 3 limit 1;
select * from trip
where tno in ((select tno from trip where tno < 3 order by tno desc limit 1),
(select tno from trip where tno > 3 limit 1));
where tno > 3 limit 1; 의미해석:
tno가 3보다 큰 데이터들 중에서 첫번째만 가져온다.
* TripMapper.xml - > 메서드 구현 (sql 문 부분)
- 쿼리문 안에 있는 < 부등호는 xml , html 에서 태그로 인식되어
오류를 일으킴.
쿼리문을 <![CDATA[ 쿼리문 ]]> 이렇게 안에 들어가게 해줌으로써
[] 안에 있는 내용을 순수한 문자열로 인식하도록 해줘야함.
<!-- 이전글, 다음글 -->
<!-- xml or html에서 부등호는 태그로 인식이 되는데, CDATA는 순수한 문자열 형태로 인식을 시킴 -->
<select id="getPrevNext" resultType="TripVO">
<![CDATA[
select * from trip
where tno in ((select tno from trip where tno < #{tno} order by tno desc limit1),
(select tno from trip where tno > #{tno} limit 1))
]]>
</select>
* 컨트롤러 (이전글 다음글 부분 보기)
- 반환받은 list를 모델에 담아서 상세화면 페이지로 다시 넘어감.
//상세화면
@RequestMapping("/notice_view")
public String notice_view(@RequestParam("tno") int tno,
Model model,
HttpServletResponse response,
HttpServletRequest request) {
//클릭한 글 번호에 대한 내용을 조회
TripVO vo = tripService.getContent(tno);
model.addAttribute("vo", vo);
//-----------------------------------
//이전글 다음글
ArrayList<TripVO> list = tripService.getPrevNext(tno);
model.addAttribute("list", list);
//-----------------------------
//조회수 - Cookie or 세션 이용해서 조회수 중복 방지
tripService.upHit(tno);
// Cookie cookie = new Cookie("key", "1");
// cookie.setMaxAge(30);
// response.addCookie(cookie);
return "trip/notice_view";
}
*상세화면 페이지 (notice_view)
- <c:if test= 실행 조건 써주는 부분>
- list 의 길이를 알아야 조건문의 처리를 다르게 해줄 수 있음.
jstl 에서 list의 길이를 얻어오는 법
1. 상단부에 jstl 선언
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
2. ${fn:length(list)} -> list의 길이 반환
- 상세화면이 맨 처음글 , 맨 마지막글 이라면 리스트에 담을 수 있는 글은 1개 등등
상황에 맞게 조건문을 잘 걸어줘야함.
글의 pk (tno)값 을 기준으로 이전글과 다음글을 잘 구분해서 조건문을 작성해주도록 하자. (생각 잘 해야하는 부분!!)
<ul class="near_list mt20">
<!--
1. 글이 2개인 경우 - 이전글 < 현재글 인 경우 이전글
2. 글이 1개인 경우 - 리스트 길이가 1이고, 글 < 현재글 인 경우는 다음글이 없음
3. 글이 0개인 경우 - list 가 없으니 애초에 반복문이 돌지 않을것.
-->
<c:forEach var="data" items="${list }">
<c:if test="${fn:length(list) == 1 and data.tno < vo.tno }">
<li><h4 class="prev">다음글</h4>은 없습니다</li>
</c:if>
<c:if test="${data.tno > vo.tno }">
<li><h4 class="prev">다음글</h4><a href="notice_view?tno=${data.tno }">추석 연휴 티켓/투어 배송 및 직접 수령 안내${data.tno }</a></li>
</c:if>
<c:if test="${data.tno < vo.tno }">
<li><h4 class="next">이전글</h4><a href="notice_view?tno=${data.tno }">이번 여름 휴가 제주 갈까? 미션 투어 (여행경비 50만원 지원)${data.tno }</a></li>
</c:if>
<c:if test="${fn:length(list) == 1 and data.tno > vo.tno }">
<li><h4 class="prev">이전글</h4>은 없습니다</li>
</c:if>
</c:forEach>
<!--
<li><h4 class="prev">다음글</h4><a href="javascript:;">추석 연휴 티켓/투어 배송 및 직접 수령 안내</a></li>
<li><h4 class="next">이전글</h4><a href="javascript:;">이번 여름 휴가 제주 갈까? 미션 투어 (여행경비 50만원 지원)</a></li>
-->
</ul>
<페이징 구현>
아래 빨간부분과 같은 기능
# 페이지별 보여줄 글을 가져오는 쿼리문 (MySQL)
*MySQL에서는 limit 라는 특별한 기능을 제공해줌
- limit 지정해준 숫자만큼의 데이터를 가져와 줌.
아래에서 limit 사용에대한 해석
limit 0, 10 -> 1번부터 시작, 10개의 데이터
limit 10, 10 -> 11번부터 시작, 10개의 데이터
select *
from trip order by tno desc limit 0, 10; -- 1번에서 10개의 데이터
select *
from trip order by tno desc limit 10, 10; -- 11번에서 10개의 데이터
select *
from trip order by tno desc limit 10, 50; -- 11번에서 50개의 데이터
# criteria 클래스
* 페이징을 처리하는 기준을 다룸.
- mysql의 limit 함수 쿼리문에
limit 시작번호 , 가져올데이터 개수 이 두가지 부분의 값을 처리해줌.