- JAVA 스터디 중 스터디장이 댓글 게시판 숙제로 구글 중 내가 적용한 알고리즘(까먹을까봐-_-)


계층형 게시판을 위한 테이블의 구성 및 알고리즘은 워낙 다양하기 때문에 특별히 정해진 솔류션은 존재하지 않습니다.
여기서는 많은 책자에서 소개되어 있는 간단한 계층형 게시판의 구조와 알고리즘에 대해서 알아봅니다.

 

설명을 위해 테이블에는 다음과 같은 컬럼들만 있다고 가정합니다. 실제로는 더 많겠죠.

No : 글번호(Primary Key)
Title : 글제목
Grp : 같은 주제를 갖는 게시물의 고유번호. 부모글과 부모글로부터 파생된 모든 자식글은 같은 번호를 갖습니다.
Seg : 같은 그룹내 게시물의 순서
Lvl : 같은 그룹내 계층

 

게시판에 첫번째 글이 올라오면, 테이블에는 다음과 같은 정보가 저장됩니다.

====================================
No, Title            , Grp, Seq, Lvl
====================================
 1, '안녕하세요'     ,   1,   1,   0

====================================

새 글에서 No는 시퀀스로부터 받아온 값, Grp는 No와 동일한 값, Seq는 1, Lvl은 0입니다.

 

두번째 글과 세번째 글이 올라옵니다.

====================================
No, Title            , Grp, Seq, Lvl
====================================
 3, '모임이 있습니다',   3,   1,   0
 2, '날씨가 맑습니다',   2,   1,   0
 1, '안녕하세요     ',   1,   1,   0

====================================

 

첫번째 글에 답글이 올라옵니다.

====================================
No, Title            , Grp, Seq, Lvl
====================================
 3, '모임이 있습니다',   3,   1,   0
 2, '날씨가 맑습니다',   2,   1,   0
 1, '안녕하세요     ',   1,   1,   0
 4, '  반가워요     ',   1,   2,   1 

====================================

답글의 경우, Grp는 부모글의 Grp 값, Seq는 부모글의 Seq+1, Lvl은 부모글의 Lvl+1이 됩니다.

 

첫번째 글의 답글에 답글이 올라옵니다. 즉, 네번째 글의 답글이 올라옵니다.

====================================
No, Title            , Grp, Seq, Lvl
====================================
 3, '모임이 있습니다',   3,   1,   0
 2, '날씨가 맑습니다',   2,   1,   0
 1, '안녕하세요     ',   1,   1,   0
 4, '  반가워요     ',   1,   2,   1 
 5, '    감사합니다 ',   1,   3,   2

====================================

 

첫번째 글의 두번째 답글이 올라옵니다.

====================================
No, Title            , Grp, Seq, Lvl
====================================
 3, '모임이 있습니다',   3,   1,   0
 2, '날씨가 맑습니다',   2,   1,   0
 1, '안녕하세요     ',   1,   1,   0
 6, '  환영합니다   ',   1,   2,   1 
 4, '  반가워요     ',   1,   2,   1 
 5, '    감사합니다 ',   1,   3,   2

====================================

 

여기서, 두번째 답글이 첫번째 답글보다 먼저 표시되려면, 동일한 그룹 번호에 있으면서 부모의 Seq 번호보다 큰 게시물의 Seq 번호를 모두 1만큼 증가시킵니다.

====================================
No, Title            , Grp, Seq, Lvl
====================================
 3, '모임이 있습니다',   3,   1,   0
 2, '날씨가 맑습니다',   2,   1,   0
 1, '안녕하세요     ',   1,   1,   0
 6, '  환영합니다   ',   1,   2,   1
 4, '  반가워요     ',   1,   3,   1
 5, '    감사합니다 ',   1,   4,   2

====================================

 

여섯번째 게시물에 답글이 올라옵니다.

====================================
No, Title            , Grp, Seq, Lvl
====================================
 3, '모임이 있습니다',   3,   1,   0
 2, '날씨가 맑습니다',   2,   1,   0
 1, '안녕하세요     ',   1,   1,   0
 6, '  환영합니다   ',   1,   2,   1
 7, '    감사합니다 ',   1,   3,   2
 4, '  반가워요     ',   1,   3,   1
 5, '    감사합니다 ',   1,   4,   2

====================================

 

마찬가지로 동일한 그룹 번호에 있으면서 부모의 Seq 번호보다 큰 게시물의 Seq 번호를 모두 1만큼 증가시킵니다.

====================================
No, Title            , Grp, Seq, Lvl
====================================
 3, '모임이 있습니다',   3,   1,   0
 2, '날씨가 맑습니다',   2,   1,   0
 1, '안녕하세요     ',   1,   1,   0
 6, '  환영합니다   ',   1,   2,   1
 7, '    감사합니다 ',   1,   3,   2
 4, '  반가워요     ',   1,   4,   1
 5, '    감사합니다 ',   1,   5,   2

====================================

 

검색 할 때는 Grp를 내림차순으로 Seq를 오름차 순으로 정렬하면 됩니다. 인덱스는 기본키와 Grp+Seq의 복합 인덱스를 설정하면 됩니다.

 

실제 오라클에서 테이블을 구성하고 검색해봅니다.

 

drop table qnaboard;

 

create table qnaboard
(no number,
 title varchar2(100),
 contents varchar2(4000),
 writer varchar2(20),
 wdate date,
 grp number,
 seq number,
 lvl number);

 

drop sequence qnaboard_no_seq;

 

create sequence qnaboard_no_seq
start with 1
increment by 1;

 

create index qnaboard_no_idx on qnaboard(no) reverse;

 

alter table qnaboard
add constraint qnaboard_no_pk primary key (no);

 

create index qnaboard_grp_seq on qnaboard(grp desc, seq asc);

alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss';

 

첫번째 게시물 입력 - 새 글 입력
insert into qnaboard
values(qnaboard_no_seq.nextval, '안녕하세요', null, '길동', sysdate, qnaboard_no_seq.currval, 1, 0);

 

두번째 게시물 입력 - 새 글 입력
insert into qnaboard
values(qnaboard_no_seq.nextval, '날씨가 맑습니다', null, '철수', sysdate, qnaboard_no_seq.currval, 1, 0);

 

세번째 게시물 입력 - 새 글 입력
insert into qnaboard
values(qnaboard_no_seq.nextval, '모임이 있습니다', null, '영희', sysdate, qnaboard_no_seq.currval, 1, 0);

 

네번째 게시물 입력 - 첫번째 게시물의 답글

답글이 올라오는 경우에는 답글에 부여 할 seq를 먼저 확보해줍니다.
update qnaboard
set seq=seq+1
where grp=1 and seq>1;

 

insert into qnaboard
values(qnaboard_no_seq.nextval, '반가워요', null, '만수', sysdate, 1, 2, 1);

 

다섯번째 게시물 입력 - 네번째 게시물의 답글
update qnaboard
set seq=seq+1
where grp=1 and seq>2;

 

insert into qnaboard
values(qnaboard_no_seq.nextval, '감사합니다', null, '길동', sysdate, 1, 3, 2);

 

여섯번째 게시물 입력 - 첫번째 게시물의 두번째 답글
update qnaboard
set seq=seq+1
where grp=1 and seq>1;

 

insert into qnaboard
values(qnaboard_no_seq.nextval, '환영합니다', null, '찬호', sysdate, 1, 2, 1);
 
일곱번째 게시물 입력 - 여섯번째 게시물의 답글
update qnaboard
set seq=seq+1
where grp=1 and seq>2;

 

insert into qnaboard
values(qnaboard_no_seq.nextval, '감사합니다', null, '길동', sysdate, 1, 3, 2);

 

게시판을 검색하는 경우, grp로 내림차순, seq로 오름차순으로 정렬하면 됩니다.
select case when lvl=0 then no
            when lvl>0 then null end no
, rpad('+', lvl, '-')||title title
, writer
, wdate
from qnaboard
order by grp desc, seq;


   NO TITLE                WRITER     WDATE
----- -------------------- ---------- -------------------
    3 모임이 있습니다      영희       2007-09-15 19:54:00
    2 날씨가 맑습니다      철수       2007-09-15 19:53:55
    1 안녕하세요           길동       2007-09-15 19:53:50
      +환영합니다          찬호       2007-09-15 19:54:21
      +-감사합니다         길동       2007-09-15 19:54:26
      +반가워요            만수       2007-09-15 19:54:08
      +-감사합니다         길동       2007-09-15 19:54:15

스트럿츠2 프레임워크 유효성 검사 지원

1. 신규 회원의 정보를 입력하는 폼에서 입력한 값의 유효성 검사

2. 입력 폼의 <form action=" ">에 struts.xml 에 입력한 action.class(extends ActionSupport) 넘긴 후

    (※ 입력 폼에서 -> action.class 로  웹 페이지의 모든 파라미터 값들이 자동으로 액션의 프로퍼티 값으로 세팅되게

        하려면 struts.xml <action> </action> 내에 <interceptor-ref name="params"/> 가 지정되야 한다.)

3. struts.xml 에 입력한 action.class 에서 유효성 체크 메소드

 public void validate() {

if()

addFielError("FieldName","ErrorMessage");

}

4. action.calss 에서 validate(){} 메서드 중에 if() 에 지정한 유효성에 충족치 않으면.

(※ 2번과 동일하게 validate() 검사를 하기위해서는 struts.xml <action></action> 내에

 <interceptor-ref name="validation"/>

 <interceptor-ref name="workflow"/>

가 존재하여야 한다. 그래야 5번의 RETURN INPUT 으로 반환하고. SUCCESS RESULT 를 실행하지 않는다.)

5. action.class 안에 execute() 메서드의 return 의 값이 SUCCESS 대신 INPUT 이 반환된다.

      6. INPUT 반환 후 에 result 가 INPUT 일때의 포워딩 설정

<action></action> 내에 <result name="input">/userRegForm.jsp</result>

    <result name="success>/userRegSuccess.jsp</result>

---------- 위의 설명에 충족한 struts.xml 참조 ---------

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">
  
<struts>
    <package name="actionSupport" namespace="" extends="struts-default">
        <action name="UserRegForm">
            <result>/userRegForm.jsp</result>
        </action>
        <action name="UserRegAction" class="action.UserRegAction">
            <interceptor-ref name="params"/>
            <interceptor-ref name="validation"/>
            <interceptor-ref name="workflow"/>
            <result name="input">/userRegForm.jsp</result>  -> 유효성 검사 실패시 return INPUT 성공시 SUCCESS
            <result name="success">/userRegSuccess.jsp</result>
        </action>
    </package>
</struts>


- 요소

필터 :URL의 확장자가 action 이면 FilterDispatcher 에 의해서 액션을 실행하기 위한 환경을 구축하도록 한다  (web.xml)

/* web.xml(struts2-core-2.3.4.1.jar) */

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>struts2_ActionSupport</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
 
  <!-- Struts2 FilterDispatcher -->
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
            <!-- Struts2 인코딩 설정(struts-default 참조) -->
            <init-param>
                <param-name>struts.i18n.encoding</param-name>
                <param-value>UTF-8</param-value>
            </init-param>
            <!-- Struts2 인코딩 설정(struts-default 참조) -->
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
 
</web-app>



액션 : 리절트가 필요로 하는 메시지를 제공한다 (action.java)

/* action.java */

package action;

import com.opensymphony.xwork2.ActionSupport;

/* Struts2 ActionSupport 상속 */
public class UserRegAction extends ActionSupport{

    private String userId;
    private String userPw;
    private String userName;

    /* Getter & Setter */
    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserPw() {
        return userPw;
    }

    public void setUserPw(String userPw) {
        this.userPw = userPw;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Override
    public String execute()throws Exception{
        return SUCCESS;
    }//end - execute Method


    @Override
    public void validate() {
        if(userId == null || userId.equals("")) {
            addFieldError("userId", "회원아이디를 입력해 주십시오");
        }
        if(userPw == null || userPw.equals("")) {
            addFieldError("userPw", "비밀번호를 입력해 주십시오");
        }
        if(userName == null || userName.equals("")) {
            addFieldError("userName", "이름을 입력해 주십시오");
        }
    }// end - validate Method()

}// end - class



매핑액션 : 실행 후 결과를 처리할 리절트와의 매피을 설정  (struts.xml)

/* struts.xml */

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">
   
<struts>
    <package name="actionSupport" namespace="" extends="struts-default">
        <action name="UserRegForm">
            <result>/userRegForm.jsp</result>
        </action>
        <action name="UserRegAction" class="action.UserRegAction">
            <interceptor-ref name="params"/>
            <interceptor-ref name="validation"/>
            <interceptor-ref name="workflow"/>
            <result name="input">/userRegForm.jsp</result>
            <result name="success">/userRegSuccess.jsp</result>
        </action>
    </package>
</struts>



리절트 : 메시지를 출력하기 위한 화면 처리를 담당  (view.jsp)

/* view.jsp */

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>회원가입 결과</title>
</head>
<body>
    <center>
    <b><font color="red">회원 가입이 완료되었습니다.</font></b><p>
    아 이 디 : ${userId}<p>
    비밀번호 : ${userPw}<p>
    이 름 : ${userName}<p>
    </center>
</body>
</html>

+ Recent posts