55일차(2)/Spring(16) : 글 상세보기, 이전글/다음글 불러오기
- 글 상세보기 (detail)
- 상세보기에서 이전글, 다음글 불러오기
컨트롤러
@RequestMapping("/cafe/detail")
public String detail(HttpServletRequest request) {
service.getDetail(request);
return "cafe/detail";
}
- getDetail 메소드를 보면 request를 필요로 하므로 메소드의 인자로 넣어준다.
- String으로 수정함!
- Service 메소드 안에서 request를 찾아다 쓰면 된다.
서비스
@Override
public void getDetail(HttpServletRequest request) {
//자세히 보여줄 글번호를 읽어온다.
int num=Integer.parseInt(request.getParameter("num"));
//조회수 올리기
cafeDao.addViewCount(num);
//글 하나의 정보를 얻어온다.
CafeDto dto=cafeDao.getData(num);
//request scope에 글 하나의 정보 담기
request.setAttribute("dto", dto);
}
- 번호 정보를 넘겨주면 글 정보를 가져와주는 메소드가 있다(getData)
- 사용해서 dto에 담아주기!
detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/cafe/detail.jsp</title>
</head>
<body>
<div class="container">
<%-- 만약 다음 글(더 최신글)의 번호가 0이 아니라면(다음 글이 존재한다면) --%>
<c:if test="${dto.nextNum ne 0 }">
<a href="detail?num=${dto.nextNum }">다음글</a>
</c:if>
<%-- 만약 이전 글(더 옛날글)의 번호가 0이 아니라면(이전 글이 존재한다면) --%>
<c:if test="${dto.prevNum ne 0 }">
<a href="detail?num=${dto.prevNum }">이전글</a>
</c:if>
<h3>글 상세 보기</h3>
<table>
<tr>
<th>글번호</th>
<td>${dto.num}</td>
</tr>
<tr>
<th>작성자</th>
<td>${dto.writer}</td>
</tr>
<tr>
<th>제목</th>
<td>${dto.title}</td>
</tr>
<tr>
<th>조회수</th>
<td>${dto.viewCount}</td>
</tr>
<tr>
<th>작성일</th>
<td>${dto.regdate}</td>
</tr>
<tr>
<td colspan="2">
<div>${dto.content}</div>
</td>
</tr>
</table>
<%-- 만일 글 작성자가 로그인된 아이디와 같다면 수정, 삭제 링크를 제공한다. --%>
<c:if test="${sessionScope.id eq dto.writer }">
<a href="updateform?num=${dto.num}">수정</a>
<a href="javascript:" onclick="deleteConfirm()">삭제</a>
<script>
function deleteConfirm(){
const isDelete=confirm("이 글을 삭제 하겠습니까?");
if(isDelete){
location.href="delete?num=${dto.num}";
}
}
</script>
</c:if>
</div>
</body>
</html>
- c:if를 사용해서 글 작성자가 같다면 삭제 링크를 내보내준다.
- 이 detail 화면에서 목록의 다음글을 누르면 이동할 수 있도록 하려면?
- 이전글, 다음글도 select를 해야한다.
- 이렇게 검색해서 봤을 때, 한칸 위, 한칸 아래 글을 select 할 수 있게 해야한다.
- select만 할수있다면 그 정보를 dto에 담아버리면 된다.
- 같은 row에 select될수있다면 dto에 담는것은 쉬움
- 오라클에는 LAG() 이라는 함수가 있다. 뒤처지는 이라는 뜻!(게임하다가 렉걸렸다고 할때...)
LAG(num, 1, 0)
- 번호num에 대해서 내림차순 정렬했을때 하나 뒤처지는 것! 을 select하는 것이다.
- num을 가져오고 없으면 0을 가져오기(기본값 0)
- 이렇게 값을 불러와준다.
- 하나 앞서는 값을 가져오는 LEAD() 라는 함수도 있다.
SELECT num, writer, title,
LAG(num, 1, 0) OVER (ORDER BY num DESC) AS nextNum,
LEAD(num, 1, 0) OVER (ORDER BY num DESC) AS prevNum
FROM board_cafe
ORDER BY num DESC
- 동일한 row에 다음글 번호와 이전글 번호가 존재하도록 했다.
- 몇칸 뒤처졌는지, 몇칸 앞섰는지를 알아내는 함수
- 맨앞, 맨끝과 같이 양쪽 끝에있는 글은 기본값 0을 가지도록 넣어둔 것이다.
- from , where, select 순서로 실행되므로 LAG, LEAD로 나타난 결과에 대해서 다시 한번 select를 해야 한다.
- 결과 테이블을 만들어놓고 이 결과에 대해서 select를 다시 해주어야 한다.
- dto에 이미 필드를 만들어놓았다.
mapper에 넣어준다.
- LAG, LEAD 조건 추가시 쉽표 주의
- 별칭을 nextNum, prevNum 이라고 작성했기때문에 dto에 알아서 잘 담긴다.(필드명과 같기 때문에)
<select id="getData" parameterType="int" resultType="cafeDto">
SELECT result1.*
FROM
(SELECT num,writer,title,content,viewCount,TO_CHAR(regdate, 'YY.MM.DD HH24:MI') AS regdate,
LAG(num, 1, 0) OVER (ORDER BY num DESC) nextNum,
LEAD(num, 1, 0) OVER (ORDER BY num DESC) prevNum
FROM board_cafe) result1
WHERE num=#{num}
</select>
- 이렇게 서브쿼리로 만들어주어야 한다.(result1을 한번더 정렬)
- 이제 detail 에서 이전글, 다음글 클릭시 이동할수있도록 수정하기
detail.jsp(최종)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/cafe/detail.jsp</title>
</head>
<body>
<div class="container">
<%-- 만약 이전 글(더 옛날글)의 번호가 0이 아니라면(이전 글이 존재한다면) --%>
<c:if test="${dto.prevNum ne 0 }">
<a href="detail?num=${dto.prevNum }&condition=${condition}&keyword=${encodedK}">이전글</a>
</c:if>
<%-- 만약 다음 글(더 최신글)의 번호가 0이 아니라면(다음 글이 존재한다면) --%>
<c:if test="${dto.nextNum ne 0 }">
<a href="detail?num=${dto.nextNum }&condition=${condition}&keyword=${encodedK}">다음글</a>
</c:if>
<%-- 만일 겁색 키워드가 있다면 --%>
<c:if test="${not empty keyword }">
<p>
<strong>${condition }</strong> 조건
<strong>${keyword }</strong> 검색어로 검색된 내용 자세히 보기
</p>
</c:if>
<h3>글 상세 보기</h3>
<table>
<tr>
<th>글번호</th>
<td>${dto.num}</td>
</tr>
<tr>
<th>작성자</th>
<td>${dto.writer}</td>
</tr>
<tr>
<th>제목</th>
<td>${dto.title}</td>
</tr>
<tr>
<th>조회수</th>
<td>${dto.viewCount}</td>
</tr>
<tr>
<th>작성일</th>
<td>${dto.regdate}</td>
</tr>
<tr>
<td colspan="2">
<div>${dto.content}</div>
</td>
</tr>
</table>
<%-- 만일 글 작성자가 로그인된 아이디와 같다면 수정, 삭제 링크를 제공한다. --%>
<c:if test="${sessionScope.id eq dto.writer }">
<a href="updateform?num=${dto.num}">수정</a>
<a href="javascript:" onclick="deleteConfirm()">삭제</a>
<script>
function deleteConfirm(){
const isDelete=confirm("이 글을 삭제 하겠습니까?");
if(isDelete){
location.href="delete?num=${dto.num}";
}
}
</script>
</c:if>
</div>
</body>
</html>
- 그런데 항상 그 값이 있는 것은 아니다. 맨앞, 맨뒤에서는 0이므로 없다.
- c:if 를 사용해서 0이 아닌 경우에 이전글, 다음글이 나오도록 작성해주기
- 전체 리스트에서 다음글, 이전글로 잘 이동한다.
- 검색된 결과에 대한 이전 글, 다음 글이 나오도록 하기
- 검색어를 사용해서 검색한 후 자세히보기(detail)에 들어가면
그 검색결과에 대해서 이전글,다음글이 나와야 한다! 이 기능을 추가할 것.
- list 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/cafe/list.jsp</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<a href="${pageContext.request.contextPath }/cafe/insertform">새글 작성</a>
<h3>CAFE 게시판 목록 보기</h3>
<table class="table table-striped">
<thead class="table-dark">
<tr>
<th>번호</th>
<th>작성자</th>
<th>제목</th>
<th>조회수</th>
<th>등록일</th>
<th>삭제</th>
</tr>
</thead>
<tbody>
<c:forEach var="tmp" items="${list }">
<tr>
<td>${tmp.num }</td>
<td>${tmp.writer }</td>
<td>
<a href="detail?num=${tmp.num }&condition=${condition}&keyword=${encodedK }">${tmp.title }</a>
</td>
<td>${tmp.viewCount }</td>
<td>${tmp.regdate }</td>
<td>
<c:if test="${tmp.writer eq sessionScope.id }">
<a href="javascript:deleteConfirm(${tmp.num })">삭제</a>
</c:if>
</td>
</tr>
</c:forEach>
</tbody>
</table>
<nav>
<ul class="pagination">
<%--
startPageNum 이 1 이 아닌 경우에만 Prev 링크를 제공한다.
&condition=${condition}&keyword=${encodedK}
--%>
<c:if test="${startPageNum ne 1 }">
<li class="page-item">
<a class="page-link" href="list?pageNum=${startPageNum-1 }&condition=${condition}&keyword=${encodedK}">Prev</a>
</li>
</c:if>
<c:forEach var="i" begin="${startPageNum }" end="${endPageNum }">
<li class="page-item ${pageNum eq i ? 'active' : '' }">
<a class="page-link" href="list?pageNum=${i }&condition=${condition}&keyword=${encodedK}">${i }</a>
</li>
</c:forEach>
<%--
마지막 페이지 번호가 전체 페이지의 갯수보다 작으면 Next 링크를 제공한다.
--%>
<c:if test="${endPageNum lt totalPageCount }">
<li class="page-item">
<a class="page-link" href="list?pageNum=${endPageNum+1 }&condition=${condition}&keyword=${encodedK}">Next</a>
</li>
</c:if>
</ul>
</nav>
<!-- 검색 폼 -->
<form action="list" method="get">
<label for="condition">검색조건</label>
<select name="condition" id="condition">
<option value="title_content" ${condition eq 'title_content' ? 'selected' : '' }>제목 + 내용</option>
<option value="title" ${condition eq 'title' ? 'selected' : '' }>제목</option>
<option value="writer" ${condition eq 'writer' ? 'selected' : '' }>작성자</option>
</select>
<input type="text" name="keyword" placeholder="검색어..." value="${keyword }"/>
<button type="submit">검색</button>
</form>
<c:if test="${not empty condition }">
<p>
<strong>${totalRow }</strong> 개의 자료가 검색 되었습니다.
<a href="list">리셋</a>
</p>
</c:if>
</div>
<script>
function deleteConfirm(num){
let isDelete=confirm("삭제 하시겠습니까?");
if(isDelete){
location.href="delete?num="+num;
}
}
</script>
</body>
</html>
- 자세히보기 페이지도 글 정보만 가져가는게 아니라 검색정보, 검색키워드도 가져가야 한다.
getDetail에서 추가해주기
@Override
public void getDetail(HttpServletRequest request) {
//자세히 보여줄 글번호를 읽어온다.
int num=Integer.parseInt(request.getParameter("num"));
//조회수 올리기
cafeDao.addViewCount(num);
/*
[ 검색 키워드에 관련된 처리 ]
-검색 키워드가 파라미터로 넘어올수도 있고 안넘어 올수도 있다.
*/
String keyword=request.getParameter("keyword");
String condition=request.getParameter("condition");
//만일 키워드가 넘어오지 않는다면
if(keyword==null){
//키워드와 검색 조건에 빈 문자열을 넣어준다.
//클라이언트 웹브라우저에 출력할때 "null" 을 출력되지 않게 하기 위해서
keyword="";
condition="";
}
//CafeDto 객체를 생성해서
CafeDto dto=new CafeDto();
//자세히 보여줄 글번호를 넣어준다.
dto.setNum(num);
//만일 검색 키워드가 넘어온다면
if(!keyword.equals("")){
//검색 조건이 무엇이냐에 따라 분기 하기
if(condition.equals("title_content")){//제목 + 내용 검색인 경우
//검색 키워드를 CafeDto 에 담아서 전달한다.
dto.setTitle(keyword);
dto.setContent(keyword);
}else if(condition.equals("title")){ //제목 검색인 경우
dto.setTitle(keyword);
}else if(condition.equals("writer")){ //작성자 검색인 경우
dto.setWriter(keyword);
} // 다른 검색 조건을 추가 하고 싶다면 아래에 else if() 를 계속 추가 하면 된다.
}
//글 하나의 정보를 얻어온다.
CafeDto resultDto=cafeDao.getData(dto);
//특수기호를 인코딩한 키워드를 미리 준비한다.
String encodedK=URLEncoder.encode(keyword);
//request scope에 글 하나의 정보 담기
request.setAttribute("dto", resultDto);
request.setAttribute("condition", condition);
request.setAttribute("keyword", keyword);
request.setAttribute("encodedK", encodedK);
}
- 넘어오는 keyword 값이 null이라면 빈 문자열을 추가해준다.
- 글 정보가 필요하기 때문에, dto를 전달하는 구조로 만들어서
resultDto라는 변수에 그것을 담는다.
- 그리고 글번호가 아니라 dto를 담는 구조로 수정해야 한다.
cafeDao에 새 메소드를 넣어준다.
- dto에 담은 키워드를 활용한 글 정보 얻어오기 메소드를 추가한다.
(키워드에 부합하는 글 중에서 이전글, 다음글의 글번호도 얻어올 수 있도록)
DaoImpl
@Override
public CafeDto getData(CafeDto dto) {
return session.selectOne("cafe.getData2", dto);
}
- 추가 메소드에 대한 override 해주기
- dto 안에 담긴 키워드를 사용해서 이전글, 다음글을 얻어온다.
- 이제 getData에 사용되는 mapper에 새로운 부분이 추가된다. (검색조건)
- 그러나 경우에 따라 들어갈 수도 있고 들어가지 않을 수도 있는 부분!! (조건부)
WHERE writer LIKE
WHERE title LIKE
WHERE title LIKE OR content LIKE
(검색조건에 따른 WHERE절 추가하기)
<select id="getData2" parameterType="cafeDto" resultType="cafeDto">
SELECT result1.*
FROM
(SELECT num,writer,title,content,viewCount,TO_CHAR(regdate, 'YY.MM.DD HH24:MI') AS regdate,
LAG(num, 1, 0) OVER (ORDER BY num DESC) nextNum,
LEAD(num, 1, 0) OVER (ORDER BY num DESC) prevNum
FROM board_cafe
<include refid="searchCondition"></include>
) result1
WHERE num=#{num}
</select>
- 동적 sql 문을 include를 사용해서 getData2에 넣어주어야한다.
- Dto에서 패러미터값을 받아와서 searchCondition안에 알아서 넣어준다.
- service 메소드의 마지막부분에 추가
- 검색한 값을 request에 다시 담아주기
- detail 수정
- keyword가 넘어오면 이렇게 같이 추가정보를 제공해줄 수도 있다.
- keyword 값이 넘어오지 않을 경우 실행되지 않는 블럭
&condition=${condition}&keyword=${encodedK}
- 이전글, 다음글을 가져올수있도록 경로에 &condition, &keyword를 추가
- 이제 이렇게 b 로 검색한 결과만 모아서 이전글, 다음글로 이동할 수 있다.
'국비교육(22-23)' 카테고리의 다른 글
55일차(4)/Spring Boot(1) : Spring Boot 기초, AOP의 구조 익히기 (0) | 2022.12.26 |
---|---|
55일차(3)/Spring(17) : 게시판 글 삭제, 글 수정 기능 구현 (0) | 2022.12.25 |
55일차(1)/Spring(15) : Cafe 게시판 기능 구현 (목록보기, 새글 작성) (1) | 2022.12.25 |
54일차(2)/Spring(14) : 자료실 파일 다운로드, 삭제, 검색 기능 구현 (0) | 2022.12.22 |
54일차(1)/Spring(13) : 자료실 게시판 만들기, 파일 업로드 기능 구현 (1) | 2022.12.22 |