✅ 작성순서
- DB 테이블/시퀀스 설계 (board 테이블, seq)
- DAO 클래스 작성 (insertBoard, getArticles, getArticle, updateBoard, deleteBoard 등)
- 화면 JSP 작성 (insertForm.jsp, list.jsp, detail.jsp 등)
- Proc JSP 작성 (insertProc.jsp, updateProc.jsp, deleteProc.jsp)
- 흐름 연결 & 테스트
글목록 조회 결과화면


🌈게시글 IP란에 나의 IP를 넣는방법
상단메뉴 Run > Run Configuration > Tomcat Server선택 > Aguments > VM aguments 맨 아래칸에 작성
-Djava.net.preferIPv4Stack=true

(1) SQL Table 생성
drop table board;
create table board(
num number not null primary key,
writer varchar2(10) not null,
email varchar2(10),
subject varchar2(20) not null,
passwd varchar2(12) not null,
reg_date date not null,
readcount int default 0,
ref number not null,
re_step number not null,
re_level number not null,
content varchar2(20) not null,
ip varchar2(15) not null
);
drop sequence board_seq;
create sequence board_seq
increment by 1
start with 1
minvalue 1
maxvalue 10000
nocache;
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a1','a1','a1','1234','2023-1-1',1,0,0,'내용1','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a2','a2','a2','1234','2023-1-1',2,0,0,'내용2','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a21','a21','a21','1234','2023-1-1',2,4,1,'내용21','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a22','a22','a22','1234','2023-1-1',2,1,1,'내용22','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a221','a221','a221','1234','2023-1-1',2,3,2,'내용221','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a222','a222','a222','1234','2023-1-1',2,2,2,'내용222','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a3','a3','a3','1234','2023-1-1',7,0,0,'내용3','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a31','a31','a31','1234','2023-1-1',7,6,1,'내용31','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a311','a311','a311','1234','2023-1-1',7,7,2,'내용311','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a32','a32','a32','1234','2023-1-1',7,2,1,'내용32','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a321','a321','a321','1234','2023-1-1',7,5,2,'내용321','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a322','a322','a322','1234','2023-1-1',7,3,2,'내용322','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a3221','a3221','a3221','1234','2023-1-1',7,4,3,'내용3221','127.0.0.1');
insert into board(num,writer,email,subject,passwd,reg_date,ref,re_step,re_level,content,ip)
values(board_seq.nextval,'a33','a33','a33','1234','2023-1-1',7,1,1,'내용33','127.0.0.1');
commit;
(2). BoardDao 계정연결 및 싱글톤패턴 생성코드 보기
public class BoardDao {
String driver = "oracle.jdbc.driver.OracleDriver";
String url = "jdbc:oracle:thin:@localhost:1521:orcl";
String id = "sqlid";
String pw = "sqlpw";
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
// 🌈싱글톤패턴으로 객체생성(객체를 한번만 생성하기)
private static BoardDao instance = new BoardDao();
public static BoardDao getInstance() {
return instance;
}// getInstance
// 드라이버 접속
private BoardDao() {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 계정연결
public void getConnection() {
try {
conn = DriverManager.getConnection(url, id, pw);
} catch (SQLException e) {
e.printStackTrace();
}
}
🌈 싱글톤패턴
// 싱글톤패턴으로 객체생성(객체를 한번만 생성하기)
private static BoardDao instance = new BoardDao();
- 싱글톤 패턴: 프로그램 전체에서 BoardDao 객체를 딱 하나만 만들고 공유.
- new BoardDao()를 private static으로 선언 → 클래스가 로딩될 때 단 한 번 객체를 생성해둔다.
👉 리소스 절약 + 일관성 + 유지보수성 때문에 DAO에서 싱글톤을 많이 씀.
📌 <jsp:useBean>이랑 뭐가 다르냐?
- JSP 페이지가 실행될 때마다 필요할 때 new 해서 객체 생성
- JSP가 다르면 DAO 객체도 새로 만들어질 수 있음
- 기본적으로 싱글톤 보장 안 됨
👉 Bean (BoardBean, MovieBean 등 데이터 담는 클래스): <jsp:useBean>으로 사용
1. 🌈게시판 만들기 - list.jsp 글목록 조회
1-1. BoardDao.getArticles (글 목록조회)
public ArrayList<BoardBean> getArticles(int start, int end) {
ArrayList<BoardBean> list = new ArrayList<BoardBean>();
getConnection();
String sql = "select num, writer, email, subject, passwd, reg_date, readcount, ref, re_step, re_level, content, ip " ;
sql += "from (select rownum as rank, num, writer, email, subject, passwd, reg_date, readcount, ref, re_step, re_level, content, ip ";
sql += "from (select num, writer, email, subject, passwd, reg_date, readcount, ref, re_step, re_level, content, ip ";
sql += "from board ";
sql += "order by ref desc, re_step asc )) ";
sql += "where rank between ? and ? ";
try {
ps = conn.prepareStatement(sql);
ps.setInt(1, start);
ps.setInt(2, end);
rs = ps.executeQuery();
while(rs.next()) {
int num = rs.getInt("num");
String writer = rs.getString("writer");
String email = rs.getString("email");
String subject = rs.getString("subject");
String passwd = rs.getString("passwd");
Timestamp reg_date = rs.getTimestamp("reg_date");
int readcount = rs.getInt("readcount");
int ref = rs.getInt("ref");
int re_step = rs.getInt("re_step");
int re_level = rs.getInt("re_level");
String content = rs.getString("content");
String ip = rs.getString("ip");
BoardBean bb = new BoardBean(num, writer, email, subject, passwd,
reg_date, readcount, ref, re_step, re_level, content, ip);
list.add(bb);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if(rs != null)
rs.close();
if(ps != null)
ps.close();
if(conn != null)
conn.close();
} catch(SQLException e) {
e.printStackTrace();
}
}
System.out.println("list: " + list.size());
return list;
}// getArticles
1-2. BoardDao.getArticlesCount() (글 개수 조회)
✅ 조회 메서드 작성 순서 & 설명
1. 기본 조회 (정렬까지)
SELECT num, writer, email, subject, passwd, reg_date, readcount, ref, re_step, re_level, content, ip
FROM board
ORDER BY ref DESC, re_step ASC;
- 게시글 전체 조회
- ORDER BY ref DESC, re_step ASC
→ ref(원글 기준 그룹번호) → 최신 글이 위로 오도록 내림차순
→ re_step(답글 순서) → 나중에 단 답글(re_step이 큰 값)이 아래로 보이도록 오름차순
2. ROWNUM 붙이기
SELECT ROWNUM AS rank, num, writer, email, subject, passwd, reg_date, readcount, ref, re_step, re_level, content, ip
FROM ( ...정렬된 select... )
- Oracle에서 페이징 처리를 위해 ROWNUM 붙임
- 단, ROWNUM은 ORDER BY보다 먼저 적용되므로 정렬된 결과를 서브쿼리로 감싸야 원하는 순서대로 번호(rank)를 매길 수 있음
3. 바깥쪽에서 원하는 구간만 자르기
SELECT num, writer, email, subject, passwd, reg_date, readcount, ref, re_step, re_level, content, ip
FROM ( ...ROWNUM 붙인 select... )
WHERE rank BETWEEN ? AND ?;
- ? 바인딩 변수 사용 (JDBC PreparedStatement에서 값 넣음)
- ? 첫 번째: 시작 글 번호 (예: 1, 11, 21 …)
- ? 두 번째: 끝 글 번호 (예: 10, 20, 30 …)
- 예: BETWEEN 11 AND 20 → 11번째부터 20번째까지 게시글 가져오기
✅ 요약 흐름
- 게시글 가져오기 + 정렬
→ 최신 글 순서대로 정렬 - ROWNUM 붙이기
→ 행 번호(rank)를 생성 - WHERE rank BETWEEN ? AND ?
→ 특정 페이지 범위(시작~끝)만 잘라서 가져오기
🔑 정리
- 안쪽 SELECT: 실제 데이터 정렬
- 중간 SELECT: ROWNUM 붙여서 순번 매김
- 바깥 SELECT: 페이지 범위 추출 (페이징)
🔑 한 줄 정리
- rownum = "넣은순서"가 아니라 "조회 순서"
- num = "글 고유번호"
🔍 예시
1. 정렬 안 한 경우
select rownum, num, subject from board;
ROWNUM | NUM | SUBJECT
-------+-----+----------
1 | 1 | 첫 번째 글
2 | 2 | 두 번째 글
3 | 3 | 세 번째 글
4 | 4 | 네 번째 글
👉 이 경우에는 rownum이 insert된 순서대로 보이는 것처럼 보임.
왜냐면 기본적으로 Oracle이 클러스터 인덱스 순으로 읽어오기 때
2. 정렬을 바꾼 경우
select rownum, num, subject
from (select * from board order by num desc);
ROWNUM | NUM | SUBJECT
-------+-----+----------
1 | 4 | 네 번째 글
2 | 3 | 세 번째 글
3 | 2 | 두 번째 글
4 | 1 | 첫 번째 글
👉 이번엔 rownum이 num 컬럼 내림차순 정렬된 순서대로 다시 매겨짐.
✅ 차이 요약
- num
- 우리가 시퀀스로 넣은 글번호 (테이블에 저장된 실제 값)
- 절대 변하지 않음
- rownum
- SELECT 실행 시점에 결과 집합에 붙는 "임시 순번"
- 정렬 결과에 따라 계속 달라질 수 있음
2. list.form 전체코드
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.*"%>
<%@ page import="myPkg.*"%>
<%
BoardDao bdao= BoardDao.getInstance();
%>
<%@ page import="java.text.SimpleDateFormat"%>
<%
// static 이므로 클래스이름으로 접근한다.
// 객체가 한번만 만들어질 수 있도록 static으로 설정
// 날짜 포맷 지정 (작성일 출력용)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
int pageSize = 10; // 1페이지당 10개
String pageNum = request.getParameter("pageNum");
if(pageNum == null){ // 현재화면에서 실행 1페이지 시작
pageNum = "1";
}
int currentPage = Integer.parseInt(pageNum);
// 현재 페이지에서 가져올 레코드의 시작행/끝행 계산
int startRow = (currentPage - 1) * pageSize + 1;
int endRow = (currentPage * pageSize);
// 전체 레코드 개수 구하기
int count = bdao.getArticleCount();
ArrayList<BoardBean> list = bdao.getArticles(startRow, endRow);
// 현재 페이지에 처음 시작되는 번호
int number = count - (currentPage - 1) * pageSize;
%>
<style>
table {
border-collapse: collapse;
margin: 10px auto;
}
td, th {
border: 1px solid #232323;
padding: 5px;
}
th{
background-color: cyan;
}
a {
text-decoration: none;
color: blue;
}
#page{
text-align: center;
}
#titlelist{
text-align:center;
}
</style>
<div id="titlelist">
<b>글목록(전체 글: <%=count %>)</b>
</div>
<table width="700">
<tr>
<td><a href="writeForm.jsp">글쓰기</a></td>
</tr>
</table>
<table width="700">
<tr height="30">
<td align="center" width="50">번 호</td>
<td align="center" width="250">제 목</td>
<td align="center" width="100">작성자</td>
<td align="center" width="150">작성일</td>
<td align="center" width="50">조 회</td>
<td align="center" width="100">IP</td>
</tr>
<%
for(int i=0; i<list.size(); i+=1){
BoardBean bb = list.get(i);
%>
<tr>
<td><%=number-- %></td>
<%-- <td><%=bb.getNum() %></td> --%>
<td id="title">
<%
if(bb.getRe_level()>0){//답글:1 답답:2 답답답:3
int wid = bb.getRe_level() * 15;
%>
<img src="./images/level.gif" width="<%=wid%>">
<img src="./images/re.gif">
<%
} //if
%>
<a href="content.jsp?num=<%=bb.getNum()%>&pageNum=<%=currentPage%>"><%=bb.getSubject()%></a>
<%
if(bb.getReadcount() >= 10){
%>
<img src="./images/hot.gif" height="14"/>
<%
}
%>
</td>
<td><%=bb.getWriter() %></td>
<td><%=sdf.format(bb.getReg_date()) %></td>
<td><%=bb.getReadcount() %></td>
<td><%=bb.getIp() %></td>
</tr>
<%
}
%>
</table>
<div id="page">
<%
if(count > 0){
// 전체 페이지 수 = 전체글수 / 페이지크기 (+나머지 있으면 +1)
int pageCount = count / pageSize + (count % pageSize ==0 ? 0 : 1);
int pageBlock = 10; // 한번에 보여줄 페이지 블록 수
int startPage = ((currentPage-1) / pageBlock * pageBlock) + 1;
int endPage = startPage + pageBlock - 1; // 끝 페이지
if(endPage > pageCount){
endPage = pageCount; // 마지막 페이지 번호를 초과하지 않도록 보정
}
if(startPage>10){
%>
<a href="list.jsp?pageNum=<%=startPage-10%>">[이전]</a>
<%
}
%>
<%
for(int i=startPage; i<=endPage; i+=1){
%>
<a href="list.jsp?pageNum=<%=i %>">[<%=i %>]</a>
<%
}// for
if(endPage < pageCount){
%>
<a href="list.jsp?pageNum=<%=startPage+10%>">[다음]</a>
<%
}
}
%>
</div>
📌 전체 흐름
1. DAO 불러오기
<%
BoardDao bdao= BoardDao.getInstance();
%>
- DAO를 싱글톤 패턴으로 생성해서 가져옴.
- 이제 이 페이지에서 DB 접근은 bdao가 맡음.
2. 페이지 번호 및 시작(startRow)·끝(endRow) 행 구하기
int pageSize = 10; // 1페이지당 10개
String pageNum = request.getParameter("pageNum");
if(pageNum == null) {
pageNum = "1";
}
int currentPage = Integer.parseInt(pageNum);
int startRow = (currentPage - 1) * pageSize + 1;
int endRow = currentPage * pageSize;
⚫pageSize: 한 페이지당 글 개수 (10개).
⚫ pageNum: 현재 몇 번째 페이지인지 파라미터로 받음 (없으면 1페이지).
===> 처음 창을 열었을때는 null 이므로
===> pageNum = 1
===> int currentPage = 1
- startRow: 이 페이지에서 가져올 첫 번째 글 번호
- endRow: 이 페이지에서 가져올 마지막 글 번호
startRow = (선택한 페이지 번호 - 1) * 페이지당 글 개수 + 1
endRow = (선택한 페이지 번호) * 페이지당 글 개수
3. 게시판 글 개수 · 글 목록 조회 & 화면 번호 계산
// 게시판 글 개수
int count = bdao.getArticleCount();
// 글 목록 조회
ArrayList<BoardBean> list = bdao.getArticles(startRow, endRow);
// 화면에 출력할 페이지 번호
int number = count - (currentPage - 1) * pageSize;
- count: 전체 글 수 (DB에서 select count(*)).
- list: 현재 페이지 범위(startRow~endRow)의 글 목록.
- number: 화면에 출력할 글 번호
✅ number 예시:
- 전체 글 수 = 95, 페이지 크기 = 10
- 1페이지 → 95 - (1-1)*10 = 95 (맨 위 글 번호 95)
- 2페이지 → 95 - (2-1)*10 = 85 (맨 위 글 번호 85)
- 3페이지 → 95 - (3-1)*10 = 75
4. 게시판 목록을 <table>에 출력하는 부분 (글 목록 테이블)
<tr>
<td align="center" width="50">번 호</td>
<td align="center" width="250">제 목</td>
<td align="center" width="100">작성자</td>
<td align="center" width="150">작성일</td>
<td align="center" width="50">조 회</td>
<td align="center" width="100">IP</td>
</tr>
<%
for(int i=0; i<list.size(); i++){
BoardBean bb = list.get(i);
%>
<tr>
<td><%=number-- %></td>
<td id="title">
<% if(bb.getRe_level()>0){ %>
<img src="./images/level.gif" width="<%=bb.getRe_level()*15%>">
<img src="./images/re.gif">
<% } %>
<a href="content.jsp?num=<%=bb.getNum()%>&pageNum=<%=currentPage%>"><%=bb.getSubject()%></a>
<% if(bb.getReadcount() >= 10){ %>
<img src="./images/hot.gif" height="14"/>
<% } %>
</td>
<td><%=bb.getWriter() %></td>
<td><%=sdf.format(bb.getReg_date()) %></td>
<td><%=bb.getReadcount() %></td>
<td><%=bb.getIp() %></td>
</tr>
<%
}
%>
- 🌈 제목 처리
- number--: 번호를 순차적으로 줄여나감.
- re_level: 답글 들여쓰기 (답글일수록 왼쪽 여백 wid ↑).
- hot.gif: 조회수 10 이상이면 🔥 표시.
- sdf.format: 작성일 yyyy-MM-dd HH:mm 형태로 출력.
5. 페이지 블록 출력 (하단 페이지 번호)
int pageCount = count / pageSize + (count % pageSize == 0 ? 0 : 1);
int pageBlock = 10;
int startPage = ((currentPage-1) / pageBlock * pageBlock) + 1;
int endPage = startPage + pageBlock - 1;
if(endPage > pageCount) endPage = pageCount;
- count: 전체 글 수
- pageSize: 한 페이지에 보여줄 글 개수
👉 count / pageSize 로 몫 계산 → 기본 페이지 수
👉 나머지가 있으면 +1 페이지 더 추가
int startPage = ((currentPage-1) / pageBlock * pageBlock) + 1;
👉 현재 페이지(currentPage)가 속한 페이지 블록의 시작 번호를 구하는 공식
📌 블록 개념 먼저
- pageBlock = 10 이라고 하면
- [1][2][3]...[10] → 첫 번째 블록
- [11][12]...[20] → 두 번째 블록
- [21][22]...[30] → 세 번째 블록
즉, 10개씩 묶어서 페이지 번호를 보여주는 것
예시:
currentPage = 7, pageBlock = 10
(currentPage - 1) = 6
6 / 10 = 0 (정수 나눗셈)
0 * 10 = 0
+ 1 → startPage = 1👉 즉, 7번 페이지는 1~10 블록에 속하므로 시작 번호는 1
6. 페이지 번호링크 출력
if(startPage > 10){
<a href="list.jsp?pageNum=<%=startPage-10%>">[이전]</a>
}
for(int i=startPage; i<=endPage; i++){
<a href="list.jsp?pageNum=<%=i %>">[<%=i %>]</a>
}
if(endPage < pageCount){
<a href="list.jsp?pageNum=<%=startPage+10%>">[다음]</a>
}
👉 페이지 번호 링크 출력
- [이전], [다음] 버튼으로 앞/뒤 블록 이동.
- 현재 블록 내 페이지 번호를 반복문으로 출력
📌 정리
- 이 페이지 역할: list.jsp = 게시판의 글 목록 + 페이징 처리.
- 데이터 흐름:
DB → DAO (getArticles) → ArrayList<BoardBean> → JSP에서 for문으로 테이블 출력. - 주요 기능:
- 글 번호 출력
- 답글 들여쓰기 (re_level)
- 조회수 10 이상 시 hot 아이콘
- 작성일 포맷팅
- 페이지 번호 블록 표시
'기초 및 언어 > ▶ Java&JSP' 카테고리의 다른 글
| 27. Servlet (0) | 2025.09.16 |
|---|---|
| 26.JSP Programming - 표현 언어 EL (Expression Language) (0) | 2025.09.16 |
| 24 JSP + JDBC 다중 삭제 (multiDelete) 구현 (0) | 2025.09.15 |
| 23. JSP + JDBC delete예제 정리3 _ Movie (0) | 2025.09.15 |
| 22. JSP + JDBC update 예제 정리3 _ Movie (1) | 2025.09.15 |