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

1. Statement

- JDBC로드 -> DB Connection 연결 -> 쿼리

/* JDBC 드라이브 로드 */
        Class.forName("oracle.jdbc.OracleDriver");

        /* DB연결 */
        Connection conn = DriverManager.getConnection(
                "jdbc:oracle:thin:@localhost:1521:orcl", "scott", "scott")

String query = "INSERT INTO USERINFO(NAME, ID, PASSWORD) VALUES(" + name + "," + id + "," + password + ")";

if(conn != null){

Statement stmt = conn.createStatement();

stmt.executeUpdate(query);

}



2. PreparedStatement

- JDBC로드 -> DB Connection 연결 -> 쿼리

/* JDBC 드라이브 로드 */
        Class.forName("oracle.jdbc.OracleDriver");

        /* DB연결 */
        Connection conn = DriverManager.getConnection(
                "jdbc:oracle:thin:@localhost:1521:orcl", "scott", "scott")

String query = "INSERT INTO USERINFO(NAME, ID, PASSWORD) VALUES(?, ?, ?)";

if(conn != null){
                PreparedStatement pstmt = conn.prepareStatement(query);
                pstmt.setString(1, name);
                pstmt.setString(2, id);
                pstmt.setString(3, password);
                pstmt.executeUpdate();
            }



※ PreparedStatement 가 훨씬 편함.

    예제)

<%@page import="java.sql.PreparedStatement"%>
<%@page import="java.sql.DriverManager"%>
<%@page import="java.sql.Driver"%>
<%@page import="java.sql.Statement"%>
<%@page import="java.sql.Connection"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" errorPage="DBError.jsp"%>
<!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>
    <%
        request.setCharacterEncoding("UTF-8");
   
        String name = request.getParameter("name");
        String id = request.getParameter("id");
        String password = request.getParameter("password");
       
        if(name == null || id ==null || password == null){
            throw new Exception("데이타를 입력하세요");
        }
        Connection conn = null;
        PreparedStatement pstmt = null;
       
        String DBurl = "jdbc:oracle:thin:@localhost:1521:orcl";
        String DBuser = "scott";
        String DBpassword = "scott";
        try{
            Class.forName("oracle.jdbc.OracleDriver");
            conn = DriverManager.getConnection(DBurl, DBuser, DBpassword);
           
            String query = "INSERT INTO USERINFO(NAME, ID, PASSWORD) VALUES(?, ?, ?)";
           
            if(conn != null){
                pstmt = conn.prepareStatement(query);
                pstmt.setString(1, name);
                pstmt.setString(2, id);
                pstmt.setString(3, password);
                pstmt.executeUpdate();
            }
            else {
                throw new Exception("DB 연결 실패!");
            }
        }
        finally{
            pstmt.close();
            conn.close();
        }
        response.sendRedirect("SubscriptionResult.jsp");
        %>
</body>
</html>


- CASE : 기능적 측변에서 DECODE와 흡사하지만 좀 더 확장된 기능을 제공

  • - CASE 함수는 DECODE함수가 제공하지 못하는 비교연산의 단점을 해결할 수 있는 함수이다.
  • - DECODE함수에서 비교연산을 수행하기 위해서는 GREATEST, LEAST등의 함수를 사용해야 하지만, 
  •   CASE함수에서는 조건 연산자를 모두 사용 할 수 있다.
  • - CASE함수는 IF.. THEN .. ELSE 구문과 비슷 하다. WHEN절 다음에 여러 조건이 올 수 있다.


- 사용법

CASE
WHEN (조건1) THEN
(값1)
WHEN (조건2) THEN
(값2)
ELSE
(값3)
END

=> 조건1이면, 값1, 조건2이면 값2, 그 외에는 값3을 출력

- 예제

위의 DECODE예제를 CASE함수로 변환한 예이다.
--위의  DECODE예제를 CASE함수로 변환한 예이다. 
SELECT deptno, 
       CASE deptno
         WHEN 10 THEN 'ACCOUNTING'
         WHEN 20 THEN 'RESEARCH'
         WHEN 30 THEN 'SALES'
         ELSE 'OPERATIONS'
       END as "Dept Name"
  FROM dept;

DEPTNO Dept Name
------- ----------
     10 ACCOUNTING
     20 RESEARCH
     30 SALES
     40 OPERATIONS 

아래는 WHEN절 다음에 연산자가 오는 예제이다.

--급여별로 인상율을 다르게 계산하였다. 
SELECT ename ,
       CASE
          WHEN sal < 1000  THEN sal+(sal*0.8)
          WHEN sal BETWEEN 1000 AND 2000 THEN sal+(sal*0.5)
          WHEN sal BETWEEN 2001 AND 3000 THEN sal+(sal*0.3)
          ELSE sal+(sal*0.1)
       END sal
  FROM emp; 


'공부 > ORACLE_SQL' 카테고리의 다른 글

ORACLE_[ 집계함수(Aggregate function) ]  (0) 2013.05.20
ORACLE_[ OUTER JOIN ]  (0) 2013.05.20
ORACLE_[ JOIN ]  (2) 2013.05.20
ORACLE_[CREATE TABLE]  (0) 2013.01.09
ORACLE_[DECODE문_사용법]  (0) 2012.10.05

- DECODE문 : 자바문법으로 따지면 IF ~ ELSE IF ~ ELSE 와 같은 구조

- DECODE 함수는 조건에 따라 데이터를 다른 값이나 컬럼값으로 추출 할 수 있다.

- DECODE(VALUE, IF1, THEN1, IF2, THEN2...) 형태로 사용 할 수 있다.

- VALUE 값이 IF1일 경우에 THEN1 값을 반환하고, VALUE 값이 IF2일 경우에는 THEN2 값을 반환한다.

- DECODE 함수 안에 DECODE함수를 중첩으로 사용 할 수 있다.


- 구문

DECODE({column | expression}, search1, result1 [,search2,result2] ...

[,default] )

: column 값이 search1 이면, result1 출력, 아니면 default 출력

 

- 예제

아래는 DECODE 함수의 일반적인 예제이다.

-- 부서번호가 10이면 ACCOUNTING, 20이면 RESEARCH, 30이면 SALES
-- 나머지는 OPERATIONS를 출력하는 예제
SELECT deptno, DECODE(deptno, 10 , 'ACCOUNTING' ,
                              20 , 'RESEARCH' ,
                              30 , 'SALES', 'OPERATIONS') name
  FROM dept;

DEPTNO NAME
------ ----------
     10 ACCOUNTING
     20 RESEARCH
     30 SALES
     40 OPERATIONS

아래는 DECODE 함수에서 집계 함수를 사용한 예제이다

-- 10부서는 급여합계를, 20부서는 최대값을, 30부서는 최소값을 출력하는 예제
SELECT deptno, DECODE(deptno, 10 , SUM(sal),
                              20 , MAX(sal),
                              30 , MIN(sal)) sal
  FROM emp
 GROUP BY deptno; 

DEPTNO        SAL
--------- --------
       30      950
       20     3000
       10     8750 

DECODE함수는 집계함수와 함께 통계 데이터를 추출할 때 많이 사용한다. 아래는 부서별로 급여합계를 조회하는 예이다

-- 부서별로 급여 합계를 출력한다. 
SELECT deptno, NVL(SUM(DECODE(deptno, 10, sal)),0) deptno10, 
               NVL(SUM(DECODE(deptno, 20, sal)),0) deptno20,
               NVL(SUM(DECODE(deptno, 30, sal)),0) deptno30,
               NVL(SUM(DECODE(deptno, 40, sal)),0) deptno40
  FROM emp
 GROUP BY deptno; 

DEPTNO   DEPTNO10   DEPTNO20   DEPTNO30   DEPTNO40
------- --------- --------- ---------- ----------
     30         0         0       9400          0
     20         0     10875          0          0
     10      8750         0          0          0 

아래 부서별 급여합계 예를 보면 일반적인 집계함수를 사용할 때는 급여 합계가 행으로 조회가 되지만, DECODE와 MAX함수를 사용하면 열로 값을 표시할 수 있다.

-- 부서별로 급여 합계를 행으로 출력한다. 
SELECT d.deptno, NVL(SUM(e.sal),0) sal
  FROM emp e, dept d
 WHERE e.deptno(+) = d.deptno
 GROUP BY d.deptno; 

DEPTNO        SAL
-------- ----------
      10       8750
      20      10875
      30       9400
      40          0



-- 부서별로 급여 합계를  열로 출력한다. 
SELECT MAX(NVL(SUM(DECODE(deptno, 10, sal)),0)) deptno10, 
       MAX(NVL(SUM(DECODE(deptno, 20, sal)),0)) deptno20,
       MAX(NVL(SUM(DECODE(deptno, 30, sal)),0)) deptno30,
       MAX(NVL(SUM(DECODE(deptno, 40, sal)),0)) deptno40
  FROM emp
 GROUP BY deptno; 

DEPTNO10   DEPTNO20   DEPTNO30   DEPTNO40
--------- ---------- ---------- ----------
    8750      10875       9400          0


'공부 > ORACLE_SQL' 카테고리의 다른 글

ORACLE_[ 집계함수(Aggregate function) ]  (0) 2013.05.20
ORACLE_[ OUTER JOIN ]  (0) 2013.05.20
ORACLE_[ JOIN ]  (2) 2013.05.20
ORACLE_[CREATE TABLE]  (0) 2013.01.09
ORACLE_[CASE문 사용법]  (0) 2012.10.05

- JSTL 의 함수 라이브러리는 익스프레이션 언어(EL) 식 안에서 사용할수 있는 함수 라이브러리

이 함수들은 주로 문자열을 처리하는 일을 하며, 자바 JDK 라이브러리의 java.lang.String 클래스에 속하는 메서드들과 거의 같은 기능을 제공한다.

예) substring, trim

 

- 사용법

 

1. Taglib 등록

<%@ taglib prefix="fn" uri=http://java.sun.com/jsp/jstl/functions %>

 

2. 함수 라이브러리의 함수들

1) substring(str, index1, index2) : str 의 index1 부터 index2 - 까지의 문자열 리턴

2) substringAfter(str1, str2) : str1 에서 str2 를 찾아서 그 후의 부분문자열 리턴

3) substringBefore(str1, str2) : str1 에서 str2 를 찾아서 그 전의 부분문자열 리턴

4) toUpperCase(str) : 모든 소문자를 대문자로 치환한 값을 리턴

5) toLowerCase(str) : 모든 대문자를 소문자로 치환한 값을 리턴

6) trim(str) : 문자열에서 앞뒤 공백 문자를 제거한 결과를 리턴

7) replace(str, src, dest) : str 문자열에 포함된 src 를 dest 로 치환한 결과를 리턴

8) indexOf(str1, str2) : str1에 포함된 str2의 시작 인덱스를 리턴

9) startsWith(str1, str2) : str1이  str2 로 시작하면 true, 그렇지 않으면 false 리턴

10) endsWith(str1, str2) : str1이 str2 로 끝나면 true, 그렇지 않으면 false 리턴

11) contains(str1, str2) str1이 str2를 포함하면 true, 그렇지 않으면 false 리턴(대소문자 구분)

12) containslgnoreCase(str1, str2) : str1이  str2를 포함하면 true, 그렇지 않으면 false 를 리턴,

contains 함수와는 달리 대소    문자 구별하지 않고 비교함.

13) split(str1, str2) : str1을 str2를 기준으로 분리해서 만든 부문자열들의 배열을 리턴

14) join(str1, str2) : arr 배열의 모든 항목을 합쳐서 리턴, 항목사이에는 str2가 들어옴

15) escapeXml(str) : HTML 문법에 의한 특수문자로 취급되는 모든 문자를 이스케이프 시퀀스로 치환하여 결과를 리턴

16) length(obj) : obj가 문자열이면 문자열의 길이, List 나 Collection 이면 항목의 수를 리턴.

 

3, 사용법

<c:set var="greeting" value="How Are You?" />

기본출럭 : ${greeting}

대문자 : ${fn:toUpperCase(greeting)}

소문자 : ${fn:toLowerCase(greeting)}

- 등록할 파일 : TLD 파일(ex : 웹어플리케이션/web-app/WEB-INF/tld/math-functons.tld)

 

- TLD 파일의 골격(TLD 파일 생성하기)

<taglib xmlns="http://java.sun.com/xml/ns/javaee" version="2.1">

                                (TLD 문법의 식별자)                (TLD 문법의 버전)

<tlib-version>1.0</tlib-version> -> 태그 라이브러리의 버젼

<short-name>math</short-name> -> 태그 라이브러리의 이름

<function>

<name>squareroot</name> ->  EL 함수의 이름(나름대로 정한 EL 함수의 명칭)

<function-class>java.lang.Math</function-class> -> 자바 정적메서드(static method)가 속하는 클래스 풀네임

<function-signature>double sqrt(double)</function-signature> -> 정적 메서드의 시그니처

</function>

</taglib>

 

- TLD 파일 생성을 하여도 바로 호출할 수 없다. web.xml 파일에 TLD 파일을 등록해야한다.

<web-app>

<taglib>

<taglib-uri>/math-functions.tld</taglib-uri> -> TLD 파일의 식별자 명칭

<taglib-location>tlds/math-functions.tld</taglib-location> -> TLD 파일의 실제 경로명

</taglib>

</web-app>

 

- JSP 페이지에 호출하기 위해 지시어(<%@ %>) 사용해서 등록하기

<%@taglib uri="/math-functions.tld" prefix="m" %>

 

- JSP 페이지에서 호출해서 사용하기

${m:squareroot(4)}

 설명 : java.lang.Math 클래스의 sqrt 메서드를 'squareroot' 라는 이름의 EL 함수로 등록한다!! 잊어버리자 말자.

 

----------------------------------------------------------------------------------------------------

※ 테스트를 하는데 web.xml 에 <taglib> 태그에서 오류가 발생!!!

    google 검색 결과 답변은...

web.xml 이부분에서
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

빨간색으로 나타낸 부분을 지우면 된다.
XML 스키마나 DTD 지정은 JSP/Servlet 어플리케이션 지정할 때 매우 민감하게 작용합니다.
요즘에는 Servlet Spec 2.3 이 보통이고, 최근들어 2.4도 많이 쓰는 추세입니다. 2.2는 안쓰게 된지 오래됐습니다.
그러니까 처음에 지정한 DOCTYPE에서 2.2로 지정하면 안됩니다.(DOCTPYE 삭제해버린다) 
Servlet 스펙 2.4(가장 최신)에서는 web.xml 에서 TLD 파일을 지정하는 기능이 없다.

내 이클립스에서 생성된 web.xml version=2.5 에서 오류가 발생하는 것이다. 

해결방법

1. 위의 빨간 밑줄 부분을 삭제한다.

2. web.xml 에 TLD 파일 지정하지않고, 바로 JSP 에서 <%@ taglib uri="tld파일경로 지정" prefix="c" %>

    TLD 설치경로 : 웹 어플리케이션 디렉터리/WEB-INF 서브디렉터리 아래이면 어느 곳이든 상관 없다.

EX> 자바빈 클래스의 예

public class PersonInfo {

private String name;

private int age;

private String tell;

/* Getter & Setter */

    /* getter & setter */
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setTell(String tell) {
        this.tell = tell;
    }
    public void setTell(String tell) {
        this.tell = tell;
    }

} // end - class

 

EX> 자바빈 프로퍼티 입력 JSP

<%

PersonInfo personInfo = new PersonInfo();

personInfo.setName("daniel");

personInfo.setAge("27");

personInfo.setTell("177");

request.setAttribute("PERSONINFO", personInfo);

RequestDispatcher dispatcher =

request.getRequestDispatcher("-- forward 시킬 JSP 경로 --");

dispatcher.forward(request, response);

%>

 

EX> 자바빈 프로퍼티 받아서 출력하는 JSP

이름 : ${PERSONINFO.name}<br>

나이: ${PERSONINFO.age}<br>

번호: ${PERSONINFO.tell}<br>

 

※ 제발, 까져먹지말자...

find 명령어
find는 글자 그대로 사용자 원하는 파일을 찾아주는 역활을 한다.
단순히파일 이름만을 가지고 찾는 것은 물론, 파일 모드, 파일 타입, 크기, 마지막으로 접근한 시간등 여러가지 다양한 조건으로 파일을 찾아준다.
형식 : find <경로> <연산자>
경로 : find가 파일을 찾을 처음 위치를 지정한다. 예을 들어 '/'는 /(root)부터 찾고, '.'은 현재 디렉토리 부터 찾는다.

-name : 확장자가 txt 인 화일을 찾는다.
find / -name '*.txt'

-perm : 퍼미션이 666(-rw-rw-rw-)인 화일을 찾는다.
find . -perm 666

-type : 파일의 타입을 지정하여, 찾고자하는 파일을 찾는다.
타입의 종류는 다음과 같다.
b : 블록 특수 파일(block device)
c : 캐릭터 특수 파일 (character deice)
d : 디렉토리(directory)
f : 일반파일(file)
l : 심볼릭 링크(link)
p : 파이프 (pipe)
s : 소켓 (socket)

현재 디렉토리 아래에 있는 서브디렉토리를 모두 찾는다.
find . -type d

-atime ?n/n : 최근 n일 이전에 액세스된 파일을 찾아준다.(accessed time)
+n은 n일 또는 그보다 더 오래 전의 파일
-n은 오늘 부터 n일 전까지의 파일
n은 정확히 n일 전에 액세스되었음을 의미한다.

시스템 전체에서 한 달 또는 그 이상의 기간동안 한번도 액세스하지 않은 디렉토리
find / -atime +30 -type d

ctime ?n/n : ctime은 파일의 퍼미션을 마지막으로 변경시킨 날짜를 의미한다. (changed time)
+n은 n일 또는 그보다 더 오래 전의 파일
-n은 오늘 부터 n일 전까지의 파일
n은 정확히 n일 전에 수정되었음을 의미한다.

현재 디렉토리 아래에서 최근 일주일 동안 고친 파일
find . -ctime -7

-mtime ?n/n : mtime은 파일내의 data를 마지막으로 변경한 날짜를 의미한다.(modified time)
+n은 n일 또는 그보다 더 오래 전의 파일
-n은 오늘 부터 n일 전까지의 파일
n은 정확히 n일 전에 수정되었음을 의미한다.
-cnewer 파일명 : '파일명' 부분에 적어준 파일보다 더 최근에 수정된 파일들을 찾아준다.

test.txt 화일이 생성된 이후의 화일을 찾는다.
find . -cnewer test.txt -print

-user 유저네임 : '유저네임' 부분에 지정한 유저 소유의 파일을 찾아준다.

nalabi 라는 계정의 화일을 찾아준다.
find / -user nalabi

그외 자주 쓰이지 않지만 추가 옵션으로는 아래와 같다.

-maxdepth n
0이 아닌 정수값으로 경로 깊이를 지정하여 검색을 할 경우에 사용한다. 예를들어, '-maxdepth 1'은 시작위치로 지정한 디렉토리만 검색하고 하위 디렉토리는 찾지 않는다. -mindepth 옵션은 반대로 동작한다. 즉, 지정한 숫자만큼의 깊이부터 그 하위 디렉토리를 검색한다. (GNU find 버전)

-follow
심볼릭 링크된 디렉토리도 검색을 할 경우에 사용한다.

-mount
현재의 파일 시스템과 동일한 타입의 파일 시스템에서만 검색을 할 경우에 사용한다.
test에는 다음과 같은 방법들이 있으며, test에 사용하는 인수에는 보다 큰 수를 의미하는 `'나, 보다 작은 수를 의미하는 `'를 함께 사용할 수 있다. 인수에 아무 연산자가 없을 경우에는 정확히 그 인수 값을 의미한다.

-group
특정 그룹 소유의 파일들을 찾을 경우에 사용한다.

-nouser
소유자가 없는 파일을 찾을 경우에 사용한다. 즉, /etc/passwd 파일에 없는 소유자의 파일을 찾을 경우에 사용한다.

-nogroup
올바른 그룹의 소유가 아닌 파일을 찾을 경우에 사용한다. 즉, /etc/groups 파일에 없는 그룹의 소유인 파일을 찾을 경우에 사용한다.

newer file1 file2
`file1' 보다는 이후에 `file2' 보다는 이전에 생성되거나 변형된 파일들을 찾을 경우에 사용한다.

-size n[bckw]
크기가 n 유닛(unit)인 파일을 찾을 경우에 사용한다. 유닛은 기본 설정(`b''와 함께 사용한 경우와 동일)인 512 바이트의 블럭, `c'를 사용할 경우에는 1 바이트, `k'를 사용할 경우에는 킬로바이트, `w'를 사용할 경우에는 2 바이트의 워드 크기를 나타낸다.

-empty
비어있는 파일이나 디렉토리를 찾을 경우에 사용한다. (GNU find 버전)

-regex
정규표현식(regular expression)을 이용하여 파일들을 찾을 경우에 사용한다. `-iregex'는 대소문자를 구별하지 않을 경우에 사용한다. (GNU find 버전)
action은 test에서의 조건과 일치하는 파일들에 대해 수행할 작업을 명시하는 것으로 다음과 같은 방법들이 있다.

-print
찾은 파일들을 표준출력(stdout)으로 출력한다. 기본으로 설정되어 있다.

-fprint file
찾은 파일들을 `file'로 출력한다. `file'이 존재 하지 않을 경우에는 새로 생성되고, 존재할 경우에는 기존의 파일은 없어진다. (GNU find 버전)

-exec
파일을 찾았을 경우, 찾은 파일들에 대해 특정 명령을 수행 할 때 사용한다. 일반적으로 `-exec command {} ;'의 형식을 취한다.

-ok
-exec와 동일한 작업을 한다. 다른 점은, 명령을 실행할 때마다 실행 의사를 물어본다.

-ls
`ls -dils' 형식으로 찾은 파일들의 정보를 출력할때 사용한다.

-fls file
`ls'와 동일하게 동작하며 결과를 `file'로 출력한다.
operator는 test에서 사용한 옵션들을 조합하여 조건식을 만들고자 할때 사용는 것으로 다음과 같은 방법들이 있다. (설명 순서는 우선순위(precedence)에 따른다.)

[예제]
자신의 홈 디렉토리에서 확장자가 '.txt'인 파일을 찾을 경우
$ find -name "*.txt'' -print

현재 디렉토리 밑에서 첫글자가 영어 대문자인 모든 파일을 찾을 경우
$ find . -name "[A-Z]*'' -print

'/usr/local'에서 첫 두글자는 영어 소문자이고 세번째 한자리는 숫자로 시작하는 이름을 가진 파일을 찾을 경우
$ find /usr/local -name "[a-z][a-z][0-9]*'' -print

확장자가 .txt 인 파일을 찾으면서 현재 디렉토리와 한 단계 밑의 디렉토리에서만 파일을 찾을 경우
$ find -maxdepth 2 -name "*.txt'' -print

현재 디렉토리 밑에서 `zzang'이라는 이름을 가진 사용자 소유의 파일을 찾을 경우
$ find . -user zzang -print

시스템에서 소유자나 그룹이 없는 파일을 찾을 경우 (크래커가 만들어 놓은 파일일 경우도 있음)
$ find / -nouser -o -nogroup -print

자신의 홈 디렉토리에서 최근 3일 동안 변경된 파일들을 찾을 경우
$ find . -mtime -3 -print

'/tmp'에서 최근 5일 동안 변경되지 않은 파일들을 찾아서 삭제할 경우 (파일을 삭제할 때마다 삭제할 것인가를 물어보도록)
$ find . -mtime +5 -print -ok rm {} ;

현재 디렉토리 밑에 있는 모든 포스트 스크립트 파일(.ps)을 찾아서 gzip으로 압축을 하고 그 목록을 result.txt라는 파일에 저정할 경우
$ find . -name "*.ps" -fprint result.txt -exec gzip {} ;

크랙커의 침입이 의심스러워 자신의 시스템에서 suid와 guid가 설정된 일반 파일들을 찾아서 권한을 확인할 경우
$ find / -type f -perm +6000 -print -ls

시스템 관리의 실수로 일반 사용자가 쓰기 권한을 갖도록 설정되어 있는 파일을 찾아서 실행 권한을 없애는 경우 (단, 링크 파일은 제외함)
$ find / -perm +2 ! -type l -print -exec chmod o-w {} ;

이밖에 예들..

옵션들을 차례로 기술하여 파일을 찾을 경우 AND 연산이 적용된다.
하지만 -o 옵션을 이용하여 OR 을 적용할수도 있고 ( ) 를 이용하여 그룹을 지어 적용할수도 있다. 이밖에 NOT 연산도 가능한데 옵션 앞에 !부호를 달아 주면 된다. 밑의 몇가지 예를 살펴 보자.

# find ./ -atime 60 -mtime 120
( AND 논리연산으로 접근한지 60일 지난 파일중 수정한지 120 일 지난 파일 )

# find ./ \( -user design -o -group design \)
(OR 논리연산으로 소유자나 그룹이 design 인경우 )

# find ./ \( ! -user design -o ! -group design \)
(NOT 논리연산으로 소유자나 그룹이 design 이 아닌 경우 )

-perm 옵션은 숫자형태의 특정 접근 모드를 이용하여 파일을 검색하는 옵션이다.
옵션별 사용예를 들어 보자.
-perm 75 : permission = 755
-perm -002 : 모든 사람들이 기록할수 있는 파일
-perm -4000 : SUID 액세스 설정
-perm -2000 : SGID 액세스 설정

-print 옵션은 근래는 기본으로 들어간다. 굳이 붙일 필요는 없고, -exec, -ok옵션 사용시에는 반드시 마지막에 \; 으로 구문을 마감해야 한다.

예를 들어서 find 로 검색한 파일을 지우기 위해서는 다음과 같이 한다.
# find ./ -name *.* -exec rm -f {} \;

파일 크기가10M 이상이며 한달 이상동안 수정되지 않은 파일을 찾는다.
# find / -type f -size 20480 -mtime 30 -ls
# find / -type f -size 20480 -mtime 30 -exec rm -f {} \;
(대응되는 파일삭제)

보안에 관련된 팁이다. 모든 setuid, setgid 를 검색한다.
# find / -type f \( -perm -4000 -o -perm -2000 \)
# find / -type f \( -perm -4000 -o -perm -2000 \) | diff - setuidlist
(찾아된 setuid,setgid를 기존에 작성한 목록과 비교하여 새로 추가 된것이 있는지를 확인한다.)

침입을 당한 시스템에 있는 setuid 와 setgid 파일을 모두 찾아 본다(특히, setuid root 파일). 일반적으로 나중에 재침입을 위해 침입자들은 이러한 setuid 설정이 된 /bin/sh 이나 /bin/time 등과 같은 복사본을 만들어 놓는 경우가 많다.
이러한 파일은 찾아서 삭제해야만 한다. 유닉스의 find(1) 프로그램을 시용하여 이러한 setuid 나 setgid 파일을 찾아낼 수가 있다.
예를 들면, 아래 명령을 이용하여 setuid root 파일과 setgid kmem 파일을 전체 파일 시스팀에서 찾을 수 있다.
# find / -user root -perm -4000 -print find / -group kmem -perm -2000 -print

주의할 점은 위 명령은 NFS/AFS로 마운트된 파일 시스템까지 몽땅 찾으므로 이러한 파일 시스템을 찾는 대상에서 제외하고자 하는 경우, “-xdev” 옵션을 사용한다(단, 이 옵션은 지원하지 않는 find 도 있음).
# find / -user root -perm -4000 -print -xdev

필요한 명령어 두개 섞어서 써도 가능함!!

내가 필요했던 명령문

# find ./ -name "*.txt" -mtime +7 -exec gzip {};

(일주일 지난 .txt 로 끝나는 파일을 찾아서 gzip로 압축한다)

+ Recent posts