꼭 jsp게시판만이 아니라 asp든 php든 상관 없이 ..
일단 답변글을 구현할려면 게시판 테이블에 최소 3개의 컬럼이 있써야
합니다..
컬럼은 편의상 ref,rev,res 이라고 정의 하겠습니다..
컬럼의 설명부터 하자면..
ref = > 어느글에 답변인지 ??
res = > 답변글이 누가 먼저 달았는지??(하나의 글에 대해 답변이 많을경우)
rev = >답변글에 답변글인지 ??(답변글에도 위게 질서가 있다는건??)
로직은 다음과 갔습니다..
먼저 쓴글이 답변글인지 아님 새로운 글인지 먼저 판단해줘야 하죠~
새로운 글같은경우는 ref = 자신의 글번호 ,rev = 0 res = 0으로 입력해
주세요
쉽게 하기 위해서 rev랑 res는 디폴트 값으로 0으로 하면되죠
답변글일땐 원본글의 글번호가 필요합니다..
여기서 원본글번호는 ref 입니다
첫째 답변글 디비 입력 단 ref는 원본글의 번호입력..
insert into board(num,title,headname,content,name,email ,dateday,ref,pwd) values(?,?,?,?,?,?,?,?,?)"
둘째 원본글의 ref,rev,res 값이 필요 합니다
select ref,rev,res from board where num=원본글의 글번호
셋째 답변글의 첫번째 업데이트
update board set res=res+1 where ref=원본글의ref and res>원본글의
res
네째 답변글의 두번째 업데이트
update board set ref=원본글번호 ,rev=(원본글rev + 1),res= (원본글의res +1) where num=답변글의 글번호
이상입니다...
위의 로직을 bean 아님 jsp로 만들면 되겠죠...
다음은 화면상에 뿌려줄때 입니다
select * from board order by ref desc, res asc,num desc
이렇게 불러 와서..배열에 담은후 배열의 길이 만큼 돌려주면서
타이틀만 뿌려주는 로직입니다
for(int i = 0 ; 배열의 길이만큼(jsp인경우는 rs.next()); i++)
{
답변글일경우
if(rev_a[i]!=0)
{
<img src="images/level.gif" width="<%=rev_a[i]*10%>">
<img src="images/reply.gif" border="0">
}//답변글 끝
<a href="view.jsp?pnum=<%=num_a[i]%>&next=<%=next%>">
<%=title_a[i]%>
</a>
}for문 끝
** 참고**
위의 로직은 빈을 사용 짠 게시판을 참고로 작성한 것입니다.
자바 카페 :자바의 꿈 (http://cafe.daum.net/DOJ)
◎ 김민규 (sepina@hitel.net) 07/12[
정보 고맙습니다. 그런데 답변글의 답변글 로직을 구현하기 위해선 4번째 업데이트 부분에서요 ref=원본글번호 를 ref=원본글의 ref로 수정해 주어야 되더군요.
◎ 김경원 09/01[
제가 생각한거랑 똑같은 로직이네요. 근데 문제는 게시물수가 많아지면 너무 느려진다는거네요. 혹시 이렇게 하면서 속도또한 잘 나오게 할수 있나요. 거의 2만 넘어가면 만개당 1초씩 느려지는것 같아여.
주의: 시작 번호는 내부적으로 0입니다만 화면에 표시되기는 1로 표시됩니다. board.jsp?pg=1 은 실제로는 2페이지를 보여줍니다.
<%@ page contentType="text/html;charset=euc-kr"%><%
String link= request.getParameter("link"); // 링크할 주소
int pageTotal = 0; // 전체 페이지 수
int cpage = 0; // 현재 페이지
int psize = 0; // 페이지 리스트 사이즈
int pageGroupStart = 0; // 페이지 리스트 그룹시작번
int pageGroupEnd = 0; // 페이지 리스트 그룹 끝번
%>
<a href="<%=link%>&pg=0">Top</a>
<%
try{
pageTotal = Integer.parseInt(request.getParameter("pageTotal"));
cpage = Integer.parseInt(request.getParameter("cpage"));
psize = Integer.parseInt(request.getParameter("psize"));
pageGroupStart = (int)(cpage/psize) * psize;
pageGroupEnd = pageGroupStart + psize;
if (pageGroupEnd>pageTotal) pageGroupEnd = pageTotal + 1;
if (cpage-psize>=0){
%><a href="<%=link%>&pg=<%=pageGroupStart-1%>">◀이전 </a><%
} // end if
for(int i=pageGroupStart; i<pageGroupEnd ;i++){
if (i==cpage) {
%> [<b><%=i+1%></b>] <%
} else {
%><a href="<%=link%>&pg=<%=i%>" style="list">[<%=i+1%>]</a><%
} // end if
} // end for
if ((pageGroupStart+psize)<(pageTotal+1)){
%><a href="<%=link%>&pg=<%=pageGroupStart+psize%>"> 다음▶</a><%
} // end if
} catch(NumberFormatException e) {
out.println(e.getMessage());
}
%>
<a href="<%=link%>&pg=<%=pageTotal%>">Bottom</a>
현재 JSP, Bean을 이용한 게시판 컴포넌트를 개발하고 있습니다.
물론 회사에서 진행하고 있는 프로젝트입니다.
게시판에 대해 깊이 고찰하다보니, 내가 자바를 써서 게시판을 만드는 이점이
무엇인지 곰곰히 생각하지 않을수가 없더군요...
우선, 그동안의 모든 게시판들이 목록을 가져오는 쿼리가 모두 absolute 로써,
만약 게시물이 10만건이라면 10만건을 모두 읽은 후 목록을 뿌려주는 형태입니다.
이것은 게시물이 증가할 수록 심각한 퍼포먼스 저하를 가져오는 주요 원인이 되며
한정된 DB Connection 자원을 낭비하는 또하나의 원인이 됩니다.
자바를 사용함으로써의 장점이 다른 스크립팅 언어와는 다르게 낮은 레벨로 접근할
수 있기 때문에 DB Connection Pool을 연동하여 구성할 수 있다는 것입니다.
게시물 목록은 단지 10개만 뿌려주기 위함인데 10만건을 모두 읽고나서
페이지 점프에 대한 링크도 걸어줘야되고... 하여튼 복잡한 구조가 되겠지요.
이 근본적인 문제를 해결하는 방법은 보드캐슁 기법을 이용하는 방법입니다.
보드캐슁은 요즘 최신으로 제공되는 Connection Pool에서 SQL 캐슁과 비슷한
기법을 사용하는 방법입니다.
먼저 이를 이용하기 위해서는 쿼리를 단지 조건없이 모두 읽는 방법보다
어느정도 끊어서 읽을 필요가 있습니다.
즉, select first 10 user_id, user_name from test_board 와 같이 말입니다.
인포믹스, 오라클, MS SQL에서는 first 문을 이용해 원하는 개수만큼의
resultset을 얻을 수 있습니다.
아, mysql에서는 select user_id, user_name from test_board limit 10; 을
사용해야 합니다.
그렇다면 매번 이렇게 나눠서 읽어야 할까?
그건 아닙니다.
게시판이 변경(insert, delete)되지 않는다면, 최초 한번만 읽도록 하고
나머지 사람들은 해당하는 게시판의 정보(메모리상의 인스턴스를 말합니다)를
읽어내면 굳이 DB를 다시 읽어낼 필요가 없겠지요.
저의 경우에는 게시물에 대한 페이지 블럭 5개 정도를 메모리에 공유 인스턴스로
띄워서 게시물에 대한 정보를 나눠갖는 캐슁기법을 사용하여 개발하는 중입니다.
추가적으로, 게시물 첫 페이지에 대해서는 아예 표시내용을 벡터에 넣어서
역시 다른 사용자가 DB를 읽지 않고도 벡터값만 가져다 쓰도록 했습니다.
메모리에 대한 계산을 해봤는데, 페이지 블럭 5개 정도면 32kb 밖에 차지하지
않으며, 첫페이지에 대한 벡터 사이즈도 미미할정도입니다.
이런 캐슁기법을 사용하지 않은 게시판은 게시물이 대략 10000건 이상인 경우
심각한 속도저하를 가져오는게 대다수입니다.
특히, 테이블을 1개만 사용하고 여러개의 게시판이 공유하는 경우에는
더욱 심각한 상황이 벌어질 수 밖에 없겠죠...
그리고 인덱스의 중요성을 꼭 알아두시길 바랍니다.
인덱스를 거는 것이랑 아닌것이랑 속도차이는 게시물 수가 증가할 수록
엄청나게 차이가 납니다. (단지 수치적으로 몇십배 이상이 되기도 합니다.)
인덱스를 거는 요령은 where절에서 어떤 필드값을 주로 사용하는지가 포인트이며
보통 2-3개 정도의 필드를 묶어서 cluster index를 생성시킵니다.
그냥 인덱스를 거는것보다 cluster index를 생성하는게 최상의 방법입니다.
쩝... mysql에서는 보통 인덱스만 지원합니다...
(제 개인적으로 mysql을 굉장히 좋아합니다. 작고 파워풀한게 정말 맘에 듭니다.
그러나 기능/성능으는 오라클이나 MS SQL이 더 낫다고 인정해야겠죠...)
제가 아직 프로젝트 중이어서 구체적인 소스나 예제를 제시할 수준은 안되므로,
설명이 답답하신 분들은 자바에 대한 기본적인 서적을 보시고,
어떻게 공용 인스턴스를 띄우고 이를 나눠쓸 수 있는지를 공부하시기 바랍니다.
사담입니다만...
여기 운영자가 제 대학후배 라는 특별한 인연때문에 종종 들리곤 했는데,
요즘 회사다니느라 바쁜것 같습니다.
연락도 안하고 뭐하고 사는지 원...
정말 오랜만에 글을 올리니 두서가 없군요...
쩝... 요새 프로젝트 때문에 낮밤이 바뀌어서 정신이 없어서 그런가보죠...
요새 인도에서 수입한 프로그래머들이 각광받고 있다고 합니다.
인도 프로그래머와 국내 프로그래머의 차이는 뭘까요?
그건 기본기 입니다.
우리나라에는 제대로 된 자바 프로그래머 만나기 어렵다는 말이 공공연하게
떠돕니다. 그건 제대로 기본기를 갖추지 못한 경우가 많기 때문이죠.
즉, Java의 언어적 철학부터 문법 등에 이르기 까지 순수 자바를 제대로
공부하지 않고, 서블릿이니 애플릿이니 JSP를 한다고 순수 자바기본을 무시하고
들어가는 경우가 많습니다만...
이러면 십중팔구 나중에 크게 어려움을 겪게 됩니다. (저처럼 말이죠... -.-;)
인도 프로그래머가 만든 소스를 보았는데, 정말 기본에 치밀하다는 말이 절로
나오더군요... 군더기가 없고 메모리 활용에 까지 신경쓴 모습이 참 이색적입니다.
또다른 사담...
다음 이라면 모두들 한메일을 떠올리실 겁니다.
거기서 솔루션을 개발해서 판매하는데...
저희 회사에서 동호회 솔루션을 이번에 수천만원을 들여 계약을 했더군요...
그래서 기본적인 DB 테이블 등을 보내주길래 봤는데...
정말 이게 제대로된 인간들이 만든건지 모를정도라고요...
DB에 인덱스, 외래키등을 전혀 사용하지도 않으면서도....
게시판은 테이블 달랑 하나 쓰면서 수많은 동호회가 같이 쓰더군요... -.-;
아마 동호회 몇개가 몇달만 뻐티면 아마 무리없이 뻗어버릴것이 확실합니다.
이미 세종증권 동호회에 포팅했다는데... 얼마나 버틸지 궁금하더군요...
DB에 대해서도 정석이 있습니다.
그런 정석을 무시하면 어떤 결과를 보여줄지는 안보고도 알수 있는 결과입니다.
단지 프로그램만 잘 짜면 된다고 생각한다면 큰 오판입니다.
DB자체만 놓고 보더라도 프로그램 이상의 비중을 가지게 됩니다.
제가 위에다 주절주절 썼지만, 위의 기법을 사용하지 않고서도 속도를
기막히게 빠르게 처리하는 방법이 있습니다.
바로 DB에서 지원하는 Stored Procedure를 사용하는 방법입니다.
사실 이게 더 빠른 성능을 제공합니다.
(흑... Mysql은 아직 stored procedure를 지원하지 않습니다... ㅠ.ㅠ)
저도 이전에 인포믹스와 오라클에서 stored procedure를 이용해서 게시판을
만든 경험이 있는데, 정말 편하고 속도도 빠릅니다.
이렇게 DB 벤더와 특성을 잘 이해하고 꼭 프로그램을 써야할 경우와
DB에게 맡겨도 될 부분이 무엇인지를 정확히 인지하는게 중요한 포인트입니다.
이상 주저리를 마칩니다.
자주 들리는 편이 아니므로 질문에 대한 답변은 늦을수도 있습니다.
계층형 게시판에 사용되는 필드의 구성 |
까오기 보드에서는 두 개의 필드를 통해 계층형 게시판을 구현하고 있다.
re_level
re_level은 새 글을 등록했을 때 0의 값을 가지며 답변 시 1씩 더해지는 값이다. 이를 통해 목록보기에서 들여 쓰기 등의 방법으로 현재 글의 level를 보여 준다. 이외에 글 삭제 시 이 칼럼을 참고하여 이 글의 답변 글이 있는지를 확인할 수 있다.
re_step
re_step은 글의 순서를 나타내는 칼럼이다.
이 값은 답변이 아닐 때는 100씩 증가하는 값으로 뒤에 2자리를 여유공간으로 가지고 있다. 답변 글을 쓰면 이 곳에 저장이 되며 답변이 2자리 이상일 때는 1000단위로 증가하게끔 처리하면 된다.
100자리 |
여유공간 |
3 |
99 |
2 |
99 |
1 |
99 |
DB에 저장하기 |
1. 새 글일 때
위에서 보면 새 글일 때는 199, 299, 399 이런 식으로 값이 증가를 한다. 목록보기에서 글이 순차적으로 보이기 위해서는 새 글일 때 뒤에 두 자리의 값이 0 ~ 99의 값 중 가장 높은 값인 99를 가지며 이 후 답변 글은 이 값에서 1씩 빼나간다.
새 글일 때 최대 re_step 구하기
a. 해당 테이블의 최대 re_step 값을 구한다.
String query = "select max(re_step) re_step from kkaok";
b. 만약 re_step 값이 1499라고 한다면 100으로 나누고 ⇒ 14.99
c. int로 형변환하여 소수점 이하는 버린다. ⇒ 14
d. 거기서 1을 더한다. ⇒ 15
e. 그 값에 100을 곱하고 다시 99를 더하면 된다. ⇒ 1599
아래는 실제 최대 값을 구하는 소스부분이다.
String sql="select re_step from kkaok order by re_step desc limit 1"; int re_step = 199; // DB안에 아무 값도 없을 때는 199이다. ResultSet rs = null; PreparedStatement pstmt = null; Connection conn = null; try{ conn = DBManager.getConnection(); pstmt = conn.prepareStatement(sql); rs = pstmt.executeQuery(); if(rs.next()) re_step=((int)(rs.getInt("re_step")/100)+1)*100+99; }finally{ DBManager.close(rs,pstmt,conn); } |
새 글일 때 re_level은 항상 "0"이다.
2. 답변일 때
글을 저장한 순서
1번 글 → 2번 글 → 3번 글 → 2_1번 글 → 2_1_1번 글 → 2_2번 글 → 2_2_1번 글 |
위에서 보면 알 수 있듯이 re_step은 목록보기의 순서와 일치가 된다. 답변을 하면 원본 글의 아래에 저장이 되며 같은 레벨에서 답변은 상위에 저장이 된다.
답변 글을 저장할 때는 re_step은 원본 글의 값에서 1을 빼고 re_level은 원본 글의 값에 1을 더하면 된다.
|
re_step |
re_level |
원본글 |
299 |
0 |
답변글 |
298(-1) |
1(+1) |
이때 원본 글과 답변 사이에 또 다른 답변이 삽입이 되면 원본 글보다 작고 여유공간 범위 안에 있는 값들을 모두 1씩 빼줘야 한다.
원본 re_step이 297이라고 가정 할 때 297보다 작고 200보다 큰 값은 모두 1씩 빼줘야 한다.
update kkaok set re_step=re_step-1 where re_step > 200 and re_step < 297
|
re_step |
re_level |
원본글 |
297 |
2 |
이 공간에 답변글이 들어간다. | ||
답변글 |
295(-1) |
1 |
답변글 |
294(-1) |
2 |
DB에 처리할 때는 최대 re_step의 값은 원본 값으로 하고 최소 값은 원본 re_step의 값을 이용하여 구할 수 있다.
먼저 원본 값을 백으로 나누고 ⇒ 297/100 = 2.97
그 값을 int로 형변환하여 소수점을 버린다. ⇒ (int)(2.97) = 2
결과값에 다시 100을 곱하면 최소값을 구할 수 있다. ⇒ 2*100
String sql="update kkaok set re_step=re_step-1 where re_step > ? and re_step < ?"; PreparedStatement pstmt = null; Connection conn = null; try{ conn = DBManager.getConnection(); pstmt = conn.prepareStatement(sql); pstmt.setInt(1,(int)(re_step/100)*100); pstmt.setInt(2,re_step); pstmt.executeUpdate(); }finally{ DBManager.close(pstmt,conn); } |
이와 같은 처리를 하였을 때 좋은점 |
re_step은 고유한 값을 가지며 list의 순서와 일치가 된다. 따라서 primary key로 사용이 가능하다. primary key로 설정을 하면 unique 속성을 가지며 물리적으로 정렬(clustered index 속성을 가짐)이 되기 때문에 index를 부여하는 것보다 빠른 엑세스가 가능하다. 또한 order by 절에서 re_step만으로 정렬을 시킬 수 있다.
목록보기 쿼문의 작성 |
이해를 위해 다음과 같이 가정을 하고 설명을 하겠습니다.
int gotoPage : 현재의 페이지 값, 2page로 가정한다.
int pageSize : 보여주려고 하는 게시물의 수, 15로 가정한다.
int recordCount : 전체 레코드 수, 57로 가정한다.
1. mysql
select seq,name,title from kkaok order by re_step desc limit ?,? |
첫 번째 바인드변수의 값 : pageSize*(gotoPage-1) ⇒ 15*(2-1) = 15
두 번째 바인드변수의 값 : pageSize ⇒ 15
15번째 게시물부터 15개를 불러온다.
사용되는 소스 예제
public List getListData(String tableName,int gotoPage, int pageSize,int recordCount) throws Exception { int start = pageSize*(gotoPage-1); List listData = new ArrayList(); String query = "select seq,name,title,email,readnum,writeday,re_level, relativeCnt from "+tableName+" order by re_step desc limit ?,?"; BoardTable bTable = null; ResultSet rs = null; PreparedStatement pstmt = null; Connection conn = null; try{ conn = DBManager.getConnection(); pstmt = conn.prepareStatement(query); pstmt.setInt(1,start); pstmt.setInt(2,pageSize); rs = pstmt.executeQuery(); while (rs.next()){ bTable = new BoardTable(); bTable.setSeq(rs.getInt("seq")); bTable.setName(rs.getString("name")); bTable.setTitle(Utility.getTitleLimit( ReplaceUtil.encodeHTMLSpecialChar(rs.getString("title"),14) ,40,rs.getInt("re_level"))); bTable.setEmail(rs.getString("email")); bTable.setReadnum(rs.getInt("readnum")); bTable.setWriteday(rs.getTimestamp("writeday")); bTable.setRe_level(rs.getInt("re_level")); bTable.setRelativeCnt(rs.getInt("relativeCnt")); listData.add(bTable); } } finally { DBManager.close(rs,pstmt,conn); } return listData; } |
2. mssql
int start = pageSize*gotoPage;
if (start > recordCount) pageSize -= (start-recordCount);
만약 4페이지라면 start는 60이며 이 값은 전체 레코드 수 보다 크다. 그때는 불러오는 pageSize는 15가 아니고 12가 된다.
pageSize = 15-(60-57)
현재는 2페이지로 가정하였으므로 start는 30이다.
select top "+start+" * from kkaok order by re_step desc |
이렇게 하면 re_step을 내림차순으로 정렬하여 30개를 가져온다.
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57
가져온 값을 다시 오름차순으로 해서 pageSize 만큼만 뽑아낸다.
select top "+pageSize+" * from (select top "+start+" * from kkaok order by re_step desc) as DERIVEDTBL order by re_step |
28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54, 55,56,57
이제 가져온 값을 다시 내림차순으로 정렬한다.
select * from (select top "+pageSize+" * from (select top "+start+" * from kkaok order by re_step desc) as DERIVEDTBL order by re_step) as DERIVEDTBL order by re_step desc |
42,41,40,39,38,37,36,35,34,33,32,31,30,29,28
사용되는 소스 예제
public List getListData(String tableName,int gotoPage, int pageSize,int recordCount) throws Exception { int start = pageSize*gotoPage; if (start > recordCount) pageSize -= (start-recordCount); StringBuffer query = new StringBuffer(); query.append("select seq,name,title,email,readnum,writeday,re_level,"); query.append("relativeCnt from (select top "+pageSize+" seq,name,title,"); query.append("email,readnum,writeday,re_level,relativeCnt,re_step from "); query.append("(select top "+start+" seq,name,title,email,readnum,writeday"); query.append(",re_level,relativeCnt,re_step from "+tableName+" order by "); query.append("re_step desc) as DERIVEDTBL order by re_step)as DERIVEDTBL "); query.append("order by re_step desc"); List listData = new ArrayList(); BoardTable bTable = null; ResultSet rs = null; PreparedStatement pstmt = null; Connection conn = null; try{ conn = DBManager.getConnection(); pstmt = conn.prepareStatement(query.toString()); rs = pstmt.executeQuery(); while(rs.next()){ bTable = new BoardTable(); bTable.setSeq(rs.getInt("seq")); bTable.setName(rs.getString("name")); bTable.setTitle(Utility.getTitleLimit( ReplaceUtil.encodeHTMLSpecialChar(rs.getString("title"),14), 40,rs.getInt("re_level"))); bTable.setEmail(rs.getString("email")); bTable.setReadnum(rs.getInt("readnum")); bTable.setWriteday(rs.getTimestamp("writeday")); bTable.setRe_level(rs.getInt("re_level")); bTable.setRelativeCnt(rs.getInt("relativeCnt")); listData.add(bTable); } } finally{ DBManager.close(pstmt,conn); } return listData; } |
3. oracle
int start = pageSize*gotoPage;
if (start > recordCount) pageSize -= (start-recordCount);
select /*+ index_desc(kkaok kkaok_pk) */ *,rownum as rnum from "+tableName+" where rownum <= ? order by rnum desc |
여기서는 hint로 order by를 대신하여 정렬을 하고 rownum을 이용하여 start 만큼 가져온 후 다시 결과물을 내림차순으로 정렬을 한다.
57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32, 31,30,29,28
가져온 값을 pageSize 만큼만 뽑아낸다.
select * from (select /*+ index_desc(kkaok kkaok_pk) */ *,rownum as rnum from "+tableName+" where rownum <= ? order by rnum desc) where rownum <= ? order by rnum |
42,41,40,39,38,37,36,35,34,33,32,31,30,29,28
사용되는 소스 예제
public List getListData(String tableName,int gotoPage, int pageSize,int recordCount) throws Exception { int start = pageSize*gotoPage; if (start > recordCount) pageSize -= (start-recordCount); StringBuffer query = new StringBuffer(); query.append("select seq,name,title,email,readnum,writeday,re_level,"); query.append("relativeCnt from (select /*+ index_desc("+tableName+" "); query.append("tableName+"_pk_re_step) */ seq,name,title,email,"); query.append("readnum,writeday,re_level,relativeCnt,rownum as rnum from "); query.append("tableName+" where rownum <= ? order by rnum desc) where "); query.append("rownum <= ? order by rnum"); List listData = new ArrayList(); BoardTable bTable = null; ResultSet rs = null; PreparedStatement pstmt = null; Connection conn = null; try{ conn = DBManager.getConnection(); pstmt = conn.prepareStatement(query.toString()); pstmt.setInt(1,start); pstmt.setInt(2,pageSize); rs = pstmt.executeQuery(); while (rs.next()){ bTable = new BoardTable(); bTable.setSeq(rs.getInt("seq")); bTable.setName(rs.getString("name")); bTable.setTitle(Utility.getTitleLimit( ReplaceUtil.encodeHTMLSpecialChar(rs.getString("title"),14), 40,rs.getInt("re_level"))); bTable.setEmail(rs.getString("email")); bTable.setReadnum(rs.getInt("readnum")); bTable.setWriteday(rs.getTimestamp("writeday")); bTable.setRe_level(rs.getInt("re_level")); bTable.setRelativeCnt(rs.getInt("relativeCnt")); listData.add(bTable); } } finally { DBManager.close(rs,pstmt,conn); } return listData; } |
위에서 보여지는 쿼리문은 만 건 미만일 때 효과적이다. 하지만 만 건이 넘어서면 서서히 신음소리를 내기 시작한다. 페이지가 점점 뒤로 갈수록 느려지는 것이 느껴지기 시작한다. 이때 쿼리문만으로 50만 건까지 극복할 수 있다. 물론 이런 방식이 검색을 할 때도 적용되는 건 아니다. 검색은 조금 다른 처리가 필요하다. 예를 들어 날짜로 제한을 주거나 검색 게시물의 수를 제한을 두는 방식을 사용할 수 있다.
방법은 간단하다. 자신이 보여줄 페이지의 첫 번째 값을 구한 다음, 페이지 사이즈만큼 불러오기를 하면 되는 것이다. 이렇게 하는 것이 빠른 이유는 위에 방법은 re_step 뿐 아니라 목록보기에 필요한 다른 데이터도 가져와서 필요한 것을 뽑지만 아래 방법은 re_step 하나의 필드에서 필요한 위치를 찾아서 불러오기 때문이다.
1. mysql
int start = pageSize*(gotoPage-1);
start는 15*(2-1), 15가 된다.
select re_step from kkaok order by re_step desc limit ?,1 |
바인드 변수에는 start 값이 들어간다. 즉 15번째부터 하나만 가져와라~~
이렇게 하면 2페이지의 첫 번째 re_step의 값을 알 수 있다.
select * from kkaok where re_step<= ? order by re_step desc limit ? |
위에서 구한 값을 첫 번째 바인드변수에 넣고 뒤에는 pageSize를 넣어주면 된다. 그렇게 되면 첫 번째 값보다 작은 15개를 불러올 것이다.
사용되는 소스 예제
public List getListData(String tableName,int gotoPage, int pageSize) throws Exception { ResultSet rs = null; PreparedStatement pstmt = null; Connection conn = null; int start = pageSize*(gotoPage-1); int pageTopNum = 0; String query = "select re_step from "+tableName+" order by re_step desc limit ?,1"; try { conn = DBManager.getConnection(); pstmt = conn.prepareStatement(query); pstmt.setInt(1,start); rs = pstmt.executeQuery(); if(rs.next()) pageTopNum = rs.getInt("re_step"); else pageTopNum = 0; } finally { DBManager.close(rs,pstmt,conn); } List listData = new ArrayList(); query = "select seq,name,title,email,readnum,writeday,re_level, relativeCnt from "+tableName+" where re_step<= ? order by re_step desc limit ?"; BoardTable bTable = null; try{ conn = DBManager.getConnection(); pstmt = conn.prepareStatement(query); pstmt.setInt(1,pageTopNum); pstmt.setInt(2,pageSize); rs = pstmt.executeQuery(); while (rs.next()){ bTable = new BoardTable(); bTable.setSeq(rs.getInt("seq")); bTable.setName(rs.getString("name")); bTable.setTitle(Utility.getTitleLimit( ReplaceUtil.encodeHTMLSpecialChar(rs.getString("title"),14), 40,rs.getInt("re_level"))); bTable.setEmail(rs.getString("email")); bTable.setReadnum(rs.getInt("readnum")); bTable.setWriteday(rs.getTimestamp("writeday")); bTable.setRe_level(rs.getInt("re_level")); bTable.setRelativeCnt(rs.getInt("relativeCnt")); listData.add(bTable); } } finally { DBManager.close(rs,pstmt,conn); } return listData; } |
2. mssql
int start = pageSize*(gotoPage-1)+1;
start는 16이 된다.(15*(2-1)+1=16)
첫 번째 값 구하기
select min(re_step) AS Expr1 from (select top "+start+" re_step from kkaok order by re_step desc) as DERIVEDTBL |
게시물 목록 구하기
select top "+pageSize+" * from kkaok where re_step <= ? order by re_step desc |
바인드 변수에는 위에서 구한 첫 번째 re_step 값을 넣는다.
사용되는 소스 예제
public List getListData(String tableName,int gotoPage, int pageSize) throws Exception { int start = pageSize*(gotoPage-1)+1; ResultSet rs = null; PreparedStatement pstmt = null; Connection conn = null; String query = "select min(re_step) AS Expr1 from (select top "+start+" re_step from "+tableName+" order by re_step desc) as DERIVEDTBL"; int pageTopNum = 0; try{ conn = DBManager.getConnection(); pstmt = conn.prepareStatement(query); rs = pstmt.executeQuery(); if(rs.next()) pageTopNum = rs.getInt("Expr1"); } finally { DBManager.close(rs,pstmt,conn); } List listData = new ArrayList(); BoardTable bTable = null; query = "select top "+pageSize+" seq,name,title,email,readnum,writeday, re_level,relativeCnt from "+tableName+" where re_step <= ? order by re_step desc"; try{ conn = DBManager.getConnection(); pstmt = conn.prepareStatement(query); pstmt.setInt(1,pageTopNum); rs = pstmt.executeQuery(); while (rs.next()){ bTable = new BoardTable(); bTable.setSeq(rs.getInt("seq")); bTable.setName(rs.getString("name")); bTable.setTitle(Utility.getTitleLimit( ReplaceUtil.encodeHTMLSpecialChar(rs.getString("title"),14),40, rs.getInt("re_level"))); bTable.setEmail(rs.getString("email")); bTable.setReadnum(rs.getInt("readnum")); bTable.setWriteday(rs.getTimestamp("writeday")); bTable.setRe_level(rs.getInt("re_level")); bTable.setRelativeCnt(rs.getInt("relativeCnt")); listData.add(bTable); } } finally { DBManager.close(rs,pstmt,conn); } return listData; } |
3. oracle
int start = pageSize*(gotoPage-1)+1;
start는 16이 된다.(15*(2-1)+1=16)
첫 번째 값 구하기
select min(re_step) re_step from (select /*+ index_desc(kkaok kkaok_pk) */ re_step from kkaok where rownum <=?) |
바인드변수에는 start가 들어간다.
게시물 목록 구하기
select /*+ index_desc(kkaok kkaok_pk) */ * from kkaok where re_step <= ? and rownum <= ? |
첫 번째 바인드 변수에는 위에서 구한 첫 번째 re_step 값을 넣고 두 번째는 pageSize를 넣어서 구한다.
사용되는 소스 예제
public List getListData(String tableName,int gotoPage, int pageSize) throws Exception { ResultSet rs = null; PreparedStatement pstmt = null; Connection conn = null; int start = pageSize*(gotoPage-1)+1; int pageTopNum = 0; String query = "select min(re_step) re_step from (select /*+ index_desc("+tableName+" "+tableName+"_pk_re_step) */ re_step from "+tableName+" where rownum <=?) DERIVEDTBL"; try { conn = DBManager.getConnection(); pstmt = conn.prepareStatement(query); pstmt.setInt(1,start); rs = pstmt.executeQuery(); if(rs.next()) pageTopNum = rs.getInt("re_step"); else pageTopNum = 0; } finally { DBManager.close(rs,pstmt,conn); } List listData = new ArrayList(); query = "select /*+ index_desc("+tableName+" "+tableName+"_pk_re_step) */ seq,name,title,email,readnum,writeday,re_level,relativeCnt from "+ tableName+" where re_step <= ? and rownum <= ? "; BoardTable bTable = null; try{ conn = DBManager.getConnection(); pstmt = conn.prepareStatement(query); pstmt.setInt(1,pageTopNum); pstmt.setInt(2,pageSize); rs = pstmt.executeQuery(); while (rs.next()){ bTable = new BoardTable(); bTable.setSeq(rs.getInt("seq")); bTable.setName(rs.getString("name")); bTable.setTitle(Utility.getTitleLimit( ReplaceUtil.encodeHTMLSpecialChar(rs.getString("title"),14),40, rs.getInt("re_level"))); bTable.setEmail(rs.getString("email")); bTable.setReadnum(rs.getInt("readnum")); bTable.setWriteday(rs.getTimestamp("writeday")); bTable.setRe_level(rs.getInt("re_level")); bTable.setRelativeCnt(rs.getInt("relativeCnt")); listData.add(bTable); } } finally { DBManager.close(rs,pstmt,conn); } return listData; } |
[Tip][게시판 글제목 영/한 길이 일정하게 잘라주기]
자바의 특성상 2바이트 문자의 경우와 영숫자와 같은 1바이트 문자의 길이 차이로
게시판의 글 제목 출력시 문제가 길이가 맞지 않거나 한글 경우 '?' 등이 끝에 붙게
되는 경우가 생기게 됩니다.
사실 어느분이 이 것에 대해서 질문을 하셔서 한번 말들어 보았는데 어디에 질문이
올려져 있었는지 기억을 못해서. 이렇게 팁에다 올립니다.
조금이나마 도움이 되셨길 바랍니다.
/*
* 부호비트에 값이 있는 경우는 2바이트 문자로 간주하여 처리한다.
* @param in_Str 잘려져야 할 스트링
* @param in_CutPos 잘려져야 할 인덱스.
* @return 잘려진 스트링.
*/
public String cutKoreanText(String in_Str, int in_CutPos)
{
byte[] byteStr = in_Str.getBytes();
// 부호비트의 값이 0 이 아닌 경우는 2바이트 문자로 간주.
if ((byteStr[in_CutPos] & 0x80) == 0)
return new String(byteStr,0,in_CutPos);
else return new String(byteStr,0,in_CutPos+1);
// in_CutPos+1은 잘려진 한글을 완전하게 출력하라는 것입니다.
// 잘려진 한글을 출력하지 않으시려면 -1 값을 넣어주시면 됩니다.
}
'asp' 카테고리의 다른 글
html --> Excel 파일 출력 (0) | 2007.05.02 |
---|---|
대용량 게시판 만들기 강좌 #4/4 (WRITE, REPLY) (0) | 2007.05.02 |
천만건 이상게시판로직 (0) | 2007.05.02 |
계층형 게시판 로직 (0) | 2007.05.02 |
ServerVariables 개체 (0) | 2007.05.02 |