[1] JSTL - for each속성 사용
varStatus="st": for Each문이 반복될때 관리되는 상태 값을 사용할 수 있게 하는 속성. 상태를 알기위한 변수값만 설정하면 됨
-- 상태값이 st이므로 속성은 st.로 사용함
${st.current}: 반복될 때 사용되고있는 현재 개체
${st.index}: 반복시 인덱스
${st.count}: 반복되는 횟수
${st.first}: 첫번째면 true, 그렇지않다면 false
${st.last}: first와 동일
${st.begin}: 시작하는 인덱스값
${st.end}: 끝나는 인덱스값
${st.step}: 인덱스가 늘어나는 단위. ++과 같은 역할. 2를 넣으면 2씩 증가
<tbody>
<c:forEach var="n" items="${list}" begin="1" end="3"> // 시작과 끝점 설정
list에 있는 내용을 하나씩 꺼내서 n에 담는다
<tr>
<td>${n.id }</td>
<td class="title indent text-align-left"><a href="detail?id=${n.id}">${n.title}</a></td>
<td>${n.writerId}</td>
<td>${n.regdate }</td>
<td>${n.hit }</td>
</tr>
</c:forEach>
</tbody>
<tbody>
<c:forEach var="n" items="${list}" begin="0" end="10" varStatus="st"> // 상태값 설정
<tr>
<td>${st.index} / ${n.id }</td> // 아이디 앞에 인덱스 출력
<td class="title indent text-align-left"><a href="detail?id=${n.id}">${n.title}</a></td>
<td>${n.writerId}</td>
<td>${n.regdate }</td>
<td>${n.hit }</td>
</tr>
</c:forEach>
</tbody>
1-1) JSTL forEach를 사용해서 pager번호 만들기
page값으로 파라미터를 받았을때, 공통 그룹에 속한 숫자끼리의 법칙을 파악한다. (17페이지나 19페이지 모두 16부터 시작)
5로 나눴을때 나머지값을 뺄 경우 모두 15로 시작 > +1을 해야 16으로 들어옴
page(page%5)+1 == page-(page-1)%5
<div class="margin-top align-center pager">
<div>
<span class="btn btn-prev" onclick="alert('이전 페이지가 없습니다.');">이전</span>
</div>
<c:set var="page" value="${(param.p ==null) ? 1 : param.p}" />
<c:set var="startNum" value="${page-(page-1)%5}" /> //파라미터를 받아 시작값으로 설정
<ul class="-list- center">
<c:forEach var="i" begin="0" end="4">
<li><a class="-text- orange bold"
href="?p=${i+startNum}&t=&q=">${i+startNum}</a></li>// 반복문값에 시작값이 더해져서 출력됨
</c:forEach>
</ul>
<div>
<span class="btn btn-next" onclick="alert('다음 페이지가 없습니다.');">다음</span>
</div>
</div>
</main>
👉 http://localhost:8080/notice/list?p=15&t=&q=
1-2) JSTL C:IF로 이전/다음 pager링크만들기
:<c:if test="else문이 없기 때문에 이 조건식에는 해당 식이 충족해야하는 참 조건만 적어준다">
<div class="margin-top align-center pager">
// 페이지값을 얻기 위해 가장 앞으로 땡겨준다
<c:set var="page" value="${(param.p ==null) ? 1 : param.p}" />
<c:set var="startNum" value="${page-(page-1)%5}" />
<c:set var="lastNum" value="23" /> // 마지막값 임의지정
<div>
<c:if test="${startNum-1>0}"> // 꼭 start값을 맨앞(-5)으로 맞출 필요는 없고, 앞페이지 숫자만 나오도록 -1
<a href="?p=${startNum-1}&t=&q=" class="btn btn-prev">이전</a>
</c:if>
<c:if test="${startNum-1<=0}"> // 참이 되는 조건문을 각각 작성. startNum<=1도 가능
<span class="btn btn-prev" onclick="alert('이전 페이지가 없습니다.');">이전</span>
</c:if>
</div>
<ul class="-list- center">
<c:forEach var="i" begin="0" end="4">
<li><a class="-text- orange bold"
href="?p=${i+startNum}&t=&q=">${i+startNum}</a></li>
</c:forEach>
</ul>
<div>
<c:if test="${5+startNum <lastNum }"> // 아래 조건과 배타적관계
<a href="?p=${5+startNum}&t=&q=" class="btn btn-next">다음</a>
</c:if>
<c:if test="${5+startNum >=lastNum }">
<span class="btn btn-next" onclick="alert('다음 페이지가 없습니다.');">다음</span>
</c:if>
</div>
</div>
1-3) JSTL C:forTokens로 첨부파일 목록 출력하기
forTokens: 토큰을 만들어주는걸 기본으로 하면서 반복을 하는 제어 태그
토큰: 내가 원하는 단위로 잘랐을때 잘린 하나의 단위
delims: 문자열 내에서 자르는 기준. delim은 delimiter의 약자로 '구본 문자'를 뜻함
<tr>
<th>첨부파일</th>
<td colspan="3" class="text-align-left text-indent">
<c:forTokens var="fileName" items="${n.files}" delims="," varStatus="st">
//forTokens items로 파일 값을 받고, 변수로 파일명 표시.delims는 데이터에서 파일 구분값
<a href="${fileName}">${fileName}</a> // 변수값(파일명)으로 하이퍼링크화
<c:if test="${!st.last}"> // 상태변수 st를 가지고 조건식에 사용. 마지막값이 아닌 경우만 /로 파일명 구분
/
</c:if>
</c:forTokens>
</td>
</tr>
1-4) JSTL:format 태그로 날짜 형식 변경
* oracle data형식
DATE | 01-JAN-99 |
TIMESTAMP | NLS_TIMETAMP_FORMAT 파라미터에 명시된 값을 따름 |
TIMESTAMP WITH TIME ZONE | NLS_TIMESTAMP_TZ_FORMAT 파라미터에 명시된 값을 따름 |
TIMESTAMP WITH LOCAL TIME ZONE 지역변수에 따라 표현되는 방법이 다름 |
NLS_TIMESTAMP_FORMAT 파라미터에 명시된 값을 따름 SELECT * FROM NLS_DATABASE_PARAMETERS |
//list.jsp
//상단에 태그라이브러리 지시자 import
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
//데이터를 받아 작성일 출력 부분
<c:forEach var="n" items="${list}" begin="0" end="10" varStatus="st">
<tr>
<td>${st.index}/ ${n.id}</td>
<td class="title indent text-align-left"><a href="detail?id=${n.id}">${n.title}</a></td>
<td>${n.writerId}</td>
<td><fmt:formatDate pattern="yy-MM-dd hh:mm:ss" value="${n.regdate}"/></td>
// 패턴과 값 지정
<td>${n.hit }</td>
</tr>
</c:forEach>
❣ 만약 시분초가 12:00:00으로 나올 경우 확인해봐야 할 사항
- Detail Controller와 ListController 클래스에서 (컨트롤러파일) Java.util.Date가 아닌 java.sql.Date를 import했는지 확인
- rs.getTimestamp 메소드가 아닌 rs.getDate메소드를 사용하여 regdate를 rs에서 불러올때 실수가 있었는지 확인 (time stamp가 더 자세한 자료형)
- Oracle에서 regdate컬럼 데이터타입이 timestamp이고, 기본값이 systimestamp이며, 원본 db값에 연월일시분초밀리초까지 입력이 되어있는지 확인
1-5) JSTL:formatNumber 태그로 숫자 형식 변경
//detail.jsp
<th>조회수</th>
<td><fmt:formatNumber value="${n.hit}" /> </td>
// 기본세팅값이 우리가 알고있는 3단위 콤마 3,432
---
<td><fmt:formatNumber type="number" pattern="##,####원" value="${n.hit}"/></td>
// 334,5506원 으로 네자리수마다 끊어지게 설정할 수 있다
1-6) JSTL: functions로 EL에서 함수 이용
: EL 내에서 함수로 조작을 하고싶을때.
//detail.jsp
//지시자 추가
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<tr>
<th>첨부파일</th>
<td colspan="3" class="text-align-left text-indent">
<c:forTokens var="fileName" items="${n.files}" delims="," varStatus="st"> //토큰으로 나눠주면서 반복
<c:set var="style" value=""/> // 기본적으로 적용될 스타일값
<c:if test="${fn:endsWith(fileName,'.zip')}"> //fn:endsWith zip으로 끝나는 파일에만 적용될 if값
<c:set var="style" value="font-weight:bold; color:blue;"/>
</c:if>
<a href="${fileName}" style="${style}">${fn:toUpperCase(fileName)}</a> //fn:toUpperCase
<c:if test="${!st.last}">
/
</c:if>
</c:forTokens>
</td>
</tr>
[2] 기업형 레이어
: 작은 단위면 서블릿 내에서 업무를 모두 처리해도 되지만, 일반적으로는 업무와 DB까지 세분화됨
[3] 서비스 함수 찾아서 클래스로 구현하기
3-1) 사용자 요청
① 문서 요청 (getNoticeList)
② 페이저로 다른페이지 요청(getNoticeList(int page))
③ 검색 요청(getNoticeList(String field, String query, int page)- 검색 내에서 여러 페이지가 뜰 수 있으므로 page값도 받아야 한다)
④ 현재페이지/총페이지 (getNoticeCount, getNoticeCount(String field, String query)
3-2) 문서 요청
사전조건: id를 넘겨 받는다
선택한 글 디테일: getNotice(id)
다음글: getNextNotice(id)
이전글: getPrevNotice(id)
//NoticeService.java 서비스 클래스 뼈대만들기
public class NoticeService { // 사용자가 요청할 수 있는 모든 서비스를 모은 것
public List<Notice> getNoticeList() { //문서 요청 (공지게시글리스트화면)
return getNoticeList("title", "", 1);
}
public List<Notice> getNoticeList(int page) { //페이저로 다른페이지 요청
return getNoticeList("title", "", page); //field, query, page
}
public List<Notice> getNoticeList(String field, String query, int page) { //검색요청
return null;
}
//----------------이름이 같은 경우는 하나만 구현하고 나머지는 재호출해서 구현 = 코드의 집중화
public int getNoticeCount() { //현재페이지
return getNoticeCount("title","");
}
public int getNoticeCount(String field, String query) { //총페이지
return 0;
}
//-----------------------
public Notice getNotice(int id) { //글 조회 화면
return null;
}
public Notice getNextNotice(int id) { //글조회 아래에 다음글
return null;
}
public Notice getPrevNotice(int id) { //글조회 아래에 이전글
return null;
}
}
3-3) 이전글/다음글 SQL구현
* 게시글은 역정렬로 게시된다. 최신글이 1번.
rownum | id | regdate | |
1 | 5 | 5/20 18:00 | |
2 | 다음글 | 4 | 5/20 14:00 |
3 | 3이 기준일때 | 3 | 5/20 12:00 |
4 | 이전글 | 2 | 5/20 02:00 |
5 | 1 | 5/19 19:00 |
이전글: 이전에 작성됨 = regdate가 기준값보다 더 작다 👉 역정렬을 해야 id가 2,1 순서로 나와서 rownum 1이 id 2가 된다.
다음글: 이후에 작성됨 = regdate가 기준값보다 더 크다. 👉 순정렬을 해야 id가 4,5 순서로 나와서 rownum 1번이 id 4가 된다 (역정렬은 5,4순서로 나오니까 rownum으로 찾아내기가 힘듦)
-- 다음글
select * from notice
where id=(
select id from notice
where regdate > (select regdate from notice where id=3)
and rownum =1
-- id 3보다 이후에 작성된 글 중에서 rownum이 1인것
);
-- 정렬에 헷갈리기 싫으면 min/max 로 도출된 값 중 아이디가 가장 작고 큰걸로 도출 가능
select * from notice
where id=(
select min(id) from notice where regdate > (select regdate from notice where id=3)); -- id 4,5,6이 나중에 작성된 글이므로 작은값인 4가 다음글
-- 이전글
select id from (select * from notice order by regdate desc) -- 역정렬값 중에서 where절 조건을 찾음
where id=(
select id from notice
where regdate < (select regdate from notice where id=3)
and rownum =1
);
select * from notice
where id =(
select max(id) from notice where regdate <(select regdate from notice where id=3) -- id 1,2가 이전에 작성된 값이므로 큰값인 2가 이전글
);
[4] 서비스 클래스 분리
: 기존에는 컨트롤러에서 SQL문을 다 가지고 있었는데, SQL문을 서비스로 넘기고 컨트롤러에는 요청을 받아서 넘기고 값을 받아서 넘기는 기본 구조만 남긴다.
// NoticeListController.java
package com.newlecturepre.web.controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.newlecturepre.web.entity.Notice;
import com.newlecturepre.web.service.NoticeService;
@WebServlet("/notice/list")
public class NoticeListController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
NoticeService service = new NoticeService();//사용자에게 요청이 왔을때 뭘 원하는지 캐치하고 서비스에게 값을 받아냄. 컨트롤러 역할이 심플해짐
List<Notice> list = service.getNoticeList(); // 서비스로 NoticeList()와 연결
request.setAttribute("list", list); //request에 담아 jsp로 전달
request.getRequestDispatcher("/WEB-INF/view/notice/list.jsp").forward(request, response);
}
}
//NoticeService.java
public class NoticeService { // 사용자가 요청할 수 있는 모든 서비스를 모은 것
public List<Notice> getNoticeList() { //문서 요청 (공지게시글리스트화면)
return getNoticeList("title", "", 1);
}
public List<Notice> getNoticeList(int page) { //페이저로 다른페이지 요청
return getNoticeList("title", "", page); //field, query, page
}
public List<Notice> getNoticeList(String field /*title, writer_id*/, String query/*a*/, int page) { //검색요청
List<Notice> list = new ArrayList<>(); // 컨트롤러의 service.getNoticeList와 연결됨
String sql="select * from (" +
"select rownum NUM, N.* "+
"from (select * from notice where "+field+" like ? order by regdate desc)N"+
") where NUM between ? and ?";
// 1,6,11,16... > an = a1+(n-1)*d > 1+(page-1)*5
// 5, 10, 15, 20 > page * 5
String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url, "id", "pw"); // 서버,아이디,패스워드
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, "%"+query+"%"); // 쿼리문은 물음표로 꽂아줄수있지만 타이틀은 불가해서 sql에 직접 추가함 (+field+)
st.setInt(2, 1+(page-1)*5);
st.setInt(3, page * 5);
ResultSet rs = st.executeQuery();
while (rs.next()) {
int id = rs.getInt("ID");
String title = rs.getString("TITLE");
Date regdate = rs.getDate("REGDATE");
String writerId = rs.getString("WRITER_ID");
String hit = rs.getString("HIT");
String files = rs.getString("FILES");
String content = rs.getString("CONTENT");
Notice notice = new Notice(
id, title, regdate, writerId, hit, files, content);
list.add(notice);
}
rs.close();
st.close();
con.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
* 다른 메서드도 같은 방식으로 완성한다. 이때 물음표가 받는 st.setInt, st.setString을 잘 구분할 것. 매개변수와 지역변수가 겹치는 경우를 확인할 것.