[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>

1,2,3,4중 begin-end를 설정한 1,2,3만 나온다 (0번째 인덱스 제외)

<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: 토큰을 만들어주는걸 기본으로 하면서 반복을 하는 제어 태그

목록 하나하나가 콤마로 구분될 수 있도록 jsp에서 <a>태그로 구분한다

토큰: 내가 원하는 단위로 잘랐을때 잘린 하나의 단위

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값에 연월일시분초밀리초까지 입력이 되어있는지 확인


list 뿐만 아니라 detail의 작성일 출력 코드도 동일하게 변경

 

 

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원 으로 네자리수마다 끊어지게 설정할 수 있다

<td><fmt:formatNumber pattern="#,###회" value="${n.hit}" /> </td>

 

 

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을 잘 구분할 것. 매개변수와 지역변수가 겹치는 경우를 확인할 것.

+ Recent posts