'분류 전체보기'에 해당되는 글 2491건

  1. 2009.05.04 기본 화면 메뉴 만들기(Flash Professional만 해당)
  2. 2009.04.28 java 파일 저장할때.. 중복이름 체크 2
  3. 2009.04.28 CLOB+MULTI UPLOAD
  4. 2009.04.28 oracle 데이터 삭제 후 commit 데이터 복구 하는방법.[oracle 삭제된 데이터 복구] 2
  5. 2009.04.28 tomcat5 1
  6. 2009.04.28 오라클에서의 데이터 암호화 기능
  7. 2009.04.28 oracle 데이터 암호화 2
  8. 2009.04.27 Oracle Version 확인
  9. 2009.04.27 oracle 서비스 명 알아내기
  10. 2009.04.27 oracle 실행된 쿼리확인 하기... 1
  11. 2009.04.25 문자열 자바스크립트에서 금액표시하기 JavaScript
  12. 2009.04.25 자주쓰는 자바스크립트 함수
  13. 2009.04.25 Oracle Rollup, Cube, and Grouping SetsOracle
  14. 2009.04.24 FLASH.....
  15. 2009.04.24 오라클 주기적 백업 방법
  16. 2009.04.24 유용한 팁(일련번호 관련)
  17. 2009.04.24 log4j
  18. 2009.04.24 [JAVA] Timer를 이용하여 일정시간에 작업수행하기(Schedule)
  19. 2009.04.24 iBATIS를 이용해서 간단한 예제를 만들어봅시다.
  20. 2009.04.24 javascript : CalendarView
  21. 2009.04.24 /lecture/oracle/sql
  22. 2009.04.24 [ORACLE] MERGE INTO를 활용해 없는 데이터는 INSERT, 있는 데이터는 UPDATE를 해봅시다.
  23. 2009.04.24 [Oracle] TDE(Transparent Database Encryption) 투명한 데이터베이스암호화 체험후기
  24. 2009.04.24 oracle - ROWNUM의 동작 원리와 활용 방법
  25. 2009.04.24 iBatis 꽤 맘에 드는 기능 sql 조합..
  26. 2009.04.24 iBatis QueryForMapExample 사용 예제 [펌]
  27. 2009.04.24 iBatis를 해보고자 예제 실습을 따라했다.
  28. 2009.04.24 org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory,
  29. 2009.04.23 eclipse properties Editor [이클립스 설정파일 편집기-한글적용]
  30. 2009.04.22 이클립스 실행시 아래 에러문발생시 workspace in use or cannot be created, choose a different one
98..Etc/Etc...2009. 5. 4. 17:40
반응형

기본 화면 메뉴 만들기(Flash Professional만 해당)

이 단원에서는 응용 프로그램의 기본 화면에 메뉴를 만듭니다. 이 메뉴는 Specials, View Video 및 Reservations의 세 가지 옵션으로 구성되어 있습니다.

  1. 테스트 장치 및 내용 형식 선택 (Flash Professional만 해당)에서 저장한 파일을 Flash Professional 8에서 엽니다.
  2. 타임라인 윈도우(윈도우 > 타임라인)의 메뉴 레이어에서 프레임 1을 선택합니다.
  3. 메뉴를 만들기 위해 라이브러리 패널(윈도우 > 라이브러리)을 열고 specials button 심볼을 스테이지로 드래그합니다.

    레스토랑을 소개하는 텍스트 필드(이미 만들어져 있음) 아래에 버튼을 배치합니다.

  4. specials button을 선택한 상태로 속성 관리자에서 인스턴스 이름 텍스트 상자에 specials_btn을 입력합니다.
  5. video button 심볼의 인스턴스를 스테이지로 드래그하여 specials button 아래에 배치합니다.
  6. video button을 선택한 상태로 속성 관리자에서 인스턴스 이름 텍스트 상자에 video_btn을 입력합니다.
  7. reservations button 심볼의 인스턴스를 스테이지로 드래그하여 video button 아래에 배치합니다.
  8. reservations button을 선택한 상태로 속성 관리자에서 인스턴스 이름 텍스트 상자에 reservations_btn을 입력합니다.

    응용 프로그램의 스테이지가 다음 예와 같은 모습이어야 합니다.



  9. 타임라인의 ActionScript 레이어에서 프레임 1을 선택합니다.
  10. 액션 패널(윈도우 > 액션)에 다음 코드를 입력합니다.
    stop();
    _focusrect = false;
    fscommand2("SetSoftKeys", "Set Location", "Exit");
    fscommand2("SetQuality", "high");
    fscommand2("Fullscreen", "true");
    

    이 코드는 다음과 같은 작업을 수행합니다.

  11. 이 프레임에서 재생 헤드를 중단합니다.
  12. Flash Lite에서 기본적으로 현재 포커스를 갖고 있는 버튼 또는 입력 텍스트 필드 주위에 표시되는 포커스 사각형이 나타나지 않도록 합니다(Flash Lite 2.x 응용 프로그램 개발의 초점 사각형 참조).
  13. 응용 프로그램에서 사용할 소프트 키를 등록합니다.
  14. 플레이어의 렌더링 품질을 높게 설정합니다. 기본적으로 Flash Lite는 그래픽 내용을 보통 품질로 렌더링합니다.
  15. 플레이어에서 응용 프로그램을 전체 화면으로 표시하도록 합니다.

    Flash Lite가 전체 화면 모드일 때는 SetSoftKeys 명령에서 지정한 레이블이 표시되지 않습니다. 따라서 스테이지에 사용자 정의 소프트 키 레이블을 추가해야 합니다.

  16. 다음 코드를 추가하여 메뉴 버튼과 선택 포커스에 대한 버튼 이벤트를 처리합니다.
    // 응용 프로그램을 시작할 때와 다른 화면에서
    // 기본 화면으로 돌아올 때 초기 포커스를 
    // 설정합니다.
    if (selectedItem == null) {
        Selection.setFocus (specials_btn);
    } else {
        Selection.setFocus (selectedItem);
    }
    // 각각의 메뉴 버튼에 onPress 이벤트 핸들러를 할당하고
    // selectedItem 변수를 
    // 선택된 버튼 객체로 설정합니다.
    specials_btn.onPress = function () {
        gotoAndStop ("specials");
        selectedItem = this;
    };
    video_btn.onPress = function () {
        gotoAndStop ("video");
        selectedItem = this;
    };
    reservations_btn.onPress = function () {
        if (location_so.data.phoneNumber == undefined) {
        // 사용자가 위치를 지정하지 않았으므로
        // "위치 설정" 화면으로 이동합니다.
            gotoAndStop ("options");
        }
        else {
            // 공유 객체에 번호를 호출합니다.
            var phoneNum = location_so.data.phoneNumber;
            getURL ("tel:" + phoneNum);
        }
        selectedItem = this;
    };
    

    specials_btnvideo_btn이라는 버튼에 할당된 onPress 이벤트 핸들러가 각각 "specials" 및 "video" 프레임으로 재생 헤드를 보냅니다. 위의 단원에 대한 내용은 이 자습 과정의 뒷부분에서 만들 것입니다(스페셜 화면 만들기(Flash Professional만 해당)비디오 화면 만들기(Flash Professional만 해당) 참조).

    사용자가 Reservations 옵션을 선택하면 onPress 핸들러가 location_so 공유 객체에 지정된 전화 번호로 전화를 겁니다.(이 과정의 뒷부분에서 공유 객체를 만들기 위한 코드를 만들 것입니다.) 사용자가 예약 전화를 할 레스토랑 위치를 아직 지정하지 않은 경우에는 응용 프로그램에서 재생 헤드를 "options" 프레임으로 보내어 거기서 사용자가 예약하려는 레스토랑 위치를 선택하도록 합니다.

  17. 이제 다음 코드를 추가하여 왼쪽 및 오른쪽 소프트 키에 대한 키 리스너를 만듭니다.
    Key.removeListener(myListener);
    var myListener:Object = new Object();
    myListener.onKeyDown = function() {
        var keyCode = Key.getCode();
        if (keyCode == ExtendedKey.SOFT1) {
            // 왼쪽 소프트 키 이벤트 처리
            gotoAndStop("options");
        } else if (keyCode == ExtendedKey.SOFT2) {
            // 오른쪽 소프트 키 이벤트 처리 
            fscommand2("Quit");
        }
    };
    Key.addListener(myListener);
    

    이 코드는 키 리스너 객체를 사용하여 왼쪽 및 오른쪽 소프트 키 이벤트를 처리합니다. 사용자가 왼쪽 소프트 키를 누르면 재생 헤드가 "options" 프레임으로 보내지고 오른쪽 소프트 키를 누르면 응용 프로그램이 종료됩니다.

    이벤트 리스너 사용에 대한 자세한 내용은 Flash Lite 2.x 응용 프로그램 개발의 키 수신기를 사용하여 키 누르기 이벤트 처리(Flash Professional만 해당)를 참조하십시오.

  18. 마지막으로 코드를 추가하여 예약하려는 레스토랑의 위치를 저장하는 공유 객체를 초기화합니다.
    // 공유 객체 리스너 함수를 정의합니다.
    function so_listener (the_so:SharedObject) {
        if (the_so.getSize () == 0) {
        // 공유 객체가 존재하지 않으므로 
        // 사용자가 아직 환경 설정을 하지 않았습니다. 
        }
        SharedObject.removeListener ("location");
    }
    // 공유 객체를 만듭니다.
    location_so = SharedObject.getLocal ("location");
    // SharedObject 리스너 객체를 추가합니다.
    SharedObject.addListener ("location", this, "so_listener");
    
  19. 지금까지 만든 작업을 테스트하려면 컨트롤 > 무비 테스트를 선택합니다.

    이때 사용자가 해당 버튼에 포커스를 두고 에뮬레이터의 선택 키 또는 컴퓨터 키보드의 Enter 키를 눌러서 메뉴 항목을 선택할 수 있어야 합니다. 다음 단원에서는 스페셜 화면, 비디오 화면 및 기본 위치를 지정하는 화면을 만들 것입니다.

Posted by 1010
01.JAVA/Java2009. 4. 28. 16:10
반응형

package struts02.util;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;

import org.apache.struts.upload.FormFile;

public class BoardUtil {


 public static String saveFile(FormFile uploadFile) {
  String physicalFileName=null;
  InputStream is;
  try {
   is = uploadFile.getInputStream();
   String uploadFileName = uploadFile.getFileName();
   physicalFileName = getPhysicalFileName( uploadFileName );
   String filePath = "F:/Struts_And_iBatis/workspace/struts-board-02/WebContent/upload/";
   OutputStream os = new FileOutputStream(filePath + physicalFileName);

   int byteRead = 0;
   byte[] buf = new byte[8192];
   while ((byteRead = is.read(buf)) != -1) {
    os.write(buf, 0, byteRead);
   }
   os.close();
   is.close();
  } catch (Exception e) {
   e.printStackTrace();
  }
  return physicalFileName;
 }//end saveFile()

 private static String getPhysicalFileName(String uploadFileName) {
  String systemTime=new Date().getTime()+"";//
  if( uploadFileName.indexOf(".") == -1 ){ //파일이름에 .이 없으면
   return uploadFileName+"_"+systemTime;
  }else{
   int index = uploadFileName.lastIndexOf(".");
   //확장자를뺀파일명
   String _tmpFileName = uploadFileName.substring(0, index);
   // Extension
   String _extName = uploadFileName.substring( index );
   return _tmpFileName + "_"+systemTime+_extName;
  }
 }

}

Posted by 1010
01.JAVA/Java2009. 4. 28. 15:40
반응형
oracle 의 clob 타입으로 jsp에서 select,insert,update 방법을 예제로 만들어 봤습니다.

참고하시기 바랍니다.
Posted by 1010
02.Oracle/DataBase2009. 4. 28. 13:51
반응형
나른한 점심시간.
비몽사몽중에 다음과 같은 업무요건가 들어왔다.
A 라는 테이블에서 errcode 가 309 이고 pt_no 가 3 이상인 모든 데이터를 지워라.

sql 로 표현하면 다음과 같은 쿼리가 나온다.

delete from A where errcode = '309' and pt_no >= 3;
commit;

하지만 쿼리를 다음과 같이 잘못 작성한 무대리.
delete from A where pt_no >= 3;
commit;

commit 하고 담배를 피고 와서 select 를 해보니
아뿔사!! pt_no 가 3 이상인 모든 데이터가 지워진 것이다.
commit 까지 했으니 돌릴 방법이 없을 것 같아
조용히 도망치는 무대리!!

하지만 다음 쿼리로 commit 까지 마친 delete 컬럼에 대해서 복구를 할 수 있다.

INSERT INTO A
SELECT *
FROM A
AS OF TIMESTAMP(SYSTIMESTAMP - INTERVAL '15' MINUTE);

현재 시간 기준으로 15분 이내의 데이터들을 모두 복구하는 쿼리!

멋지다 오라클!

 hanho9@nate.com
Posted by 1010
98..Etc/Tomcat2009. 4. 28. 13:24
반응형

목차

1 Apache/Tomcat 연동시 jsp/servlet 은 Tomcat으로, 나머지는 Apache가 처리하게 설정하기
2 TOMCAT5에서 한글넘겨받을때
3 TOMCAT5 에서 Altibase DB Pool 설정
4 Tomcat5 Only 일때 디렉토리 내 심볼릭 링크가 안먹힐 때
5 Apache Tomcat Native library not found on the java.library.path
6 80 포트로 띄우고 싶을때
6.1 ipchains/iptables 이용하기
6.2 JSVC 를 이용하기
6.3 CoyoteConnector
7 @include 로 포함한 페이지의 한글이 깨질 때
8 common-fileupload 사용시 넘겨받은 한글 파라메터가 깨질 때
9 Tomcat 5.x 에서 jsp 내의 스크립틀릿 사용금지하기
10 Tomcat 5.0 -> Tomcat 5.5 이후 forward 된 jsp 에서 request.getRequestURI() 의 값이 이상해진 경우


1 Apache/Tomcat 연동시 jsp/servlet 은 Tomcat으로, 나머지는 Apache가 처리하게 설정하기 #


mod_jk로 연동하는 경우 아파치의 httpd.conf 에

JkMount /* worker1

와 같은 식으로 처리해 버리는 경우가 많은데, 이렇게 하면 jsp/servlet 같은것들 뿐만 아니라 이미지/css/js 같은 static resource 들도 죄다 tomcat 이 처리하게 된다.
기본적으로 static resource 들은 apache 가 처리하도록 하고 jsp/servlet 등의 처리만 Tomcat 이 맡아 처리하는 것이 좋다.
만약 jsp 파일 또는 Struts1/Struts2 만 사용한다면(그리고 확장자를 .do 와 .action 으로 쓴다면) 이런 경우 설정을

JkMount /*.do worker1
JkMount /*.action worker1
JkMount /*.jsp worker1

와 같은 식으로 하면 된다.

물론 worker1 은 jk.properties 에서 worker.list 에 지정한 그 이름으로 맞추어야 한다.


2 TOMCAT5에서 한글넘겨받을때 #


요즘은 웹사이트를 만들때 UTF-8로 인코딩을 하는 경우가 많은데, UTF-8 페이지에서 게시물을 한글로 입력한 다음 jsp 또는 servlet 에서 넘겨받아서 DB 에 저장하거나 할때 한글이 깨지는 경우가 있다. (당근 POST 로 넘기겠지...)

그럴때는 jsp 또는 serlvet 상단에

request.setCharacterEncoding("utf-8");

라고 해주면 깔끔하게 해결된다. (request 가 내장객체로 있으니 뭐.. Servlet 이라면 함수파라메터명에 맞춰서 적어주면 되겠다)
물론 Tomcat4 도 마찬가지.
당연한 이야기지만 setCharacterEncoding 은 request.getParameter 등의 함수를 호출하기 전에 사용되어야 한다.

3 TOMCAT5 에서 Altibase DB Pool 설정 #


며칠동안 삽퍼면서 이것저것 바꿔봤다.

<Resource name="jdbc/mmsgw" auth="Container" type="javax.sql.DataSource"
        driverClassName="Altibase.jdbc.driver.AltibaseDriver"
        url="jdbc:Altibase://xxx.xx.xxx.xxx/mydb"
        initialSize="20"
        maxActive="50"
        maxIdle="20"
        maxWait="5000"
        removeAbandoned="true"
        removeAbandonedTimeout="60"
        logAbandoned="true"
        username="user"
        password="passwd"/>

파라메터 뜻은 대충

  • initialSize : 초기 DB Pool 생성시 connection 갯수
  • maxActive : connection 최고 증가 갯수
  • maxIdle : 노는 애들을 제거하지 않고 몇개나 놔둘것인가
  • maxWait : DB Pool 에서 Connection 을 가져올때 얼마나 기다릴 것인가 ( 밀리초 단위 )
  • removeAbandoned : 커넥션 끊어진거 제거해버리고 새로 넣고 어쩌구등등으로 할것인지 말것인지
  • removeAbandonedTimeout : 얼마나 자주 removeAbandoned 를 실행시킬것인지 (초단위)
  • logAbandoned : 처리상황 로깅

이정도?


4 Tomcat5 Only 일때 디렉토리 내 심볼릭 링크가 안먹힐 때 #


apache 등의 웹서버와의 연동 없이 tomcat만으로 웹서버까지 함께 쓸때 symbolic link 를 걸면 정상적으로 URL이 걸리지 않는다.
(404 not found 에러남)

이럴때는 Context 설정할에

allowLinking = "true"

로 설정해주면 된다.

예)
<Context 
path=""
docBase="httpd/html"
reloadable="true"
allowLinking="true"
debug="0">
</Context>

5 Apache Tomcat Native library not found on the java.library.path #


tomcat 을 실행시킬때 저런 에러가 계속 뜨는게 싫고 해결하고 싶다면

APR(Apache Protable Runtime) 을 우선 설치한 다음 설치되어 있는 톰캣 디렉토리의 bin 에 있는

tomcat-native.tar.gz

의 압축을 풀고, 그 아래에서

configure
make
make install

을 차례대로 해주면 된다.

만약 configure 시에 apr 못찾겠다는 오류가 나오면

./configure --with-apr=/usr/local/apr ( 아마 apr을 그냥 설치했으면 저 경로에 설치되었을 것이다 )

로 해주면 넘어간다.

그리고 LD_LIBRARY_PATH 를 apr 설치경로의 lib 로 잡아주면 된다. ( /usr/local/apr/lib 쯤 될것임 )

6 80 포트로 띄우고 싶을때 #


apache 와 같은 웹서버에서는 실행은 root로 하더라도 내부 설정에 User 또는 Group 을 지정할 수가 있는데 반해 tomcat 을 80포트로 띄우기 위해서는 root 로만 가능하다.
80포트를 쓰면서 root가 아닌 계정으로 톰캣을 띄우기 위한 몇가지 방법을 찾아서 정리해본다.
뭐 소스코드를 바꾸느니 어쩌니 하는 말들이 있는데 아무래도 그건 방법이 아니라고 생각한다.

6.1 ipchains/iptables 이용하기 #


ipchains 가 설치되어 있다면 다음과 같이 수정하면 된다.

ipchains -A input -d xx.xx.xxx.xx 80 -p tcp -j REDIRECT 톰캣리스닝포트

iptables 라면

iptables -t nat -A OUTPUT -d localhost -p tcp --dport 80 -j REDIRECT --to-ports 8080
iptables -t nat -A OUTPUT -d your hostname -p tcp --dport 80 -j REDIRECT --to-ports 8080
iptables -t nat -A PREROUTING -d your hostname -p tcp --dport 80 -j REDIRECT --to-ports 8080

이런식으로 하면 된다.


6.2 JSVC 를 이용하기 #


쓰고 있는 톰캣이 버전 5.0 이라면 저런거 안하더라도 jsvc 라는 걸로 특정 유저의 권한으로 tomcat 이 동작하도록 할 수 있다.
(jsvc : http://tomcat.apache.org/tomcat-5.5-doc/setup.html )

tomcat5 의 bin 에 보면

jsvc.tar.gz 라는게 있는데 다음 순서로 실행하면 된다.

    cd $CATALINA_HOME/bin
    tar xvfz jsvc.tar.gz
    cd jsvc-src
    autoconf
    ./configure
    make
    cp jsvc ..
    cd ..

실행은

   cd $CATALINA_HOME
    ./bin/jsvc -Djava.endorsed.dirs=./common/endorsed -cp ./bin/bootstrap.jar \
        -outfile ./logs/catalina.out -errfile ./logs/catalina.err \
        org.apache.catalina.startup.Bootstrap

이런식으로 하면 된다.
압축을 푼 디렉토리의 native/ 아래에 보면

Tomcat5.sh

라는 쉘스크립트가 있는데 나중에 /etc/init.d 같은데 넣어서 쓸 수 있도록 만들어준 스크립트다.
이걸 $CATALINA_HOME/bin/ 에다가 복사해 넣고 다음과 같이 살짝 바꿔서 쓰면 편리하다.

JAVA_HOME=/usr/java/jdk1.5.0_12
CATALINA_HOME=/usr/local/tomcat/5.5.23
DAEMON_HOME=/usr/local/tomcat/5.5.23/bin
TOMCAT_USER=사용자ID

# for multi instances adapt those lines.
TMP_DIR=/var/tmp
PID_FILE=/var/tmp/jsvc.pid
CATALINA_BASE=$CATALINA_HOME

CATALINA_OPTS=""
CLASSPATH=\
$JAVA_HOME/lib/tools.jar:\
$CATALINA_HOME/bin/commons-daemon.jar:\
$CATALINA_HOME/bin/bootstrap.jar

case "$1" in
  start)
    #
    # Start Tomcat
    #
    $DAEMON_HOME/jsvc \
    -user $TOMCAT_USER \
    -home $JAVA_HOME \
    -Dcatalina.home=$CATALINA_HOME \
    -Dcatalina.base=$CATALINA_BASE \
    -Djava.io.tmpdir=$TMP_DIR \
    -wait 10 \
    -pidfile $PID_FILE \
    -outfile $CATALINA_HOME/logs/catalina.out \
    -errfile '&1' \
    $CATALINA_OPTS \
    -cp $CLASSPATH \
    org.apache.catalina.startup.Bootstrap
    #
    # To get a verbose JVM
    #-verbose \
    # To get a debug of jsvc.
    #-debug \
    exit $?
    ;;

  stop)
    #
    # Stop Tomcat
    #
    $DAEMON_HOME/jsvc \
    -stop \
    -pidfile $PID_FILE \
    org.apache.catalina.startup.Bootstrap
    exit $?
    ;;

  *)
    echo "Usage tomcat.sh start/stop"
    exit 1;;
esac

6.3 CoyoteConnector #


CoyoteConnector 같은걸 쓴다면 HttpConnector 설정을 다음과 같이 바꿔서 해결할 수도 있다고 한다.(확인해보지 못했음)

<Connector
  className="org.apache.catalina.connector.http.HttpConnector"
  port="8080"
  proxyPort="80"
>


7 @include 로 포함한 페이지의 한글이 깨질 때 #


웹에 뒤져보니까 encoding filter 를 추가하는 방법부터 별별 쑈를 다해놨더라.
기본적으로 A.jsp 에서 B.jsp 를 인클루드하는 경우
파일 둘 다 UTF-8로 작성되어 있다면

A.jsp 에서
<%@page contentType="text/html;charset=utf-8"%>
<%@page pageEncoding="utf-8" %>

로 되어 있고
<%
    response.setContentType("text/html;charset=utf-8");
%>

로 되어 있다면

B.jsp 의 최상단에

<%@page pageEncoding="utf-8"%>

한줄만 추가해주면 끝난다.


아.. 물론 매 페이지마다 저런 짓 하기 싫으면 인코딩 세팅하는 필터 하나 구현해서 등록해서 써도 된다.


8 common-fileupload 사용시 넘겨받은 한글 파라메터가 깨질 때 #


resin 이나 다른곳에서도 그런지는 모르겠지만, 아무튼 tomcat5에서 common-fileupload 를 이용해서 넘겨받은 파라메터 중 한글이 깨지는 경우

item.getString();

대신

item.getString(인코딩타입);

을 적어주면 된다.

setCharacterEncoding
pageEncodping
setContentType

을 죄다 UTF-8로 해줘도, multipart/form-data 로 넘어오는 데이터를 실제로 처리하는 common-fileupload 에서는 인코딩이 별도로 먹히는 것 같다.
그럴때는 슬그머니

item.getString("utf-8");

로 적어주면 문제해결됨.

9 Tomcat 5.x 에서 jsp 내의 스크립틀릿 사용금지하기 #

<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<scripting-invalid>true</scripting-invalid>
</jsp-property-group>
</jsp-config>

10 Tomcat 5.0 -> Tomcat 5.5 이후 forward 된 jsp 에서 request.getRequestURI() 의 값이 이상해진 경우 #


해결책부터 적는다면 5.5에서는 getRequestURI() 대신에

request.getAttribute("javax.servlet.forward.request_uri");

를 사용하면 된다.

좀 더 자세하게 언급하자면 톰캣 5.5.7 이전의 구현이 잘못된 것이었고 SRV 8.4 에 따라 수정되었다.


이유가 궁금하신 분들은 아래쪽을 참고하시길.

Tomcat 은 버전별로 구현된 servlet 및 jsp 의 spec 버전이 다른데, Tomcat 5.x 류는 Servlet 2.4와 JSP 2.0 의 규격을 따른다.
Tomcat 6은 각각 2.5/2.1, Tomcat 4 는 2.3/1.2의 규격을 다른다.

Tomcat 5.x 가 따르는 Servlet 2.4 규격의 2.3. ReuqestDispatcher Forward Attribute 를 살펴보면

SRV.8.4 “The path elements of the request object exposed to the target servlet must reflect the path 
used to obtain the RequestDispatcher.”

와 같은 설명이 되어 있는데, getRequestURI 가 path method 인지의 여부가 불분명했기 때문에 getRequestURI() 에 대한 구현이 여러가지로 만들어졌다.

하지만 SRV.4.4 의

SRV.4.4 “It is important to note that, except for URL encoding differences between the request URI and the 
path parts, the following equation is always true: requestURI = contextPath + servletPath + pathInfo”

로 인해서 이 부분은 해결이 되었다.
requestURI 가 저렇게 구성이 되기 때문에 RequestDispatcher 를 통해 forward 된 다음에는 HttpServletRequest.getRequestURI() 의 값이 바뀌어야만 했고, 이로 인해서 client 로부터 들어왔던 원래의 request uri 를 알수가 없게 되었다.

바로 이 부분에서 기존의 Tomcat 5.0 은 이 부분의 구현을 규격대로 하지 않았던 것 같고, Tomcat 5.5 로 넘어오면서 규격대로 제대로 구현이 되며서 문제가 발생한다. ( 프로그래머의 입장에서는 잘 되던 코드가 동작하지 않게 된 것이겠지만... )


이 문제를 해결하기 위해서 규격 2.4에서는 다음의 forward 속성들을 추가했다.

javax.servlet.forward.request_uri
javax.servlet.forward.context_path 
javax.servlet.forward.servlet_path 
javax.servlet.forward.path_info
javax.servlet.forward.query_string

이 값들은 request.getAttribute(); 를 이용해서 가져올 수가 있다.
만약 해당 서블릿이 다른 곳에서도 재사용되는 것이라면 client 에서 요청한 uri 와 실제의 서비스된 uri (포워딩된 uri) 를 알기 이해서 아래와 같이 더블 체크해야 한다.

public void doGet(HttpServletRequest request, HttpServletResponse response)
{
  String uriUsedByClient=(String)
    request.getAttribute("javax.servlet.forward.request_uri");
  if (uriUsedByClient==null)
    uriUsedByClient=request.getRequestURI();
  String uriToServe=(String)
    request.getAttribute("javax.servlet.include.request_uri");
  if (uriToServe==null)
    uroToServer=request.getRequestURI();
  // ...
}

아래의 톰캣 5.5 chage log 를 살펴보면 5.5.7 버전의 Catalina 쪽 수정사항에 해당 부분의 버그가 수정되었다고 기록되어 있습니다.
http://tomcat.apache.org/tomcat-5.5-doc/changelog.html

28222: request.getRequestURL() in forwarded jsp/servlet returns original url rather than new url as per SRV8.4 (markt)


getRequestURL() 과 getRequestURI() 두개 모두 실제tomcat 5.5.7 이후부터는 forward 된 jsp 의 값이 출력되며 request.getAttribute("javax.servlet.forward.request_uri"); 를 써야 한다.
javax.servlet.forward.request_url 같은 속성값은 없다.
Posted by 1010
02.Oracle/DataBase2009. 4. 28. 13:13
반응형

오라클에서의 데이터 암호화 기능

1. 설 명

☞ Bulletin no : 12036 참고

 
Oracle 8i Release2(8.1.6)에서는 데이터를 암호화하여 저장할 수 있는
향상된 기능(DES Encryption)을 제공 합니다
 
 
신용카드번호, 패스워드 등 보안이 필요한 데이터를 암호화된 형태로 저장하여
기존의 3rd Party Tool이나, Application Logic으로 구현하던 암호화 정책을
데이터베이스 차원에서 구현할 수 있도록 해줍니다.
 
일단 암호화의 결과의 길이는 8의 배수로 나옵니다.
자리수가 8이하는 8자리, 8초과 16이하는 16자리...
'123'을 암호화하면 그 자리수는 8자리가 나오지요

 
DBMS_OBFUSCATION_TOOLKIT

암호화 기능을 이용하려면 DBMS_OBFUSCATION_TOOLKIT을 이용해야 합니다.
 
 
이 패키지는 4개의 프로시져로 이루어져 있습니다.

- VARCHAR2 타입을 Encrypt/Decrypt할 수 있는 2개의 프로시져

- RAW 타입을 Encrypt/Decrypt할 수 있는 2개의 프로시져
(다른 타입은 지원하지 않으므로 number인 경우는 to_char 이용)
 
 

DBMS_OBFUSCATION_TOOLKIT을 이용하기 위해서는 :

1) SYS 유저로 아래의 스크립트를 실행 시킵니다.

   @$ORACLE_HOME/rdbms/admin/dbmsobtk.sql
   @$ORACLE_HOME/rdbms/admin/prvtobtk.plb
   
2) 권한을 부여 합니다.

   SQL>GRANT execute ON dbms_obfuscation_toolkit TO public;



2. 패키지 실행하기



--> 패키지 선언부 생성

CREATE OR REPLACE PACKAGE CryptIT AS
   FUNCTION encrypt( Str VARCHAR2,  
                     hash VARCHAR2 ) RETURN VARCHAR2;

   FUNCTION decrypt( xCrypt VARCHAR2,
                     hash VARCHAR2 ) RETURN VARCHAR2;
END CryptIT;
/
 
 
 
--> 패키지 본체 생성

CREATE OR REPLACE PACKAGE BODY CryptIT AS
   crypted_string VARCHAR2(2000);
 
   FUNCTION encrypt( Str VARCHAR2,  
                     hash VARCHAR2 ) RETURN VARCHAR2 AS
   pieces_of_eight INTEGER := ((FLOOR(LENGTH(Str)/8 + .9)) * 8);
 
   BEGIN
 
      dbms_obfuscation_toolkit.DESEncrypt(
               input_string     => RPAD( Str, pieces_of_eight ),
               key_string       => RPAD(hash,8,’#’),
               encrypted_string => crypted_string );
      RETURN crypted_string;
   END;
 
   FUNCTION decrypt( xCrypt VARCHAR2,
                     hash VARCHAR2 ) RETURN VARCHAR2 AS
   BEGIN
      dbms_obfuscation_toolkit.DESDecrypt(
               input_string     => xCrypt,
               key_string       => RPAD(hash,8,’#’),
               decrypted_string => crypted_string );
      RETURN trim(crypted_string);
   END;
END CryptIT;
/

 



3. 실행 예제


1) Encrypt하여 데이터 입력

-- 테스트 테이블을 생성 합니다.

SQL>create table encrypt_table( id number, passwd varchar(20) );


 
-- 테스트 데이트럴 입력 합니다.
-- CryptIT.encrypt(비밀번호, 키값)

SQL>INSERT INTO encrypt_table VALUES( 1, CryptIT.encrypt(’1234’, ’storm’));
1 개의 행이 만들어졌습니다.

 
SQL>INSERT INTO encrypt_table VALUES( 2, CryptIT.encrypt(’5678’, ’oramaster’));
1 개의 행이 만들어졌습니다.

 
 
2) Decrypt하여 데이터 조회
 
--> Decrypt하지 않으면 암호화된 데이터와 비교되서 결과값이 출력되지 않습니다.
SQL> select id, passwd from encrypt_table where passwd = ’1234’;
 
선택된 레코드가 없습니다.
 
 
--> 저장장치에 Encrypt된 값으로 저장 됩니다.

SQL> col passwd format a60
SQL> select id, dump(passwd) passwd from encrypt_table;

         ID PASSWD
---------- -------------------------------------------------------------
         1 Typ=1 Len=8: 246,27,80,184,227,225,245,31
         2 Typ=1 Len=8: 175,231,213,125,85,223,46,133
 


--> Encrypt할 때 사용한 Key로만 Decrypt할 수 있습니다.
 
SQL>SELECT id, CryptIT.decrypt(passwd,’storm’) passwd
       FROM encrypt_table
       WHERE CryptIT.decrypt(passwd,’storm’) = ’1234’;
 
        ID PASSWD
---------- -----------
         1 1234
 
 
SQL>SELECT id, CryptIT.decrypt(passwd,’oramaster’) passwd
    FROM encrypt_table
    WHERE CryptIT.decrypt(passwd,’oramaster’) = ’5678’;
 
        ID PASSWD
---------- -----------
         2 5678
 
 
주의) Table에 접근 권한이 있는 다른 유저도 Key값을 알면 Decrypt할 수 있습니다.
 
 
 
4) 관련 ORA error number
 
ORA error 28231 "Invalid input to Obfuscation toolkit"
- input data, key값이 NULL일 경우 발생
 
ORA error 28232 "Invalid input size for Obfuscation toolkit"
- input data가 8 bytes 배수가 아닐 경우 발생
 
ORA error 28233 "Double encryption not supported by DESEncrypt in Obfuscation toolkit"
- encrypt data를 다시 encrypt경우 발생
 

관 련 자 료
===========
Oracle8i Supplied PL/SQL Packages Reference Release 2 (8.1.6)

 
암호화 하는 프로시저 소스를 암호화하여 보안하자~
암호화 Stored Function을 하나 만들어 wrap사용하시면 됩니다.

복호화는 만들지 안으시는게 좋습니다. (정히 필요하시면 만드시고)
복호화 함수가 있으면 개발자에 의하여 노출될 우려가 있습니다.

암호화 Stored Function => sf_encrypt
wrap iname=/mydir/sf_encrypt.sql oname=/mydir/sf_encrypt.plb
/mydir/sf_encrypt.plb 파일를 이용하여 DB에 rap된 sf_encrypt를 만들고


입력
INSERT INTO encrypt_table VALUES( 1, sf_encrypt('1234'));

SELECT id, '*****' as passwd
FROM encrypt_table
WHERE passwd = sf_encrypt('1234');

이런식으로 사용하시면 인덱스 사용 가능합니다.
 
 
 
========================================================================================================================
========================================================================================================================
 
 
 
일부 서비스에 대해 오라클 DB 버전을 9i에서 10g로 변경하면서, 암호화 패키지 CRYPTIT에서 일부 문제가 있었다.

9i에서 암호화된 테이블을 10g로 가져와서 다음과 같이 decrypt 를 쿼리하면 에러가 발생했다.

select CRYPTIT.decrypt(passwd, 'keyvalue) from user_tbl

ORA-28232: obfuscation 툴킷에 부적합한 입력 길이입니다.
ORA-06512: "SYS.DBMS_OBFUSCATION_TOOLKIT_FFI", 줄 40에서
ORA-06512: "SYS.DBMS_OBFUSCATION_TOOLKIT", 줄 153에서
ORA-06512: "WLOWN.CRYPTIT", 줄 20에서


처음에는 일부 데이터는 정상으로 조회되길래 무슨 문제인가 찾아봐도 뽀죡한 답변을 못찾았다.

좀 삽질하다가 9i와 10g의 sys user의 DBMS_OBFUSCATION_TOOLKIT의 함수내용이 틀린 것을 보고,
암호화 데이터를 다시 갱신해 줫다.

update user_tbl set passwd = CRYPTIT.encrypt(raw_pass, 'keyvalue')

그런후 다시 조회하니 잘 된다. 역시 오라클 버전이 틀려짐에 따라 암호화 내용이 달라진 거 같다.



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

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




CREATE OR REPLACE PACKAGE CRYPTIT
AS
   FUNCTION ENCRYPT (STR VARCHAR2)
      RETURN VARCHAR2;

   FUNCTION DECRYPT (XCRYPT VARCHAR2)
      RETURN VARCHAR2;
END CRYPTIT;
/



CREATE OR REPLACE PACKAGE BODY CRYPTIT
AS
   CRYPTED_STRING   VARCHAR2 (2000);
   HASH_KEY   VARCHAR2 (10) := '암호화키';

   FUNCTION ENCRYPT (STR VARCHAR2)
      RETURN VARCHAR2
   AS
      PIECES_OF_EIGHT   INTEGER := ((FLOOR (LENGTH (STR) / 8 + .9)) * 8);
   BEGIN
      DBMS_OBFUSCATION_TOOLKIT.DESENCRYPT (INPUT_STRING          => RPAD (STR, PIECES_OF_EIGHT)
                                         , KEY_STRING            => RPAD (HASH_KEY, 8, '#')
                                         , ENCRYPTED_STRING      => CRYPTED_STRING
                                          );
      RETURN CRYPTED_STRING;
   END;

   FUNCTION DECRYPT (XCRYPT VARCHAR2)
      RETURN VARCHAR2
   AS
   BEGIN
      DBMS_OBFUSCATION_TOOLKIT.DESDECRYPT (INPUT_STRING          => XCRYPT
                                         , KEY_STRING            => RPAD (HASH_KEY, 8, '#')
                                         , DECRYPTED_STRING      => CRYPTED_STRING
                                          );
      RETURN TRIM (CRYPTED_STRING);
   END;
END CRYPTIT;
/

Posted by 1010
02.Oracle/DataBase2009. 4. 28. 13:08
반응형
기술: 보안

투명한 데이터 암호화
Arup Nanda

단 한 줄의 코드도 작성하지 않고 기밀 데이터를 투명하게 암호화합니다.

누군가가 데이터베이스 백업 테이프를 훔쳐가는 것은 조직에게는 최악의 악몽입니다. 물론 보안 시스템을 구축하고 매우 중요한 기밀 자산은 암호화하고 데이터베이스 서버에 방화벽을 설치했습니다. 그러나 침입자가 쉽게 접근했습니다. 침입자는 백업 테이프를 가져가서 틀림 없이 다른 서버에서 데이터베이스를 복원하여 데이터베이스를 시작하고 느긋하게 데이터를 보고 있습니다. 그러한 침입자로부터 데이터베이스 데이터를 보호하는 것은 훌륭한 업무방식이 아닙니다. 그것은 대부분의 법률, 규정, 지침을 준수하기 위한 요구 사항입니다. 이러한 취약성으로부터 데이터베이스를 어떻게 보호할 수 있겠습니까?

한 해결책은 데이터베이스의 기밀 데이터를 암호화하고 별도의 위치에 암호화 키를 저장하는 것입니다. 암호화 키 없이 훔쳐간 데이터는 쓸모가 없습니다. 그러나 두 상반된 개념 간의 균형을 유지해야 합니다. 그 개념이란 애플리케이션이 암호화 키에 액세스할 수 있는 편리성과 키 절도를 방지하는데 필요한 보안성입니다. 그리고 회사와 연방 정부 규정을 준수하기 위해 복잡한 코딩 없이 즉시 솔루션이 필요합니다.

Oracle Database 10g Release 2의 새로운 기능을 사용하여 그렇게 할 수 있습니다. 단 한 줄의 코드도 작성하지 않고 열을 암호화됨으로 선언할 수 있습니다. 사용자가 데이터를 삽입할 때 데이터베이스가 투명하게 데이터를 암호화하여 그 열에 저장합니다. 마찬가지로 사용자가 열을 선택할 때 데이터베이스가 데이터를 자동으로 해독합니다. 이러한 모든 프로세스가 투명하게 애플리케이션의 코드를 변경하지 않고 수행되기 때문에 이 기능에는 이에 잘 어울리는 TDE(투명한 데이터 암호화)라는 이름이 붙여졌습니다.

작동 방식

전에 Oracle Magazine 1/2월호 "데이터 자산의 암호화" 에서 암호화 기초에 대해 다루었습니다. 주요 사항을 다시 설명하면, 암호화는 암호화 알고리즘과 암호화 키를 일반 텍스트 입력 데이터에 적용해야 합니다. 그리고 암호화된 값을 성공적으로 해독하려면 동일한 알고리즘과 키 값을 알아야 합니다.

그 기사에서 저는 오라클이 제공하는 암호화 툴을 사용하여 암호화 기반 구조를 구축하는 방법을 설명하였습니다. 그러나 Oracle Database 10g Release 2와 TDE를 사용할 경우 암호화 기반 구조를 구축할 필요가 없습니다. 단지 암호화할 열을 정의하기만 하면Oracle Database 10g에서 그 열이 포함된 테이블에 대한 암호화 방식의 안전한 암호화 키를 생성하고 지정한 암호화 알고리즘을 사용하여 그 열의 일반 텍스트 데이터를 암호화합니다. 이 테이블 키를 보호하는 것이 매우 중요합니다. Oracle Database 10g는 지갑이라고 부르는 안전한 위치에 저장되는 마스터 키를 사용하여 데이터를 암호화합니다. 이 마스터 키는 데이터베이스 서버의 파일일 수 있습니다. 암호화된 테이블 키는 데이터 딕셔너리에 저장됩니다. 사용자가 암호화됨으로 정의된 열에 데이터를 입력할 경우 그림 1에서와 같이 Oracle Database 10g가 지갑에서 마스터 키를 가져와 데이터 딕셔너리의 암호화 키를 해독하고 입력 값에 대해 그 암호화 키를 사용하고 데이터베이스에 암호화된 데이터를 저장합니다.

figure 1
그림 1: TDE(투명한 데이터 암호화) 작동 방식

테이블의 모든 열을 암호화할 수 있습니다. 그림 1과 같이 테이블에 열이 4개 있고 열 2와 3을 암호화할 경우 Oracle Database 10g는 테이블에 대하여 하나의 암호화된 테이블 키를 생성하고 그 키를 사용하여 두 열을 암호화합니다. 디스크에 열 1과 열 4의 값을 일반 텍스트로 저장하고 다른 두 열의 값은 암호화된 형식으로 저장됩니다. 데이터가 암호화되어 저장되기 때문에 백업과 아카이브된 로그 등 모든 다운스트림 구성 요소는 암호화된 형식을 갖습니다.

사용자가 암호화된 열을 선택할 경우 Oracle Database 10g는 투명하게 데이터 딕셔너리에서 암호화된 테이블 키를 검색하고 지갑에서 마스터 키를 인출한 다음 테이블 키를 해독합니다. 그런 다음 데이터베이스가 디스크의 암호화된 데이터를 해독하여 일반 텍스트로 사용자에게 반환합니다.

이 암호화된 데이터를 사용할 경우 디스크의 데이터가 도난되어도 훔친 데이터의 일부가 아니라 지갑에 저장되어 있는 마스터 키 없이 데이터를 검색할 수 없습니다. 지갑을 도난 당할 경우에도 지갑 암호 없이 마스터 키를 검색할 수 없습니다. 그러므로 그 침입자는 디스크를 훔치거나 데이터 파일을 복사하더라도 데이터를 해독할 수 없습니다. 이것은 여러 규정과 행정 명령(directive)에 대한 준수 요구 사항을 충족합니다. 그리고 이러한 모든 프로세스가 애플리케이션의 변경이나 복잡한 암호화 작성, 키 관리 시스템 없이 수행됩니다. 그러면 지금부터 TDE를 설정하고 사용하는 방법에 대해 설명하겠습니다.

단 한번의 설정

처음 TDE를 사용할 경우 지갑 위치를 지정하고 지갑 암호를 설정하고 지갑을 열어야 합니다.

1. 지갑 위치를 지정합니다.
처음 TDE를 설정할 경우 마스터 키를 저장할 지갑을 생성해야 합니다. 기본적으로 지갑은 디렉토리 $ORACLE_BASE/admin/$ORACLE_SID/wallet에서 생성됩니다. 그러므로 $ORACLE_BASE is /u01/app/oracle 및 $ORACLE_SID가 SWBT4일 경우 지갑은 디렉토리 /u01/app/oracle/admin/SWBT4/wallet에 저장됩니다. $ORACLE_HOME/network/admin 디렉토리에 저장되는 sqlnet.ora 파일에서 디렉토리를 지정하여 다른 디렉토리를 선택할 수 있습니다. 예를 들어 지갑을 /orawall 디렉토리에 저장하려면 sqlnet.ora 파일에 다음 행을 추가합니다.

ENCRYPTION_WALLET_LOCATION =
 (SOURCE=
   (METHOD=file)
     (METHOD_DATA=
       (DIRECTORY=/orawall)))

이 예제에서는 기본 위치가 선택된 것으로 가정합니다. 또한 지갑 위치를 정기적 백업에 포함시켜야 합니다.

2. 지갑을 생성합니다.
이제 지갑을 생성하고 지갑에 액세스하기 위한 암호를 설정합니다. 이 작업을 하려면 ALTER SYSTEM 권한을 가진 사용자로서 다음 명령을 실행합니다.

alter system set encryption key 
authenticated by "remnant";

이 명령은 다음을 수행합니다.

  • 단계 1에서 지정한 위치에 지갑을 생성합니다.
  • 지갑의 암호를 "remnant"로 설정합니다.
  • 마스터 키를 저장하고 검색할 TDE에 대한 지갑을 엽니다.

암호는 대소문자를 구분하며 큰 따옴표로 묶어야 합니다. 암호 "remnant"는 어느 동적 성능 뷰나 로그에도 일반 텍스트로 표시되지 않습니다.

지갑 열기

지갑은 한 번만 생성되기 때문에 앞의 두 단계는 한 번만 수행해야 합니다. 그러나 데이터베이스 인스턴스가 시작된 후에 지갑을 명시적으로 열어야 합니다. 지갑을 생성할 때(앞의 단계 2) 사용할 지갑을 열어야 합니다. 지갑을 생성하고 암호를 설정한 후 데이터베이스를 열 때마다 동일한 암호를 사용하여 다음과 같이 지갑을 열어야 합니다.

alter system set encryption wallet open authenticated by "remnant";

지갑을 닫으려면 다음 명령을 실행합니다.

alter system set encryption wallet close;

TDE를 사용하려면 지갑을 열어야 합니다. 지갑이 닫혀 있는 경우 암호화되지 않은 열에 액세스할 수 있으나 암호화된 열에는 액세스할 수 없습니다.

열 암호화

TDE를 사용하여 열을 암호화하려면 단지 열 정의에 간단한 절, ENCRYPT를 추가하기만 하면 됩니다. 그러나 추가하기 전에 사용할 암호화 유형과 키 길이를 선택해야 합니다. 이 문제에 대한 자세한 설명은 위에서 언급한 "데이터 자산의 암호화" 기사를 참조하십시오.

정규 스키마에서 다음의 계정 소유자의 테이블이 있다고 가정합시다.

ACC_NO      NUMBER
ACC_NAME    VARCHAR2(30) 
SSN         VARCHAR2(9)

현재 테이블의 모든 데이터는 일반 텍스트입니다. 주민등록번호가 저장된 열 SSN을 변환하여 암호화됨으로 저장하려고 합니다. 다음 명령을 실행할 수 있습니다.

alter table accounts modify (ssn encrypt);

이 명령문은 다음 두 작업을 수행합니다.

  • 테이블에 대한 암호화 키를 생성합니다. 암호화된 형식을 사용하여 동일한 테이블의 다른 열을 변경할 경우 동일한 테이블 키가 사용됩니다.
  • 열의 모든 값을 암호화된 형식으로 변환합니다.

이 명령은 열의 데이터 유형이나 크기를 변경하지 않으며 트리거 또는 뷰를 생성하지 않습니다.

기본적으로 192비트 키를 사용하는 알고리즘 AES가 암호화에 사용됩니다. 명령에 해당 절을 추가적으로 지정하여 다른 알고리즘을 선택할 수 있습니다. 예를 들어 128비트 AES 암호화를 사용하려면 다음과 같은 명령을 사용합니다.

alter table accounts modify (ssn encrypt using 'AES128');

AES128, AES192, AES256 또는 3DES168(168비트 Triple DES 알고리즘)을 절로서 사용할 수 있습니다. 이 값은 설명할 필요 없이 자명합니다. 예를 들어AES256은 256비트 키를 사용하는 AES(Advanced Encryption Standard) 알고리즘을 의미합니다.

열을 암호화한 후 테이블을 설명할 때 다음이 나타납니다.

SQL> desc accounts

Name	       Null?	      Type
------------   ------------   --------------------------------------------------
ACC_NO		              NUMBER
ACC_NAME		      VARCHAR2(30)
SSN		              VARCHAR2(9) ENCRYPT

데이터 유형 다음의 ENCRYPT 키워드에 주의하십시오. 데이터베이스에서 암호화된 열을 찾으려면 데이터 딕셔너리 뷰 DBA_ENCRYPTED_COLUMNS를 검색할 수 있습니다. (SYS 소유 테이블에 대해 TDE를 사용할 수 없습니다.)

성능 고려 사항

암호화와 해독은 CPU 주기를 소비하기 때문에 성능에 대한 영향을 고려해야 합니다. 테이블의 암호화되지 않은 열에 액세스할 때 성능은 TDE가 없는 테이블과 다르지 않습니다. 그러나 암호화된 열에 액세스할 때 selects 시 해독하는 동안과 inserts 시 암호화하는 동안 작은 성능 오버헤드가 발생하며 따라서 열을 선택적으로 암호화해야 합니다. 열을 암호화할 필요가 없을 경우 다음을 사용하여 TDE를 해제할 수 있습니다.

alter table account modify (ssn decrypt);

또한 인덱스 사용을 고려하십시오. 위 예에서 열 SSNin_accounts_ssn이라는 이름의 인덱스가 있다고 가정합시다. ACCOUNTS 테이블에 대한 질의에 다음과 같이 동등 술어가 있을 경우

select * from accounts 
where ssn = '123456789';

인덱스 in_accounts_ssn이 사용됩니다. 질의에서 다음과 같이 LIKE 술어를 사용할 경우

select * from accounts 
where ssn like '123%';

인덱스가 무시되고 전체 테이블 스캔이 사용됩니다. 그 이유는 간단합니다. 인덱스의 B 트리 구조에서 앞 몇 문자가 동일한 값("fraternal", "fraternity" 등)은 물리적으로 서로 가까이 있습니다. LIKE 술어를 처리할 때 Oracle Database 10g는 패턴 일치를 사용하여 인덱스 항목을 검색하고 물리적 근접성이 인덱스 검색 속도를 증가시키므로 전체 테이블 스캔보다 좋습니다.

그러나 열을 암호화할 경우 인덱스의 실제 값이 암호화되었기 때문에 크게 다르고 따라서 인덱스 전역에 흩어져 있습니다. 그렇기 때문에 전체 테이블 스캔보다 인덱스 스캔이 더 많은 리소스를 사용합니다. 그래서 이 LIKE 술어 질의 예제에서 Oracle Database 10g는 인덱스를 무시하고 전체 테이블 스캔을 선택합니다. 동등 술어의 경우 패턴을 따르는 여러 값 대신에 특정 인덱스 항목이 검색됩니다. 그래서 인덱스를 사용한 실행 경로가 전체 테이블 스캔보다 속도가 빠르며 최적기가 인덱스 사용을 선택합니다. 암호화할 열을 결정할 때 암호화가 인덱스에 얼마나 영향을 주는지 고려하고 암호화 열을 포함하는 특정 질의를 재작성해야 하는 경우가 있으니 주의하십시오.

키와 암호 관리

누군가가 테이블 키를 알고 있거나 암호화된 테이블 키를 해독했을지도 모른다는 의심이 들 때에는 어떻게 할까요? 간단하게 테이블에 대한 새 키를 생성할 수 있고 간단한 명령을 실행하여 새로운 테이블 키를 사용하여 암호화된 열 값을 다시 생성할 수 있습니다. 또한 이 작업을 할 때 AES256 등 다른 암호화 알고리즘을 선택할 수도 있습니다. 다음 명령을 수행하여 두 작업을 수행할 수 있습니다.

alter table accounts rekey using 'aes256';

누군가가 지갑 암호를 알고 있을 경우 어떻게 할까요? Oracle Wallet Manager를 사용하여 암호를 변경할 수 있습니다. 이 그래픽 툴을 호출하려면 명령줄에 OWM을 입력합니다(그림 2 참조). 상단 메뉴에서 지갑 -> 열기를 선택하고 지정한 지갑 위치를 선택한 다음 지갑 암호를 입력합니다. 그런 다음 지갑 -> 암호 변경을 선택하여 암호를 변경합니다. 지갑 암호를 변경하여도 키는 변경되지 않습니다.

figure 2
그림 2: Oracle Wallet Manager

암호화와 함께 "Salt"를 원하십니까?

암호화는 완전히 데이터를 숨기지만 때때로 데이터의 원본 일반 텍스트 값에 반복되는 값이 있을 경우 암호화된 데이터 값을 쉽게 추측할 수 있습니다. 예를 들어 급여 정보 테이블에 반복되는 값이 포함될 수 있습니다. 이 경우에 암호화된 값도 동일하며 침입자가 동일한 급여 항목을 판별할 수 있습니다. 그러한 경우를 방지하기 위해 입력 데이터가 같더라도 암호화된 값이 다르도록 데이터에 "salt"를 추가합니다. 기본적으로 TDE는 salt를 적용합니다. 암호화된 열에 대한 인덱스를 생성할 경우 인덱스에 salt를 포함시킬 수 없습니다. 예를 들어 SSN 열에서 salt를 제거하려면 다음을 실행합니다.

alter table accounts modify 
(ssn encrypt no salt);

salt를 사용하여 암호화된 열에 대한 인덱스를 생성할 경우 다음 예에서와 같이 오류가 발생합니다.

SQL> create index in_acc_01 
on accounts (ssn);

ORA-28338: cannot encrypt indexed column(s) with salt

salt를 사용하여 인덱스된 열을 암호화할 경우에도 동일한 오류가 발생합니다. 마찬가지로 암시적 인덱스 생성이 있을 경우, 즉 열이 기본 키의 일부이거나 unique로 정의된 경우 salt를 사용할 수 없습니다. 또한 열이 외래 키의 일부인 경우에도 salt를 사용할 수 없습니다.

TDE와 Data Pump 사용

기본적으로 Data Pump 엑스포트 유틸리티(EXPDP)를 사용하여 암호화된 열이 있는 테이블의 데이터를 엑스포트할 경우 생성되는 덤프 파일의 데이터는 일반 텍스트입니다(암호화된 열 데이터의 경우에도 마찬가지). 다음 명령은 ACCOUNTS 테이블(암호화된 열이 있음)을 엑스포트하고 경고를 반환합니다.

다음 단계

암호화에 대한
추가 정보를 참조하십시오.
oracle.com/technology/oramag/oracle/05-jan/o15security.html
www.dbazine.com/olc/olc-articles/nanda11

투명한 데이터 암호화에 대한 추가 정보를 참조하십시오.
Oracle Database Advanced Security Administrator's Guide

$ expdp arup/arup tables=accounts

ORA-39173: Encrypted data has been stored unencrypted in dump file set.

이 메시지는 단지 경고이고 오류가 아니며 행이 엑스포트됩니다..

Data Pump 덤프 파일의 암호화된 열 데이터를 보호하려면 테이블을 엑스포트할 때 덤프 파일을 암호로 보호할 수 있습니다. EXPDP 명령에서 ENCRYPTION_PASSWORD 매개변수로 지정되는 이 암호는 이 엑스포트 프로세스에만 적용됩니다. 이것은 지갑 암호가 아닙니다. 목록 1은 암호 "pooh"를 사용하여 실행한 EXPDP 명령입니다. 목록 1의 명령에 대한 출력이 어떤 방식으로 암호 "pooh"를 표시하지 않는지 주목하십시오. 암호는 별표(*) 문자열로 숨겨집니다. 생성되는 덤프 파일에는 TDE로 암호화된 열의 일반 텍스트 데이터가 보이지 않습니다.

코드 목록 1: 암호로 보호된 덤프 파일의 엑스포트


$ expdp arup/arup ENCRYPTION_PASSWORD=pooh tables=accounts
 
Export: Release 10.2.0.0.0 - Beta on Friday, 01 July, 2005 16:14:06
 
Copyright (c) 2003, 2005, Oracle.  All rights reserved.
 
Connected to: Oracle Database 10g Enterprise Edition Release 10.2.0.0.0 - Beta
With the Partitioning, OLAP and Data Mining options
Starting "ARUP"."SYS_EXPORT_TABLE_01":  arup/******** ENCRYPTION_PASSWORD=********* tables=accounts 
Estimate in progress using BLOCKS method...
Processing ...

이 암호화된 덤프 파일을 임포트할 때 목록 2에서와 같이 엑스포트 시 사용한 동일한 암호를 지정해야 합니다.

코드 목록 2: 암호로 보호된 덤프 파일의 임포트


$ impdp arup/arup ENCRYPTION_PASSWORD=pooh tables=accounts table_exists_action=replace
 
Import: Release 10.2.0.0.0 - Beta on Friday, 01 July, 2005 16:04:20
 
Copyright (c) 2003, 2005, Oracle.  All rights reserved.
 
Connected to: Oracle Database 10g Enterprise Edition Release 10.2.0.0.0 - Beta
With the Partitioning, OLAP and Data Mining options
Master table "ARUP"."SYS_IMPORT_TABLE_01" successfully loaded/unloaded
Starting "ARUP"."SYS_IMPORT_TABLE_01":  arup/******** ENCRYPTION_PASSWORD=********* table_exists_action=replace 
Processing ...

다음은 임포트 시 ENCRYPTION_PASSWORD 매개변수를 지정하지 않은 경우의 결과입니다.

$ impdp arup/arup tables=accounts

ORA-39174: Encryption password must 
be supplied.

다음은 틀린 암호를 지정한 경우의 결과입니다.

$ impdp arup/arup ENCRYPTION_PASSWORD
=piglet tables=accounts

ORA-39176: Encryption password is 
incorrect.

원본 엑스포트 유틸리티(EXP)는 암호화된 열이 있는 테이블을 엑스포트할 수 없습니다..

결론

공격으로부터 데이터를 보호하고 기업에 적용되는 많은 법률을 준수하는 일은 간단한 작업이 아닙니다. TDE를 사용하면 코딩이나 키 관리 복잡성 없이 데이터 암호화와 준수를 모두 수행할 수 있으므로 더 전략적 활동에 주력할 수 있습니다.


Arup Nanda (arup@proligence.com) 는 뉴욕주 White Plains에 소재한 Starwood Hotels and Resorts의 선임 DBA이며, Rampant Press에서 출판된 Oracle Privacy Security Auditing(2003)의 공동 저자입니다.

Posted by 1010
02.Oracle/DataBase2009. 4. 27. 14:26
반응형
출처 : http://yamoe.tistory.com/98

0. sqlplus
(sqlplus 실행)
    SQL*Plus: Release 10.2.0.1.0 - Production on Thu Feb 12 14:11:00 2009
   
    Copyright (c) 1982, 2005, Oracle.  All rights reserved.
   
    Connected to:
    Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
    With the Partitioning, OLAP and Data Mining options

1. > select * from PRODUCT_COMPONENT_VERSION
    PRODUCT                                                           VERSION        STATUS
    ----------------------------------------------------------------------------------------------
    NLSRTL                                                            10.2.0.1.0     Production
    Oracle9i Enterprise EditionOracle Database 10g Enterprise Edition 10.2.0.1.0     Production
    PL/SQL                                                            10.2.0.1.0     Production
    TNS for Linux:                                                    10.2.0.1.0     Production

2. > select * from V$VERSION
    BANNER
    ----------------------------------------------------------------
    Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod
    PL/SQL Release 10.2.0.1.0 - Production
    CORE    10.2.0.1.0      Production
    TNS for Linux: Version 10.2.0.1.0 - Production
    NLSRTL Version 10.2.0.1.0 - Production

3. ojdbc14.jar 버전 확인(Oracle JDBC Driver) : jar 압축 풀면 META-INF/MANIFEST.MF에 버전 정보 들어있음.
Posted by 1010
02.Oracle/DataBase2009. 4. 27. 14:23
반응형
select name, value from v$parameter where name like '%service%' 
Posted by 1010
02.Oracle/DataBase2009. 4. 27. 13:38
반응형

select * from v$sqltext where rownum < 100;

select * from v$sqlarea where rownum < 100;

select * from v$sql order by first_load_time desc;

Posted by 1010
반응형
function number_format(str){
        str = ""+str+"";
        var retValue = "";
        for(i=0; i<str.length; i++){
            if(i > 0 && (i%3)==0) {
                retValue = str.charAt(str.length - i -1) + "," + retValue;
            } else {
                retValue = str.charAt(str.length - i -1) + retValue;
            }
        }
        return retValue;
}
Posted by 1010
반응형

//var    _intValue   = '0123456789';
//var    _upperValue = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
//var    _lowerValue = 'abcdefghijklmnopqrstuvwxyz';
//var    _etcValue   = '~`!@#$%%^&*()-_=+\|[{]};:\'\",<.>/?';
//var    dayOfMonth = new Array(31,28,31,30,31,30,31,31,30,31,30,31);

n = (document.layers) ? 1:0;
ie = (document.all) ? 1:0;
if (n) document.captureEvents(Event.KEYPRESS);

function fnTest(pval) {
  fnTest =  doReverse(pval.value) ;
  alert (fnTest);
}

// -------------------------------------------------------------------
// Function name: isInt
// Description  : 숫자인가를 체크하는 함수
// Parameter    : value(체크대상 문자)
// -------------------------------------------------------------------
// Usage        : var a='3'; if(isInt(a)) { alert("숫자입니다"); }
// -------------------------------------------------------------------
function isInt(value) {
 var _intValue   = '0123456789';
    var j;
    for(j=0;j<_intValue.length;j++)
      if(value == _intValue.charAt(j)) {
    return true;
 }
    return false;
}

// --------------------------------------------------------------------
// Function name: isNumeric
// Description  : 숫자로만 구성되어 있는지를 검사
// Parameter    : obj(화면컨트롤)
// --------------------------------------------------------------------
// Usage        : <input type="text" onBlur="isNumeric(this)">
// Caution      : 화면컨트롤에 데이타가 없는 경우에는 false를 리턴
// --------------------------------------------------------------------
function isNumeric(obj) {
 var str=obj.value;
 if (str.length == 0)
 return false;
 for (var i = 0; i < str.length; i++){
 var ch = str.substring(i, i + 1);
 if ((ch < "0" || "9" < ch) ){
     obj.focus();
     obj.select();  
   return false;
  }
 }
 return true;
}

// ---------------------------------------------------------------------
// Function name : isNumber(str)
// Description   : 숫자로만 구성되어 있는지를 검사, 숫자이면 return true
// Parameter     : str(측정대상값)
// ----------------------------------------------------------------------
// Usage         : if(isNumber(str)) { alert("숫자입니다.."); },
// Caution       : isNumeric(obj)과 다른점은 obj가 아닌 value값 사용
// ----------------------------------------------------------------------
function isNumber(value) {
 var result = true;
 for(j = 0; result && (j < value.length); j++) {
  if((value.substring(j, j+1) < "0") || (value.substring(j, j+1) > "9")) {
   result = false;
  }
 }
 return result;
}

// ---------------------------------------------------------------------
// Function name: getNumOnly
// Description  : 숫자와 문자열이 혼합되어 있는 것에서 숫자만 리턴
// Parameter    : obj(검사대상 문자열)
// ---------------------------------------------------------------------
// Usage        : <input type="text" onBlur="getNumOnly(this);">
//                검사대상 문자열이 '1134sd3dkk8'일 경우 '113438'만 리턴
// ---------------------------------------------------------------------
function getNumOnly(obj) {
 var strNew = "";
  var chkstr = "0123456789";
 var str = obj.value;
 for (var i = 0; i < str.length; i++) {
   if (chkstr.indexOf(str.substring(i, i + 1)) >= 0) {
     strNew += str.substring(i, i + 1);
   }
 }
  return strNew;
}

// ---------------------------------------------------------------
// Function name : isFloat(str)
// Description   : 숫자값인지 체크, '.' 포함
// Parameter     : str(측정대상값)
// ---------------------------------------------------------------
// Usage         : if(isFloat(str)) { alert("Float값입니다."); }
// ---------------------------------------------------------------
function isFloat(value) {
 var count = 0;
 var ch;
 
 for(i=0; i<value.length; i++) {
  ch = value.charAt(i);
 
  if(isNaN(ch)) {
   if(ch == ".") {
    count++;
   } else {
    return false;
   }
  }    
 }
 
 if(count > 1) {
  return false;
 } else {
  return true;
 }
 
 return result;
}

// -------------------------------------------------------------------
// Function name : getOnlyNumberKey()
// Description   : 키보드 입력시 숫자만 입력 가능
// Parameter     :
// -------------------------------------------------------------------
// Usage         : onKeyDown=getOnlyNumberKey()
// -------------------------------------------------------------------
function getOnlyNumberKey() {
 if ((event.keyCode >=48 && event.keyCode <=57)   // 자판 0~9
  || (event.keyCode >=96 && event.keyCode <= 105)  // keypad 0~9
  || (event.keyCode == 109)             // 자판 -
  || (event.keyCode == 189)             // keypad -
  || (event.keyCode == 8)              // back space
  || (event.keyCode == 9)              // tab
  || (event.keyCode == 13)             // enter
  || (event.keyCode == 46)             // delete
  || (event.keyCode >= 37 && event.keyCode <= 40)) // 방향키
 {
  return true;
 } else {
  event.returnValue = false;
 }
}

// ---------------------------------------------------------------------
// Function name : getNumberNCommaKey() 
// Description   : 키보드 입력시 숫자 및 ','가 입력 가능
// Parameter     :
// ---------------------------------------------------------------------
// Usage         : onKeyDown=getNumberNCommaKey() 
// ---------------------------------------------------------------------
function getNumberNCommaKey() {
 if ((event.keyCode >=48 && event.keyCode <=57)   // 자판 0~9
  || (event.keyCode >=96 && event.keyCode <= 105)  // keypad 0~9
  || (event.keyCode == 109)             // 자판 -
  || (event.keyCode == 189)             // keypad -
  || (event.keyCode == 188)             // 자판 ,
  || (event.keyCode == 8)              // back space
  || (event.keyCode == 9)              // tab
  || (event.keyCode == 13)             // enter
  || (event.keyCode == 46)             // delete
  || (event.keyCode >= 37 && event.keyCode <= 40)) // 방향키
 {
  return true;
 } else {
  event.returnValue = false;
 }

}

// ----------------------------------------------------------------------
// Function name : getNumberNDotKey()
// Description   : 키보드 입력시 숫자 및 '.'가 입력 가능
// Parameter     :
// ----------------------------------------------------------------------
// Usage         : onKeyDown=getNumberNDotKey()
// ----------------------------------------------------------------------
function getNumberNDotKey() {
 if ((event.keyCode >=48 && event.keyCode <=57)   // 자판 0~9
  || (event.keyCode >=96 && event.keyCode <= 105)  // keypad 0~9
  || (event.keyCode == 109)             // 자판 -
  || (event.keyCode == 189)             // keypad -
  || (event.keyCode == 110)             // 자판 .
  || (event.keyCode == 190)             // keypad .
  || (event.keyCode == 8)              // back space
  || (event.keyCode == 9)              // tab
  || (event.keyCode == 13)             // enter
  || (event.keyCode == 46)             // delete
  || (event.keyCode >= 37 && event.keyCode <= 40)) // 방향키
 {
  return true;
 } else {
  event.returnValue = false;
 }
}

// --------------------------------------------------------------------
// Function name : isDigitOrBar(str)
// Description   : '-' 기호를 포함한 숫자여부 판단, 숫자면 return true
// Parameter     : str(대상 문자열)
// --------------------------------------------------------------------
// Usage         : isDigitOrBar(str), 부호의 선행, 후행은 체크 못함
// --------------------------------------------------------------------
function isDigitOrBar(str) {
 for(var i=0; i < str.length; i++) {
  var ch= str.charAt(i) ;
  if((ch < "0" || ch > "9") && ch!="-") {
   return false;
  }
 }
 return true;
}

//---------------------------------------------------------------------
// Function name : getFormattedVal
// Description   : 숫자를 포멧이 갖추어진 문자열로 바꿈
//                 ###3 <= 숫자3은 세자리마다 ,를 찍겠다는 말
//                 .##### <= .(소수점)뒤로 5자리까지 표현하겠다는 말
// Parameter     : value  : 검사할 값
//                 format : 변환할 형태
// Return        :  변환된 값 리턴
// --------------------------------------------------------------------
// Usage         : getFormattedVal(value , "###3.#####")
//---------------------------------------------------------------------
function getFormattedVal(value,format) {
    value = ""+value;

    if(!format)
      return value;

    var sp = parseInt(format.charAt(3));

    if(!sp)
      return value;

    var pos = 0;
    var ret = "";
    var vSplit = value.split('.');
    var fSplit = format.split('.');
    var fp = fSplit[1];
    var fv = vSplit[1];
    var lv = vSplit[0];
    var len = lv.length;

    for(var i = len % sp; i < len; i += sp){
        if(i == 0 || lv.charAt(i-1) == '-')
            ret += lv.substring(pos,i);
        else
            ret += lv.substring(pos,i)+',';
        pos = i;
    }

    ret += lv.substring(pos,len);

    if(!fv)
        fv = "";
    if(!fp)
        fp = "";

    var len1 = fp.length;
    var len2 = fv.length;

    if(len1)
      ret += '.' + fv.substring(0,len1) + fp.substring(len1,len2);
    return ret;
}


//-------------------------------------------------------------------
// Function name : changeInt2Han
// Description   : 숫자 -> 한글로 변환
// Parameter     : string  : 변환 할 값
// Return        : 변환된 값 리턴 / 123 -> 일백이십삼
// -------------------------------------------------------------------
// Usage         : changeInt2Han(string)
//--------------------------------------------------------------------
function changeInt2Han(string) {
 hn = new Array("영","일","이","삼","사","오","육","칠","팔","구");
 hj = new Array("","만","억","조","경","해");
 ul = new Array("영천","영백","영십","영");
 tm = new Array();
 result = "";

 if (string.charAt(0)=="-") {
  result = "마이너스 ";
  string = string.substr(1,string.length-1);
 }

 loop_size = Math.ceil(string.length/4);
 string2 = "";

 for (count=string.length; count >= 0; count--)
  string2 += string.substring(count,count-1);
  string = string2;

 for (A=0;A<loop_size;A++) {
  sum = hj[A] + " ";
  tm[A] = string.substr(A*4,4);

  tm2 = "";
 
  for (count=tm[A].length; count >= 0; count--)
   tm2 += tm[A].substring(count,count-1);
 
  tm[A] = tm2;
  part_jari = tm[A].length;
 
  for (D=0;D<10;D++) {
   for (B=0;B<10;B++) tm[A] = tm[A].replace(B,hn[B]);
  }

    if (part_jari == 4) tm[A] = tm[A].charAt(0)+"천"+tm[A].charAt(1)+"백"+tm[A].charAt(2)+"십"+tm[A].charAt(3);
    else if (part_jari == 3) tm[A] = tm[A].charAt(0)+"백"+tm[A].charAt(1)+"십"+tm[A].charAt(2);
  else if (part_jari == 2) tm[A] = tm[A].charAt(0)+"십"+tm[A].charAt(1);
  else tm[A] = tm[A].charAt(0);

  for (C=0;C<4;C++) {
   if (tm[A].match(ul[C])) {
    part_jari--; tm[A] = tm[A].replace(ul[C],"");
   }
  }
   
  if (part_jari != 0) tm[A] += sum;
  }

 for (loop_size;loop_size>-1;loop_size--)
  result += tm[loop_size];
 
 result = result.replace("undefined","");
 return result;
}

//-----------------------------------------------------------------------
// Function name : changeInt2HanJa
// Description   : 숫자 -> 한자로 변환
// Parameter     : string  : 변환 할 값
// Return        : 변환된 값 리턴 / 일백이십삼 -> 壹百貳拾參
// ----------------------------------------------------------------------
// Usage         : changeInt2HanJa(string)
//-----------------------------------------------------------------------
function changeInt2HanJa(string) {
 hn = new Array("영","壹","貳","參","四","五","六","七","八","九");
 hj = new Array("","萬","億","兆");
 ul = new Array("영千","영百","영拾","영");
 tm = new Array();
 result = "";

 if (string.charAt(0)=="-") {
  result = "마이너스 ";
  string = string.substr(1,string.length-1);
 }
 loop_size = Math.ceil(string.length/4);
      string2 = "";
 for (count=string.length; count >= 0; count--)
  string2 += string.substring(count,count-1);
   
 string = string2;
 
 for (A=0;A<loop_size;A++) {
  sum = hj[A] + " ";
  tm[A] = string.substr(A*4,4);

  tm2 = "";

  for (count=tm[A].length; count >= 0; count--)
   tm2 += tm[A].substring(count,count-1);
 
  tm[A] = tm2;
  part_jari = tm[A].length;
  for (D=0;D<10;D++) {
   for (B=0;B<10;B++) tm[A] = tm[A].replace(B,hn[B]);
  }

  if (part_jari == 4) tm[A] = tm[A].charAt(0)+"千"+tm[A].charAt(1)+"百"+tm[A].charAt(2)+"拾"+tm[A].charAt(3);
  else if (part_jari == 3) tm[A] = tm[A].charAt(0)+"百"+tm[A].charAt(1)+"拾"+tm[A].charAt(2);
  else if (part_jari == 2) tm[A] = tm[A].charAt(0)+"拾"+tm[A].charAt(1);
  else tm[A] = tm[A].charAt(0);
  for (C=0;C<4;C++) {
   if (tm[A].match(ul[C])) {
    part_jari--; tm[A] = tm[A].replace(ul[C],"");
   }
  }
  if (part_jari != 0) tm[A] += sum;
 }

 for (loop_size;loop_size>-1;loop_size--) result += tm[loop_size];
   result = result.replace("undefined","");

 return result;
}

//-----------------------------------------------------------------------------
// Function name : isFraction
// Description   : 입력된 문자가 숫자, 분수(1/3,2/5..)인가를 체크하는 함수
// Parameter     : obj(입력 컨트롤명)
// ----------------------------------------------------------------------------
// Usage         : 사용자가 텍스트박스에 숫자값으로만 입력되어야 할 경우 이를
//                 검증하기 위해서 아래와 같이 사용
//                 <input type="text" onBlur="isFraction(this)">
//-----------------------------------------------------------------------------
function isFraction(obj) {
 var i,j;
 var str = new String(obj.value);
 var check_slash = 0;

 if ((str == '')||(str.length == 0))
   return true;

 for(i=0;i< str.length;i++) {
  if(!isInt(str.charAt(i))) {
 
   if(str.charAt(i) != '/') {
    alert('정수 또는 분수만 입력가능합니다.');
    obj.focus();
    obj.select();
    return false;
    } else {
    check_slash++;
    if (i==0) {
     alert('정수 또는 분수만 입력가능합니다.');
     obj.focus();
     obj.select();
     return false;
    }
   }
  }
 }
 j = i -1;

 if (str.charAt(j) == '/' || check_slash > 1) {
  alert('정수 또는 분수만 입력가능합니다.');
  obj.focus();
  return false;
 }
 return true;
}

//---------------------------------------------------------------------------------------
// Function name : checkDigitBody2
// Description   : 숫자 혹은 구분자('-', '.' 등)로만 구성되어 있는지를 검사
// Parameter     : obj(화면컨트롤), sep(구분자)
// --------------------------------------------------------------------------------------
// Usage         : <input type="text" onBlur="checkDigitBody2(this, '-')">
// Caution       : 화면컨트롤에 데이타가 없는 경우에는 false를 리턴
// --------------------------------------------------------------------------------------
function checkDigitBody2(obj, sep) {
 var str=obj.value;
 if (str.length == 0)
  return false;
 for (var i = 0; i < str.length; i++) {
  var ch = str.substring(i, i + 1);
  if ((ch < "0" || "9" < ch)) {
   if (ch != sep)
    return false;
  }
 }
 return true;
}

//---------------------------------------------------------------------------------------
// Function name : getNumberOnly
// Description   : 실수,정수,금액 유효성 체크 및 허용하지 않는 문자는 경고 없이 자동 삭제
// Parameter     : 필수 : obj(입력 컨트롤명), cmd(숫자 유형)
//---------------------------------------------------------------------------------------
// Usage         : <input name="num1" type="text"  onkeyup= "getNumberOnly(this, 'money')"  ...>
// Caution       : 일반적으로 다른 function에서 내부적 호출로 쓰임
//---------------------------------------------------------------------------------------
function getNumberOnly(obj, cmd) {
 var instr = obj.value;
 var cstr = "";
 var tempstr = "";

 if(cmd == "real") {
  cstr = "0123456789.-";          //실수
 } else if(cmd == "real2") {
  cstr = "0123456789.";      //양의실수
 } else if(cmd=="int") {
  cstr="0123456789-";             //정수
 } else if(cmd=="money") {
  cstr="0123456789,";       //금액
 } else if(cmd == "real3") {
  cstr = "0123456789.-,";         //실수 : , 포함
 } else if(cmd=='numeric') {
  cstr = "0123456789";      //숫자
 }

 //거꾸로 돌려야 함
 if(instr.length) {
  var len = instr.length;
  for(var i=len-1; i>=0; i--) {
   if(cstr.lastIndexOf(instr.charAt(i)) == -1) {
    instr = instr.substring(0, i)+ instr.substring(i+1);
    obj.value = instr;
   }
  }
 }
}

// --------------------------------------------------------------------------------------------
// Function name : addCommaStr
// Description   : 입력창에 숫자 데이터를 입력할때 자동으로 3자리별로 ',' 가 붙어 입력되게함
// Parameter     : str(문자열(숫자))
// --------------------------------------------------------------------------------------------
// Usage         :
// Caution       : 이 함수를 사용할때 Input 박스 값이 숫자 값인지 체크할 때는 쉼표를 자동으로
//                 체크하여 숫자여부를 판단하는 checkDigitBody2(obj, ",")를 사용한다.
// --------------------------------------------------------------------------------------------
function addCommaStr(str) {
 if (str.length < 1) {
    return "";
  } else {
  var tm = "";
  var ck = "";
  if (str.substring(0, 1) == "-") { //음수
   tm = str.substring(1, str.length);
   ck = "Y";
  } else {//양수
   tm = str;
   ck = "N";
  }
  var st = "";
  var cm = ",";

  for (var i = tm.length, j = 0; i > 0; i--, j++) {
   if ((j % 3) == 2) {
    if (tm.length == j + 1) st = tm.substring(i - 1, i) + st;
    else st = cm + tm.substring(i - 1, i) + st;
   } else {
    st = tm.substring(i - 1, i) + st;
   }
  }
  if (ck == "Y") st = "-" + st;
 }
 return st;
}

// --------------------------------------------------------------------------------------------
// Function name : delCommaStr
// Description   : 화폐구분자로 사용되는 ','문자를 제거하는 함수.
//                 보통 화면에는 ,로 표시하고, DB에는 ','를 제외한 숫자만을 insert할 때 사용
// Parameter     : str(금액형태의 문자열)
// --------------------------------------------------------------------------------------------
// Usage         :
// --------------------------------------------------------------------------------------------
function delCommaStr(str) {
 if (str.length < 1) {
  return "";
 } else {
  var st = "";
  var sp = ",";
  for (var i = 0; i < str.length; i++) {
   if (sp.indexOf(str.substring(i, i + 1)) == -1) {
    st += str.substring(i, i + 1);
   }
  }
  return st;
 }
}

// --------------------------------------------------------------------------------------------
// Function name : delComma
// Description   : 화폐구분자로 사용되는 ','문자를 제거하는 함수.
//                 보통 화면에는 ,로 표시하고, DB에는 ','를 제외한 숫자만을 insert할 때 사용
// Parameter     : obj(화면 입력박스명)
// --------------------------------------------------------------------------------------------
// Usage         :
// Caution       : delCommaStr 과 다른점은 value 가 아닌 obj값 사용
// --------------------------------------------------------------------------------------------
function delComma(obj) {
 var str =  String(obj.value);
 if (str.length < 1) {
  return "";
 } else {
  var st = "";
  var sp = ",";
  for (var i = 0; i < str.length; i++) {
    if (sp.indexOf(str.substring(i, i + 1)) == -1) {
      st += str.substring(i, i + 1);
    }
  }
  return st;
 }
}

// --------------------------------------------------------------------------------------------
// Function name : isUpperChar
// Description   : 영문 대문자인지를 체크하는 함수
// Parameter     : value(체크대상 문자)
// --------------------------------------------------------------------------------------------
// Usage         : var a='A'; if(isUpperChar(a)) { alert("대문자입니다"); }
// --------------------------------------------------------------------------------------------
function isUpperChar(value) {
 var _upperValue = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 var i;
 for(i=0;i<_upperValue.length;i++)
  if(value == _upperValue.charAt(i)) {
   return true;
  }

 return false;
}

// --------------------------------------------------------------------------------------------
// Function name : isLowerChar
// Description   : 영문 소문자인지를 체크하는 함수
// Parameter     : value(체크대상 문자)
// --------------------------------------------------------------------------------------------
// Usage         : var a='k'; if(isLowerChar(a)) { alert("소문자입니다"); }
// --------------------------------------------------------------------------------------------
function isLowerChar(value) {
 var _lowerValue = 'abcdefghijklmnopqrstuvwxyz';
 var i;
 for(i=0;i<_lowerValue.length;i++)
  if(value == _lowerValue.charAt(i)) {
   return true;
  }

 return false;
}

// -------------------------------------------------------------------------------------------------
// Function name : 특수문자여부체크
// Description   : 특수문자인지를 체크하는 함수(영문이나 한글이 아닌)
// Parameter     : value(체크대상 문자)
// -------------------------------------------------------------------------------------------------
// Usage         : var a='&'; if(isEtcChar(a)) { alert("특수문자입니다"); }
// -------------------------------------------------------------------------------------------------
function isEtcChar(value) {
 var _etcValue   = '~`!@#$%%^&*()-_=+\|[{]};:\'\",<.>/?';
    var j;
    for(j=0;j<_etcValue.length;j++)
        if(value == _etcValue.charAt(j)) {
            return true;
        }
    return false;
}


// -------------------------------------------------------------------------------------------------
// Function name : isEtcChar(value)
// Description   : 특수문자인지를 체크하는 함수(영문이나 한글이 아닌)
// Parameter     : value(체크대상 문자), 문자열이 아님 char하나에 대한 체크가능
// -------------------------------------------------------------------------------------------------
// Usage         : var a='&'; if(cmm_is_etc_char(a)) { alert("특수문자입니다"); }
// -------------------------------------------------------------------------------------------------
function isEtcString(value) {
 var _etcValue   = '~`!@#$%%^&*()-_=+\|[{]};:\'\",<.>/?';
 var i,j;
 for(i=0;i<value.length;i++) {
  for(j=0;j<_etcValue.length;j++)
   if(value.charAt(i) == _etcValue.charAt(j)) {
    return true;
   }
 }
 return false;
}

// -------------------------------------------------------------------------------------------------
// Function name : 영문대문자 변환
// Description   : 영문대문자로 변환하는 함수
// Parameter     : obj(변환대상 문자열을 가지고 있는 화면 컨트롤오브젝트) cmm_to_upper
// -------------------------------------------------------------------------------------------------
// Usage         : 사용자가 소문자로 입력하더라도 DB에는 대문자로 입력하고자 하는 경우
// -------------------------------------------------------------------------------------------------
function getUpperStr(obj) {
  var strNew = '';
 var str = obj.value;
    for(i=0 ; i<str.length; i++ ) {
   if(str.charAt(i) >= 'a' && str.charAt(i) <= 'z')
    strNew += str.charAt(i).toUpperCase() ;
   else
    strNew +=  str.charAt(i);
    }
 
 obj.value = strNew;
}

// 사용빈도 : 중
// 함수명: 몇개의 정해진 문자만 입력가능하도록 검사하는 함수
// 설  명: 정해진 문자열(영문자, 공백, '-', ',')만 입력하는 지를 검사하는 함수
// 인  자: ctl_digit(화면컨트롤)
// --------------------------------------------------------------------------------------------
// 사용법: <input type="text" onBlur="cmm_check_english_body(this)">
// 주의사항: 아래 소스를 변형하여 검사대상문자열을 변경하여 사용
// --------------------------------------------------------------------------------------------
function checkEnglishBody(obj) {
 var str=obj.value;
 if (str.length == 0)
  return false;
 // Checks that characters are numbers or hyphens.
 for (var i = 0; i < str.length; i++) {
  var ch = str.substring(i, i + 1);
  if(ch != "-") {
   if ((ch < "A" || "z" < ch )) {
    if (ch == " ")
      ;
    else if (ch == ",")
     ;
    else
   
    return false;
   }
  }
 }
 return true;
}

// 사용빈도 : 상
// 함수명: 문자열 좌측공백제거
// 설  명: 문자열 좌측의 공백 제거 처리 함수
// 인  자: str(체크대상 문자)
// --------------------------------------------------------------------------------------------
// 사용법: str = cmm_ltrim(str);
// --------------------------------------------------------------------------------------------
function getLtrim(str) {
 while(str.substring(0,1) == ' ')
  str = str.substring(1, str.length);
 return str;
}

// 사용빈도 : 중
// 함수명: 문자열 중간공백제거
// 설  명: 문자열 중간의 공백 제거 처리 함수
// 인  자: str(체크대상 문자)
// --------------------------------------------------------------------------------------------
// 사용법: str = cmm_mtrim(str);
// --------------------------------------------------------------------------------------------
function getMtrim(str) {
 for (i=0; i < str.length;)
  if (str.substring(i,i+1) == ' ')
   str = str.substring(0,i) + str.substring(i+1,str.length);
  else
   i++;

  return str;
}

// 사용빈도 : 상
// 함수명: 문자열 우측공백제거
// 설  명: 문자열 우측의 공백 제거 처리 함수
// 인  자: str(체크대상 문자)
// --------------------------------------------------------------------------------------------
// 사용법: str = cmm_rtrim(str);
// --------------------------------------------------------------------------------------------
function getRtrim(str) {
 while(str.substring(str.length-1,str.length) == ' ')
  str = str.substring(0, str.length-1);
 return str;
}

// 사용빈도 : 상
// 함수명: 공백문자를 제외한 문자열을 리턴하는 함수
// 설  명: 공백만을 제외한 문자열을 리턴(특수문자 등도 같이 리턴)
// 인  자: arg_str(검사대상 문자열)
// --------------------------------------------------------------------------------------------
// 사용법: cmm_str_trim('abc def');
// --------------------------------------------------------------------------------------------
function getStrTrim(arg_str) {
 var rtn_str = "";
 var i=0;
 while(arg_str.charAt(i) != "") {
  if(arg_str.charAt(i)!=' ') {
   rtn_str += arg_str.charAt(i);
  }
  i++;
 }
 return rtn_str;
}

//--------------------------------------------------------------------------------------------
// Function name : getReverse
// Description   : 주어진 문자열을 거꾸로 치환
// Parameter     : str - 치환할 문자
// Return        : 치환된 문자열
//--------------------------------------------------------------------------------------------
// Usage         : getReverse("123") => "321"
//--------------------------------------------------------------------------------------------
function getReverse(Str) {
 var ret = "";

 for (var i = 0; i < Str.length; i++)
  ret = Str.substr(i, 1) + ret;
  
  return ret;
}

//--------------------------------------------------------------------------------------------
// 함수명: 특수문자를 제거하는 함수
// 설  명: 제거하고자 하는 특수문자를 제거하여 리턴하는 함수
// 인  자: str(문자열), sep(제거하고자 하는 특수문자)
// --------------------------------------------------------------------------------------------
// 사용법: <input type="text" onBlur="cmm_remove_special_char(this.value, '-%*');">
// --------------------------------------------------------------------------------------------
function getRmSpChar(str, sep) {
 var sTmp = "";
 var sBuffer = "";
 var i, j;
 var equal=false;

 for (i=0; i < str.length; i++) {
  equal = false;
  sTmp = str.substring(i, i+1);
  for(j=0; j<sep.length;j++) {
   if(sep.charAt(j) == sTmp) {
    equal = true;
    break;
   }
  }

  if(equal == false)
   sBuffer += sTmp;
 }
 //alert(sBuffer);
 return sBuffer;
}

//---------------------------------------------------------------------------------
// Function name : isEmailCheck
// Description   : 텍스트 라인안에 값을 정규식 표현을 사용해서 메일형식(ID@도메인네임)을 검사.
//                 /(\S+)@(\S+)\.(\S+)/
//                 (\S+) : white space 즉, 공백이 아닌 하나 이상의 문자열이 존재
//                  @ : 그 뒤로 골뱅이(at)가 존재하고
//                 (\S+) : 다시 문자열이 존재
//                 \. : (.)dot
//                 (\S+) : 다시 문자열이 존재
// Parameter     : 
// Return        : 
//----------------------------------------------------------------------------------
// Usage         :  isEmailCheck(this)
//----------------------------------------------------------------------------------
function isEmailCheck(email) {
    mvalue = email ;
   
    if (mvalue.value == null || mvalue.value == "") return;
   
    if (mvalue.value.search(/(\S+)@(\S+)\.(\S+)/) == -1) {
   alert ("ID@고유도메인명 형식으로 입력하세요!!\n\n예) test@hanmail.net");
   
   mvalue.value = "";
   mvalue.focus();
   return false;
    } else {
   // alert("입력하신 메일 주소는\n\n" + mvalue.value + "\n\n입니다.");
   return false;
    }
}

//------------------------------------------------------------------
// Function name : fnBluring
// Description   :
// Parameter     :  
// Return        : 
//------------------------------------------------------------------
// Usage         :  
//------------------------------------------------------------------

function fnBluring() {
 if(event.srcElement.tagName=="A"||event.srcElement.tagName=="IMG"||event.srcElement.tagName=="Onclick"||event.srcElement.tagName=="TABLE")
    document.body.focus();
}

//------------------------------------------------------------------
// Function name : fnBsControl
// Description   : 백스페이스 제어
// Parameter     :  
// Return        : 
//------------------------------------------------------------------
// Usage         :  
//------------------------------------------------------------------
function fnBsControl() {
 var d = document;
 //var sw = 0;
   
  if (window.event.keyCode==8) {
  // 등록된 객체이름을 백스페이스 방지
  //if (d.objList == null)
  //{
  //    return false;
  //}
  //else
  //{
  //    for (i=0; i<d.objList.length; i++)
  //    {
  //         if (window.event.srcElement.name == d.objList[i].name)
  //         {
  //            sw = 1;         
  //            break;      
  //         }
  //    }
  //   
  //    if (sw == 0)
  //    {
  //        return false;
  //    }
  //}
 
  // 폼객체의 종류로 백스페이스 방지
  if (window.event.srcElement.type != 'text') {
   return false;   
  }
  }
}

//------------------------------------------------------------------
// Function name : isValidDay
// Description   : 지정 년,월,일이 달력상으로 존재하는 날짜인지 검사
// Parameter     : year - 년 , month - 월 , day - 일 
// Return        : 
//------------------------------------------------------------------
// Usage         :  
//------------------------------------------------------------------
 
function isValidDay(year, month, day) {   
 var m = parseInt(month,10) - 1;
 var d = parseInt(day,10);

 var end = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
 if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
  end[1] = 29;
 }

 return (d >= 1 && d <= end[m]);

Posted by 1010
02.Oracle/DataBase2009. 4. 25. 16:05
반응형
Oracle Rollup, Cube, and Grouping Sets
Version 10.2
 
General
Rollup Note: ROLLUP enables a SELECT statement to calculate multiple levels of subtotals across a specified group of dimensions. It also calculates a grand total. ROLLUP is a simple extension to the GROUP BY clause, so its syntax is extremely easy to use. The ROLLUP extension is highly efficient, adding minimal overhead to a query.

The action of ROLLUP is straight forward: it creates subtotals that roll up from the most detailed level to a grand total, following a grouping list specified in the ROLLUP clause. ROLLUP takes as its argument an ordered list of grouping columns. First, it calculates the standard aggregate values specified in the GROUP BY clause. Then, it creates progressively higher-level subtotals, moving from right to left through the list of grouping columns. Finally, it creates a grand total.

ROLLUP creates subtotals at n+1 levels, where n is the number of grouping columns. For instance, if a query specifies ROLLUP on grouping columns of time, region, and department (n=3), the result set will include rows at four aggregation levels.
Cube Note: CUBE takes a specified set of grouping columns and creates subtotals for all of their possible combinations. In terms of multidimensional analysis, CUBE generates all the subtotals that could be calculated for a data cube with the specified dimensions. If you have specified CUBE(time, region, department), the result set will include all the values that would be included in an equivalent ROLLUP statement plus additional combinations.

Consider Using CUBE in any situation requiring cross-tabular reports. The data needed for cross-tabular reports can be generated with a single SELECT using CUBE. Like ROLLUP, CUBE can be helpful in generating summary tables. Note that population of summary tables is even faster if the CUBE query executes in parallel.

CUBE is typically most suitable in queries that use columns from multiple dimensions rather than columns representing different levels of a single dimension. For instance, a commonly requested cross-tabulation might need subtotals for all the combinations of month, state, and product. These are three independent dimensions, and analysis of all possible subtotal combinations is commonplace. In contrast, a cross-tabulation showing all possible combinations of year, month, and day would have several values of limited interest, because there is a natural hierarchy in the time dimension. Subtotals such as profit by day of month summed across year would be unnecessary in most analyses.
 
Create demo schema @$ORACLE_HOME\demo\schema\sales_history\sh_main.sql
 
ROLLUP
Full Rollup Demo SQL Statement col country_name format a25

SELECT ch.channel_desc, t.calendar_month_desc, co.country_name,
TO_CHAR(SUM(s.amount_sold), '9,999,999,999') SALES$
FROM sales s, customers cu, times t, channels ch, countries co
WHERE s.time_id = t.time_id
AND s.cust_id = cu.cust_id
AND s.channel_id = ch.channel_id
AND cu.country_id = co.country_id
AND ch.channel_desc IN ('Direct Sales','Internet')
AND t.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_name LIKE 'U%'
GROUP BY ROLLUP(ch.channel_desc, t.calendar_month_desc, co.country_name);
Partial Rollup Demo SQL Statement SELECT ch.channel_desc, t.calendar_month_desc, co.country_name,
TO_CHAR(SUM(s.amount_sold), '9,999,999,999') SALES$
FROM sales s, customers cu, times t, channels ch, countries co
WHERE s.time_id = t.time_id
AND s.cust_id = cu.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc IN ('Direct Sales','Internet')
AND t.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_name LIKE 'U%'
GROUP BY ch.channel_desc, ROLLUP(t.calendar_month_desc, co.country_name);
 
GROUP_ID

The following demonstrates a GROUP BY with repeating values and their identification with the GROUP_ID() function
GROUP_ID()
CREATE TABLE grp_rep (
person_id   NUMBER(3),
division    VARCHAR2(3),
commission  NUMBER(5));

INSERT INTO grp_rep VALUES (1,'SAM',1000);
INSERT INTO grp_rep VALUES (2,'EUR',1200);
INSERT INTO grp_rep VALUES (1,'EUR',1450);
INSERT INTO grp_rep VALUES (1,'EUR',700);
INSERT INTO grp_rep VALUES (2,'SEA',1000);
INSERT INTO grp_rep VALUES (2,'SEA',2000);
INSERT INTO grp_rep VALUES (1,'EUR',800);
COMMIT;

SELECT person_id, division, SUM(commission)
FROM grp_rep
GROUP BY person_id, division;

SELECT person_id, division, SUM(commission)
FROM grp_rep
GROUP BY person_id, ROLLUP (person_id, division);

SELECT person_id, division, SUM(commission), GROUP_ID() g
FROM grp_rep
GROUP BY person_id, ROLLUP (person_id, division);

SELECT person_id, division, SUM(commission), GROUP_ID() g
FROM grp_rep
GROUP BY person_id, ROLLUP (person_id, division)
HAVING GROUP_ID() = 0;
 
GROUPING
Distinguishes superaggregate rows from regular grouped rows.
Distinguish a null representing the set of all values in a superaggregate row from a null in a regular row.
GROUPING(<expression>)
conn hr/hr

set linesize 121
col job format a10

SELECT DECODE(department_name, '1', 'All Departments',
department_name) AS DEPARTMENT,
DECODE(job_id, '1', 'All Jobs', job_id) AS job,
COUNT(*) "Total Empl", AVG(salary) * 12 "Average Sal"
FROM employees e, departments d
WHERE d.department_id = e.department_id
GROUP BY ROLLUP (department_name, job_id);

SELECT DECODE(GROUPING(department_name), '1', 'All Departments',
department_name) AS DEPARTMENT,
DECODE(GROUPING(job_id), '1', 'All Jobs', job_id) AS job,
COUNT(*) "Total Empl", AVG(salary) * 12 "Average Sal"
FROM employees e, departments d
WHERE d.department_id = e.department_id
GROUP BY ROLLUP (department_name, job_id);
 
GROUPING SETS
Demo from OTN conn sh/sh

SELECT channel_desc, calendar_month_desc, co.country_id,
TO_CHAR(SUM(amount_sold) , '9,999,999,999') SALES$
FROM sales, customers, times, channels, countries co
WHERE sales.time_id=times.time_id
AND sales.cust_id=customers.cust_id
AND sales.channel_id= channels.channel_id
AND customers.country_id = co.country_id
AND channels.channel_desc IN ('Direct Sales', 'Internet')
AND times.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_iso_code IN ('UK', 'US')
GROUP BY GROUPING SETS(
(channel_desc, calendar_month_desc, co.country_id),
(channel_desc, co.country_id),
(calendar_month_desc, co.country_id));

SELECT channel_desc, calendar_month_desc, co.country_id,
TO_CHAR(SUM(amount_sold) , '9,999,999,999') SALES$
FROM sales, customers, times, channels, countries co
WHERE sales.time_id=times.time_id
AND sales.cust_id=customers.cust_id
AND sales.channel_id= channels.channel_id
AND customers.country_id = co.country_id
AND channels.channel_desc IN ('Direct Sales', 'Internet')
AND times.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_iso_code IN ('UK', 'US')
GROUP BY CUBE(channel_desc, calendar_month_desc, co.country_id);
CUBE

Full Cube Rollup
GROUP BY CUBE()
conn sh/sh

col sales$ format a20

SELECT ch.channel_desc, calendar_month_desc, co.country_name,
TO_CHAR(SUM(s.amount_sold), '9,999,999,999') SALES$
FROM sales s, customers cu, times t, channels ch, countries co
WHERE s.time_id = t.time_id
AND s.cust_id = cu.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc IN ('Direct Sales', 'Internet')
AND t.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_name LIKE 'U%'
GROUP BY CUBE(channel_desc, t.calendar_month_desc, co.country_name);

Partial Cube Rollup
conn sh/sh

SELECT ch.channel_desc, calendar_month_desc, co.country_name,
TO_CHAR(SUM(s.amount_sold), '9,999,999,999') SALES$
FROM sales s, customers cu, times t, channels ch, countries co
WHERE s.time_id = t.time_id
AND s.cust_id = cu.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc IN ('Direct Sales', 'Internet')
AND t.calendar_month_desc IN ('2000-09', '2000-10')
AND co.country_name LIKE 'U%'
GROUP BY channel_desc, CUBE(t.calendar_month_desc, co.country_name);
 
GROUPING_ID
Returns a number corresponding to the GROUPING bit vector associated with a row.

In queries with many GROUP BY expressions, determining the GROUP BY level of a particular row requires many GROUPING functions, which leads to cumbersome SQL. GROUPING_ID is useful in these cases.
GROUPING_ID(<expression>, <expression>, ..)
conn sh/sh

SELECT channel_id, promo_id, SUM(amount_sold) s_sales,
GROUPING(channel_id) gc,
GROUPING(promo_id) gp
FROM sales
WHERE promo_id > 496
GROUP BY CUBE(channel_id, promo_id);

SELECT channel_id, promo_id, SUM(amount_sold) s_sales,
GROUPING(channel_id) AS GC,
GROUPING(promo_id) AS GP,
GROUPING_ID(channel_id, promo_id) AS GCP,
GROUPING_ID(promo_id, channel_id) AS GPC
FROM sales
WHERE promo_id > 496
GROUP BY CUBE(channel_id, promo_id);
 
출처 :: http://www.psoug.org/reference/OLD/rollup.html#gbgi
Posted by 1010
반응형
사실 플래시만큼 사람들에게 호기심과 더불어 미적 감각을 자극하는 웹 저작도구는 없다고 해도 과언이 아닙니다.
하지만 그 만큼이나 오해와 더불어 저주받을 망할 것으로 취급받는 것도 없을듯 합니다.
플래시 떡칠이 된 사이트를 개념이 없는 사이트로 우리는 흔히들 단정짓곤 하죠.

사실 인터넷 쇼핑몰 같은 곳을 둘러보면 진짜 개념이 안드로메다로 간 그런 곳도 종종 발견됩니다만 일정부분 플래시에 대한 오해는 정말 지극히 단순한 관점에서 시작됩니다.
무언가 화려한만큼 그만큼 느리다...라는 무조건적인 대입이죠.
그리고 일정부분은 틀린 말도 아닙니다...OTL

많은 사람들이 플래시를 보고 무조건 웹사이트가 느리다...라고 선입견을 갖는 큰 이유는 플래시가 무언가 보여주는 만큼 네트웍 대역폭을 잡아먹는다는 선입견에서 시작합니다.
허나 제가 아는 대중적인 저작도구로 만들어진 웹 창작물 중  플래시만큼의 컨텐츠를 보여주면서 대역폭을 조금 잡아먹는것도 없습니다.
흔히들 gif로 만들어진 그림화일은 무조건 빠른거, 좋은거로 인식들 하는데 gif로 플래시가 보여주는 컨텐츠 - 기능적인 제한은 둘째치고 - 를 보여주려면 예상컨데 약 10배정도의 대역폭을 잡아먹어야 간신히 비슷해질겁니다.
게다가 인터렉티브는 택도 없는 소리고요...
그러나 실제로 플래시로 떡칠이된 곳을 가보면 웹사이트가 징그럽게 느린 것을 우리는 경험할수 있습니다.
그 이유는 플래시의 씨피유 점유율이 상당히 높기 때문입니다.
즉 네트웍에서 주고받는 무언가가 많아서 그런게 아니라 씨피유가 헐떡여서 그런겁니다.
그러나 그것도 꽤 오래전 얘기입니다.
몇년 전의 컴퓨터 상황을 가지고 플래시가 들어가면 느리다....라는 얘기가 오늘날까지도 통용되는 것이지요.
사실 요즘의 최저수준의 듀얼코어 씨피유라도 왠만한 플래시 도배는 무난히 넘어갑니다.
듀얼코어까지 가지 않더라도 어지간하면 플래시 한둘은 우습게 소화할수 있고 제 블로그도 다른 블로그에 비하면 플래시 떡칠이 되어 있다고 해도 과언이 아닐 정도로 많이 쓰이고 있습니다.
그리 쾌적한 환경은 아니라고 할 수 있지만 어지간히 구린 컴퓨터 - 이를테면 도서관이나 공공기관의 공용 대기업 컴퓨터들에서 느리다고 정평이 나있는 익스 6으로 접속해봐도 플래시로 인해 느려진다는 느낌은 거의 받을수 없습니다.
이제는 플래시는 느린 무엇...이라 말하는 자체가 과거의 얘기입니다.

- 그러나 플래시 떡칠이 되어 환장하게 하는 쇼핑몰 같은 곳을 용서할 수 있는 것은 아닙니다....ㅡ.ㅡa

사설이 길어졌는데 저는 플래시에 대해서 거의 모른다고 해도 과언이 아닙니다.
남이 만든 플래시 원본(fla)을 던져줘도 상당한 시간이 걸려야 구조를 이해하고 저에게 맞게 커스터마이징을 하는 그런 수준이라 제가 플래시를 배울 이유도 없지만 필요에 의해 사용하려하면 가끔은 난감해질 때가 많습니다.
그러나 세상은 저같은 사람을 위해 재미난 장난감들을 많이 준비해놨습니다....lol



필요에 의해 선택하는 저작도구


사실 플래시를 제대로 이용하려면 적어도 매크로미디어 플래시, 아니 지금은 합병되었으니 adobe 플래시를 어느정도는 다룰수 있어야 하고 그 기본 원리는 알고 있어야 합니다.
그래야 적어도 인터넷에서 구할수 있는 무료 템플릿을 얻어서 자신에 맞게 가공해서 써먹기라도 해보죠.
- 제 블로그 초기 화면의 메인 메뉴 http://liebe.tistory.com/ 는 제가 그렇게 수정한 것입니다.

하지만 플래시를 전혀 모르고도 얼마든 플래시를 써먹을수는 있습니다.
그를 위한 여러가지 용도의 작은 툴들도 많이 나와있고요.
여기에서는 그런 툴들에 대해 소개해보려 합니다.
소개하려는 툴은 아래와 같습니다.
  1. Swiff Chart - 그래프, 차트를 동적인 플래시로 만들어줌.
  2. Demo Builder - 화면을 동영상으로 저장 편집 프로그램인 캄타시아와 비슷한 저작 프로그램.
  3. Flah Banner Maker - 빠르고 간단하게 배너나 이미지 삽입 플래시 컨텐츠를 제작.
  4. Flash Menu Labs - 웹사이트의 네비게이션 메뉴를 플래시로 빠르게 제작.
  5. Flash Slide show Builder - 이미지와 사운드등을 조합하여 플래시로 슬라이드 쇼를 제작
위에 기재한 툴들보다 더 기능적으로 좋고 효과가 더 확실한 툴들도 많습니다.
다만 제가 저 툴들을 즐겨 사용하고 자주 찾는 이유는 저는 특별한 목적이나 전문적 목적을 위해 플래시를 이용하거나 무언가를 만들기 위해 그 저작도구를 처음부터 배울 이유도, 필요도 없는 사람이기 때문입니다.
저에게 저런 도구들은 단지 제가 만들고 싶은 무언가를 위해 선택하는 도구일뿐이지 배워야 할 무언가가 아닙니다..
저 프로그램들은 직접 사용해보시면 아시겠지만
  • 사용법이 무척 간단하다. - 대부분 마법사 기능을 지원함
  • 템플릿이 풍부하다 - 미리 디자인된 포맷을 수정해서 쓰면 됩니다. 저는 디자이너가 아닙니다...OTL
  • 이런저런 부가기능이 필요없이 사용가능하다 - 조금 더 플래시를 배우면 아시겠지만 플래시는 종종 자바스크립과 연동되어 움직입니다. 그런 부분이 거의 없이 작동합니다.
  • 다들 상당히 알려진 프로그램들이라 최적화가 잘되어 있어 결과물의 용량이 상당히 작다.
뭐 이정도의 장점이 있겠네요...



Swiff Chart


제작사 홈페이지 :  http://www.globfx.com/
다운로드 링크 : http://www.globfx.com/downloads/
국내 리뷰 : http://www.demitrio.com:8088/sonarradar/205http://www.sigistory.com/tt/236,

이번 외장하드 리뷰때 제가 사용했던 것인데 사실 이 프로그램은 파워포인트로 프레젠테이션을 할때 써왔던 것입니다.
블로그에는 그래프 같은걸 쓸 일이 별로 없어서 안썼던 것인데 이런 류의 프로그램중 사용하기가 가장 쉽고 템플릿이 풍부하여 보기 좋고 직관적으로 꾸미기가 참 좋습니다.
2009/01/22 - [괴상망측BOX] - 세이브데이타 20202 외장 하드를 사용해 보다.

백문이 불여일견.....즉석에서 간단히 표를 하나 만들어보겠습니다.


결과물



     
 
 
 
     



Demo Builder


제작사 홈페이지 :  http://www.demo-builder.com/
다운로드 링크 : http://www.demo-builder.com/download.html
국내 리뷰 : http://www.demo-builder.co.kr/,
                http://kdb.infomaster.co.kr/?view=view&mode=all&ctId=01&listCtId=&idx=119006&page=8

제가 이전부터 종종 사용해왔던 데몬스트레이션용 툴입니다.
위의 Swiff Chart의 예제 플래시 영상이나  [괴상망측BOX] - 티스토리에서 활용가능한 Web-Based Player 부록 에도 사용되었습니다.
말로 설명하기 힘들거나 시각적으로 보여줄 필요가 있을때 간단하게 전자 메뉴얼을 제작할수 있어 매우 편리합니다.
프리젠테이션으로 사용할때도 상당히 좋습니다.
이런 비슷한 프로그램으로 캄타시아 스튜디오 같은 프로그램이 있지만 전체를 동영상으로 캡처하여 동영상 취급하여 결과물을 만드는 것이 아니라 필요한 화면과 움직임만 캡처하여 플래시로 저장할수 있기에 용량을 상당히 작게 만들수 있고 인터렉티브한 동작도 가능합니다. - 물론 캄타시아와 같은 풀모션 동영상으로도 만들수 있습니다.
일단 사용법이 상당히 간단하기에 애용하는 프로그램입니다.
마법사를 지원하며 딱히 설명이 필요없을 정도로 간단한 툴이므로 직접 사용해보시면 금방 아실수 있으실겁니다.
한국 총판 싸이트에 한글로 된 매뉴얼도 있습니다.

     
   
     


처음 프로그램을 시작하게 되면 반기는 화면입니다. " 크리에이트 뉴 무비" 로 작업을 시작합니다.








Flash Banner Maker


제작사 홈페이지 :  http://www.aleosoft.com/
다운로드 링크 : http://www.aleosoft.com/download.html
국내 리뷰 : http://blog.daum.net/r____/6720604

사실 플래시 배너 만드는 프로그램은 너무나도 많습니다.
이것도 그 중 하나인데 이 프로그램의 장점은 아주 직관적으로, 빠르게 결과물을 만들어주고 프리셋이 상당히 많고 여러가지 효과도 나름 쏠쏠하게 추가할수 있다는 것입니다.
사실 말이 배너 제작 프로그램이지 저는 배너 만들 일이 없으니 거의 플래시를 이용한 포스트 꾸미는 용도로만 거의 사용했습니다.   이를테면 타이틀이라던지 트랙리스트 등등...
예전엔 이것을 사용해서 포스팅을 많이 작성했는데...요즘은 이것도 귀찮아서 사용하지 않고 대충 넘어가고 있습니다.
- 아무리 쉽고 간편하다고 해도 표현되는 텍스트는 직접 입력해야 하니까... 아무리 카피 앤 페이스트라고 해도.... :)

사용된 예는....
2008/07/26 - [그것을 평하다/Review] - SOLARIS
2008/10/04 - [TrancE/ASOT podcast] - A State Of Trance Official Podcast Episode 052
등등입니다.
(뒤져보면 무척 많이 썼는데 귀찮아서.....lol )

이 프로그램은 프로그램 전체가 마법사 형태이고 매우 단순하고 심플하지만 직관적이라 사용하기가 너무 쉽습니다.


이런 형식으로 좌측 메뉴에서 순서대로 작업이 내려오며 우측 메뉴에서 디테일을 챙기기만 하면 됩니다.
작업 적용은 프리뷰로 실시간으로 보여줍니다.



     
 

[Flash] http://liebe.tistory.com/attachment/jl330000000180.swf

 
     




다른 툴에 대해서는 2부에서 연재하도록 하겠습니다.
플래시로 포스트를 도배하니 너무 느리다는 지적이 있어서 포스트 하나에 몰아두기엔 좀 난감하네요.

이거저거 할줄 모른다고 그냥 모르니까....할수도 있습니다.
하지만 저같이 플래시맹도 이런저런 툴을 써서 전혀 모르는 상태에서도 제 필요에 의해서는 충분할 그런 플래시 결과물을 만들어냅니다.
작업시간....하나당 10분도 안걸립니다.
저는 작업을 하기 위해 뭘 배우고 시간 걸리는걸 가장 경계합니다.
필요한 무언가를 얻기 위해, 나중을 위해 배우는 것도 의미가 있지만 저는 일단 디자이너가 아니니까요.
세상엔 저같은 사람을 도와줄 좋은 프로그램들이 너무도 많습니다... :)




이 포스트는 미스티니아님의 트랜스를 만들자 강좌를 쉽고 빠르게 진행하실수 있도록 만들어졌습니다.
한번 방문해보시기 바랍니다.
Posted by 1010
02.Oracle/DataBase2009. 4. 24. 15:16
반응형
http://www.oracleclub.com/article/11368

현재는 삼성SDS에 근무중이신 양진석 DBA님께서 프로젝트 기간중에 직접 작성하신 내용들을 몰래(?) 빼내서 올립니다.


요즈음 한참 바빠서 자바쪽 강의를 계속 쉬고 있어 죄송하네요^^ 앞으로 계속 정진하도록 노력하겠습니다.


파일백업을 물리적으로 하는 방법도 있지만 전체를 백업받고 또 문제가 생겼을 때 그것이 아주 작은 문제라 하더라도 전체를 복구하고 ... 머 이런식으로 해도 되지만 이건 좀 문제가 있죠. 그래서 논리적 백업과 복구(export / import)를 가끔 해주시면 DB관리를 좀 더 효율적으로 해줄 수 있지 않을까 합니다.

정기기적으로 물리적인 백업을 해주시고, 주기적으로 아래처럼 테이블 단위의 논리적인 백업을 해주시면 좋을듯 싶습니다.


주의해서 보실 부분은 붉은 글씨 부분이며 여러가지 옵션은 검색을 하시거나 책을 보시면 자세히 나와 있습니다. 그리고 반대로 import는 문제가 생겼을 때 해주시면 되겠죠.


#!/bin/ksh

export ORACLE_HOME=/oracle

export NLS_LANG=American_America.KO16KSC5601

export PATH=$PATH:$HOME/bin:$ORACLE_HOME/bin:$ORACLE_HOME/lib

export ORACLE_SID=UNIABS

DATE=`date '+%Y%m%d'`
LOG_FILE="/unidb3/export_backup/IRIS_Full_${DATE}.log"
DUMP_FILE="/unidb3/export_backup/IRIS_Full_${DATE}.dmp"

find /oracle2/orabackup/data/. -name "IRIS_Full*" -exec rm -fr {} \;
mknod /tmp/exp_IRIS p
compress </tmp/exp_IRIS> /unidb3/export_backup/IRIS_Full_${DATE}.dmp.Z &

echo `date` >> $LOG_FILE
#exp system/iris920 file=$DUMP_FILE full=y buffer=6000000 log=$LOG_FILE
exp system/iris920 file=/tmp/exp_IRIS full=y buffer=6000000 log=$LOG_FILE
# exp를 통신화일에 집어넣고 그 파일을 background로 compress한다.
echo `date` >> $LOG_FILE


양진석 DBA 께서 아주 간단한 내용이라고 하지만 저는 좀 어렵네요^^

Posted by 1010
02.Oracle/DataBase2009. 4. 24. 15:16
반응형

출처 : http://oracleclub.com/article/12449

아시는 분은 아시리라 생각이 됩니다만 행여나 도움이 될까 해서 이렇게 몇 글자 적습니다.


우리 보통 일련번호를 만들 때면 max(일련번호)를 조회한 후 다음 쿼리를 시작하죠. 이 경우에는 두 번의 SQL 문장이 실행되었으므로 매우 비 효율적이라 할 수 있습니다.

그래서 이 단점을 보완한 것이 decode 문을 이용해서 한번에 처리하는 방법이 나왔습니다. 그러나 이 방법 역시 데이터가 적을 경우에는 상관이 없으나 대량의 데이터가 되면 상황은 완전히 달라지죠. 그 많은 데이터를 grouping 해야 하니까요.

그래서 채번 테이블이라는 것을 구성해서 일련번호가 일정 수준 이상까지는 증가하지 못하도록 구성을 하죠.


* PK 코드 + 구분 + 일련번호로 구성된 테이블이 있다고 하면 쿼리는 아래처럼 되겠죠.


select 최종일련번호 + 1

into     :v_주문일련번호

from    주문

where  코드 = '001'

and     구분 = 'off line'


그 후에는 보통 본 테이블에 입력을 하는 쿼리가 오겠죠.


그러나 이것도 여러명의 사용자가 이용할 경우에는 중복에 문제가 있기 때문에 FOR UPDATE를 통한 Lock을 걸어주어야 하므로 결국 잠금으로 인한 부하가 발생할 수 있습니다.


그래서 최종적으로 도출된 것이 인덱스 역방향 정렬을 이용한 채번 방법이라는 것이 있습니다.


/* i_01: 인덱스명 */

insert into 주문(주문일련번호, col2, col3, ...)

select /*+ INDEX_DESC(B i_01) +*/

from   주문 A,

         목록 B

where A.주문일련번호 = B.주문일련번호

and    rownum = 1;


인덱스 역방향 정렬을 이용하여 ROWNUM = 1에 해당하는 데이터를 조회하였으므로 성능도 좋고, 잠금으로 인한 대기현상도 나타나지 않겠죠.

Posted by 1010
98..Etc/Log4J2009. 4. 24. 13:26
반응형

로그 찍는 거 별로 안좋아하는데 로그를 찍어보니까 더 좋은 것 같아요 ^^
게다가 log4j라는 매우 우수한 로그찍는 프로그램이 있습니다.
sysout에서 벗어나봅시다-_-; 습관적으로 sysout을-_-(System.out.println()......-_-)

우선 이클립스에서 프로젝트를 하나 만들어봅시다.
log4j를 받아봅시다.
http://logging.apache.org/log4j/1.2/download.html
1.2버전입니다. 받아서 log4j-1.2.15.jar파일을 라이브러리에 추가합시다.

log4j설정파일을 만들어봅시다.
최상위 폴더에다가 log4j.properties파일을 만듭시다.


# Log4j Setting file
log4j.rootLogger=INFO, console

# Daily file log
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.File=D:/mudchobo/Log/glv.log
log4j.appender.logfile.DatePattern='.'yyyy-MM-dd
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=[%d{HH:mm:ss}][%-5p](%F:%L) - %m%n

# Console log
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%-5p %l - %m%n

# log level and appender
log4j.logger.com.mudchobo=DEBUG, console
log4j.logger.com.mudchobo.Test=INFO, logfile

대략 내용을 살펴보면 log4j.rootLogger는 최상위 로거입니다.
모든 INFO레벨이상의 로그는 다 console로 찍겠다는 겁니다.
(레벨에는 DEBUG, INFO, WARN, ERROR, FATAL 순인데, 예를 들어 INFO레벨로 지정해두면 logger.debug로 찍는 로그는 나타나지 않습니다. INFO레벨 이상것만 나타납니다.)

console은 아래 #Console log쪽에 보시면
log4j.appender.console <- 요 이름입니다.
요 console은 자세히보면 ConsoleAppender라는 클래스입니다. 이건 말그대로 콘솔에 로그를 찍어준다는 겁니다. layout에는 PatternLayout을 지정할 수 있는데 저 패턴은 뭐 레벨이 뭐고, 클래스가 뭐고, 메시지찍고 뭐 그런 내용입니다. 검색 고고싱-_-;

그리고, 파일에다가 출력 할 수 있는데, DailyRollingFileAppender클래스를 이용합니다. 이눔은 말그대로 매일매일 다른로그를 사용하게 만듭니다. 로그이름이 위와 같이 glv.log라면, 해당로그가 어제날짜인데 로그를 찍으려고 하면 기존에 있던 파일은 glv.log.2008-04-17 이렇게 바꿔줍니다.

아래부분에 보면 log4j.logger. 다음에 패키지명이나 클래스명을 지정해놓고, 로그레벨과 출력할 로그를 지정할 수 있는데요. 해당 클래스나 패키지의 로그는 저걸로 찍겠다는 겁니다. Test클래스는 logfile로 찍힌다는 겁니다.
그리고, rootLogger가 colsole로 지정되어 있기 때문에 console에도 찍히겠죠? ^^

로그를 찍어봅시다.
TestLogging이라는 프로젝트 이름으로 만듭시다.

Test클래스를 만들어봅시다.
Test.java

package com.mudchobo;

import org.apache.log4j.Logger;

public class Test {

 private Logger logger = Logger.getLogger(getClass());
 
 public void println() {
  logger.info("안녕하세요! Test입니다");
 }
}

Test2클래스를 만들어봅시다.
Test2.java

package com.mudchobo;

import org.apache.log4j.Logger;

public class Test2 {

private Logger logger = Logger.getLogger(getClass());
 
 public void println() {
  logger.info("안녕하세요! Test2입니다.");
 }
}

TestLogging클래스를 만들어봅시다. 메인을 만들어야합니다.

package com.mudchobo;

public class TestLogging {

 public static void main(String[] args) {
  Test test = new Test();
  Test2 test2 = new Test2();
  
  test.println();
  test2.println();
 }
}

자 그럼 콘솔에는
INFO  com.mudchobo.Test.println(Test.java:10) - 안녕하세요! Test입니다.
INFO  com.mudchobo.Test.println(Test.java:10) - 안녕하세요! Test입니다.
INFO  com.mudchobo.Test2.println(Test2.java:10) - 안녕하세요! Test2입니다.
INFO  com.mudchobo.Test2.println(Test2.java:10) - 안녕하세요! Test2입니다.
이렇게 출력이 될 것이고 로그파일에는
[19:56:35][INFO ](Test.java:10) - 안녕하세요! Test입니다.
이것만 출력될 것입니다.
위에 콘솔에 두번 찍힌 이유는 Rootlogger도 찍고, 아래 패키지를 지정한 로그도 찍었기 때문이죠.
그리고, 파일에는 한번만 쓰여진 이유는 파일에 쓰는건
log4j.logger.com.mudchobo.Test=INFO, logfile 여기 이 Test클래스 하나죠-_-;
이상입니다-_-;

출처 : http://mudchobo.tomeii.com/tt/category/자바(Java)?page=6
Posted by 1010
01.JAVA/Java2009. 4. 24. 13:25
반응형

일주일에 한번, 하루에 한번 작업을 수행하는 프로세스를 실행하고 싶을 때가 있을 껍니다.
한가한 시간에 자동으로 배치작업을 실행하는 등의 작업을 자바에서 수행할 수 있습니다.
Timer라는 클래스에다가 시작날짜, 시간을 설정한 뒤, TimerTask클래스를 상속받은 클래스에서 run메소드를 구현하게 되면 설정된 시간에 run메소드가 자동으로 수행되게 됩니다.

코드를 보도록 합시다.


package com.mudchobo.scheduler;

import java.util.TimerTask;

public class WeeklySearch extends TimerTask {

 @Override
 public void run() {
  System.out.println("WeeklySearch!");
 }
}

TimerTask를 상속받아서 run메소드를 구현했습니다. run메소드는 간단히 WeeklySearch라고 보여주는군요.

그럼 메인을 보도록 합시다.

package com.mudchobo.scheduler;

import java.util.Calendar;
import java.util.Timer;

public class Scheduler {

 public static void main(String[] args) {
  WeeklySearch weeklySearch = new WeeklySearch();
  
  Timer timer = new Timer();
  Calendar date = Calendar.getInstance();
  date.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
  date.set(Calendar.AM_PM, Calendar.PM);
  date.set(Calendar.HOUR, 11);
  date.set(Calendar.MINUTE, 29);
  date.set(Calendar.SECOND, 0);
  date.set(Calendar.MILLISECOND, 0);
  
  timer.scheduleAtFixedRate(weeklySearch, date.getTime(), 
    1000 * 60 * 60 * 24 * 7);
 }
}

Timer객체, Calendar객체를 선언합니다. Calendar객체에는 이 스케쥴이 시작될 시간을 설정해서 넣습니다. 그 뒤에 timer에 있는 scheduleAtFixedRate메소드에 첫번째 인자는 맨 위에서 생성한 TimerTask객체를 넣으면 되구요. 두번째는 이 스케쥴이 시작될 시간을 설정해서 넣으면 되구요. 3번째는 얼마만큼의 주기로 실행될 지 기간을 설정하게 됩니다.
밀리초여서 1000밀리초 * 60초 * 60분 * 24시간 * 7일 하게 되면 저것은 1주일에 한번 실행되게 됩니다.

이상입니다!


출처 : http://mudchobo.tomeii.com/tt/category/자바(Java)?page=5
Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 4. 24. 13:16
반응형

출처 : http://mudchobo.tomeii.com/tt/category/아이바티스(iBATIS)

iBATIS를 이용해서 간단한 예제를 만들어봅시다.

웹이랑 섞으면 복잡하니까 콘솔에서 아주 간단하게 해보겠습니다-_-;

iBATIS를 들어보기만 하고 저처럼 어려울 것 같아서 거부하신분들에게 바칩니다-_-; 저는 처음에 상당히 어려울 줄 알았는데 공식홈페이지가니까 이동국님이 번역해 놓으신 문서가 있더라구요. 약간 이상하게(?) 번역해 놓긴 했지만 영어울렁증이 있는 저에게는 정말 도움이 되는 자료 였습니다 ^^

iBATIS공식홈페이지입니다.( 개발자 가이드와 튜토리얼 메뉴얼도 있습니다.)
for Java를 클릭하면 자바용이 나옵니다.
http://ibatis.apache.org/

접근하기 쉽게 하기 위해서 원더걸스멤버를 DB에 저장해놓고 해봅시다-_-;


CREATE TABLE `WONDERGIRLS` (
  `NUM` int(11) NOT NULL,
  `NAME` varchar(10) NOT NULL,
  `AGE` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=euckr;

INSERT INTO `WONDERGIRLS` (`NUM`, `NAME`, `AGE`) VALUES 
(1, '선예', 18),
(2, '선미', 15),
(3, '소희', 15),
(4, '예은', 18),
(5, '유빈', 99);


사실 원더걸스가 이렇게 어린지 몰랐습니다-_-; 자 우선 넘어가고-_-;

이클립스를 실행하고 JAVA PROJECT로 프로젝트를 하나 만듭시다.
그리고 패키지를 jyp라고 해서 만듭시다-_-;
iBATIS lib파일인 ibatis-2.3.0.677.jar과 OracleJDBC를 LIB으로 추가합시다.

iBATIS를 사용하기 위한 설정파일을 구성해봅시다.
jyp에다가 new해서 xml파일을 하나 만듭시다. 이름은 SqlMapConfig.xml 이라고 합시다.

SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">

<sqlMapConfig>

 <properties resource="./jyp/SqlMapConfig.properties" />
 
 <settings
     cacheModelsEnabled="true"
     enhancementEnabled="true"
     lazyLoadingEnabled="true"
     maxRequests="32"
     maxSessions="10"
     maxTransactions="5"
     useStatementNamespaces="false" 
 />
    
 <transactionManager type="JDBC" >
    <dataSource type="SIMPLE">
      <property name="JDBC.Driver" value="${driver}" />
      <property name="JDBC.ConnectionURL" value="${url}" />
      <property name="JDBC.Username" value="${username}" />
      <property name="JDBC.Password" value="${password}" />
    </dataSource>
 </transactionManager>
 
 <sqlMap resource="./jyp/WonderGirls.xml" />
  
</sqlMapConfig>

내용을 보면 환경을 설정하고 JDBC와 연결하기 위한 작업을 하는 듯 합니다. JDBC연결작업은 SIMPLE이라고 되어있는데 DBCP로도 됩니다. 메뉴얼에 보시면 개발자가이드 부분에 보면 하는 법이 있습니다.
그리고 마지막에 sqlMap의 resource를 WonderGirls.xml을 추가해놨는데요 차후 설명하겠습니다.

그다음에 저기 JDBC설정에 ${driver}등 변수형식으로 되어있는데 맨위에 properties파일을 하나 설정한게 있는데 저기에 정의 하면 됩니다.

SqlMapConfig.properties

driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:XE
username=MUDCHOBO
password=1234

오라클은 저렇게 하면 되구요. mysql은 mysql에 맞게 바꾸면 돼요.
만약 한 프로그램에서 여러개의 db를 접근한다면 저 파일들을 또 만들면 되겠죠?

그 다음 클래스를 하나 만듭시다. MyAppSqlmapConfig라는 클래스를 만듭시다.

package jyp;

import java.io.Reader;

import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;

public class MyAppSqlMapConfig {

 private static final SqlMapClient sqlMap;
 
 static {
  try {
   String resource = "./jyp/SqlMapConfig.xml";
   Reader reader = Resources.getResourceAsReader(resource);
   sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
  } catch (Exception e) {
   e.printStackTrace();
   throw new RuntimeException("Error initializing class. Cause:" + e);
  }
 }

 public static SqlMapClient getSqlMapInstance() {
  return sqlMap;
 }
}

방금 설정한 설정파일을 이용해서 sqlMapClient를 만드는 듯 합니다. 저 sqlMap객체를 통해서 db에 접근할 수 있는 듯 합니다. 저도 잘은 모르겠네요 ^^

원더걸스 getter, setter를 만들어 봅시다. WonderGirls라는 이름으로 클래스를 만듭니다.
WonderGirls.java

package jyp;

public class WonderGirls {

 private int num;
 private String name;
 private int age;
 
 public int getNum() {
  return num;
 }
 public void setNum(int num) {
  this.num = num;
 }
 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;
 }
}

이제 가장 중요한 부분인 Wondergirls에 관련된 sqlMap을 작성해봅시다.
이름은 아까 정의한 WonderGirls.xml파일로 만듭시다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" 
"http://www.ibatis.com/dtd/sql-map-2.dtd">

<sqlMap namespace="WonderGirls">
  
 <resultMap id="WondergirlsResult" class="jyp.WonderGirls">
  <result column="NUM" property="num" />
  <result column="NAME" property="name" />
  <result column="AGE" property="age" />
 </resultMap>
 
 <select id="getWondergirls" resultMap="WondergirlsResult">
  SELECT NUM, NAME, AGE
  FROM WONDERGIRLS
  ORDER BY NUM
 </select>

 <select id="selectWondergirl" parameterClass="Integer" 
             resultClass="jyp.WonderGirls">
  SELECT NUM, NAME, AGE
  FROM WONDERGIRLS
  WHERE NUM = #num#
 </select>
 
 <insert id="insertWondergirl" parameterClass="jyp.WonderGirls">
  INSERT INTO
  WONDERGIRLS (NUM, NAME, AGE)
  VALUES (#num#, #name#, #age#)
 </insert>
 
 <update id="updateWondergirl" parameterClass="java.util.Map">
  UPDATE WONDERGIRLS SET
  NAME = #name#,
  AGE = #age#
  WHERE NUM = #num# 
 </update>
 
 <delete id="deleteWondergirl" parameterClass="Integer">
  DELETE WONDERGIRLS
  WHERE NUM = #num#
 </delete>
 
</sqlMap>

sql문을 xml로 뺐습니다. 나중에 고칠 때 편할 듯 싶네요.
sql이 총 5가지가 있네요.
전체리스트가져오기, 멤버번호받아서 한명멤버정보 가져오기, 멤버추가하기, 멤버수정하기, 멤버삭제하기

select해서 List를 가져올 때에는 원래 자동맵핑이 되서 알아서 가져오지만 그래도 명시적으로 맵핑을 해줘야돼요. 그래서 resultMap태그를 이용해서 명시해준뒤에 select를 선언해서 그 resultMap의 id를 resultMap속성값에 넣어주면 됩니다.
insert할 때에도 WonderGirls클래스형으로 받아서 넣어주면 되구요.
update할 때 좀 틀린게 parameterClass를 Map으로 선언합니다. 이름, 나이, 번호를 각각 객체로 받아서 Map에 저장한 것을 받아서 사용하는 것이죠.
delete할 때에는 Integer클래스형으로 숫자하나 받아서 해당 멤버를 지우게 됩니다.

이번엔 DAO를 만들어봅시다. WondergirlsDao라는 이름의 클래스를 만들어봅시다.

package jyp;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import com.ibatis.sqlmap.client.SqlMapClient;

public class WondergirlsDao {

 @SuppressWarnings("unchecked")
 public void viewMember(SqlMapClient sqlMap) throws SQLException {
  List<WonderGirls> wonderGirlsList = (List<WonderGirls>) sqlMap
    .queryForList("getWondergirls");
  for (int i = 0; i < wonderGirlsList.size(); i++) {
   WonderGirls wonderGirl = (WonderGirls) wonderGirlsList.get(i);
   System.out.print("번호 : " + wonderGirl.getNum());
   System.out.print("   이름 : " + wonderGirl.getName());
   System.out.println("   나이 : " + wonderGirl.getAge());
  }
 }

 public void insertMember(SqlMapClient sqlMap, WonderGirls wonderGirls) 
    throws SQLException {  
  sqlMap.insert("insertWondergirl", wonderGirls);
  System.out.println("추가에 성공했습니다.");
 }

 public void modifyMember(SqlMapClient sqlMap, Map<String, Object> map) 
    throws SQLException {
  sqlMap.update("updateWondergirl", map);
  System.out.println("수정에 성공했습니다.");
 }

 public void deleteMember(SqlMapClient sqlMap, int num) throws IOException,
   SQLException {
  sqlMap.delete("deleteWondergirl", num);
  System.out.println("삭제에 성공했습니다.");
 }
 
 public boolean validateMember(SqlMapClient sqlMap, int num) throws SQLException {
  WonderGirls wonderGirls = 
   (WonderGirls) sqlMap.queryForObject("selectWondergirl", num);
  
  if (wonderGirls == null) {
   return false;
  }
  return true;
 }
}

DAO를 보면 이상하게 간단합니다. 위에 xml에 다 쿼리를 선언해줬기때문에 그냥 파라메터만 넘겨주면 됩니다.
List보기는 sqlMap.queryForList("getWondergirls"); getWondergirls는 위에 선언한 쿼리의 id입니다.
insert할 때에는 객체를 받아서 넘겨주고, 수정할 때에는 Map을 넘겨주고, 삭제 할때에는 숫자를 넘겨주면 됩니다.
아 간단해요. 한줄이면 끝나요-_-;
예전에 Connection을 close()안해줬더니 서버가 다운되려고 한 경험이 있는데 이런일은 절대 없을 것 같군요.

이제 main함수를 작성해봅시다. 이름은 WondergirlsApplication으로 해봅시다.
public static void main(String[] args)에 체크해주시는 것 잊지 마시구요-_-;
WondergirlsApplication.java

package jyp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import com.ibatis.sqlmap.client.SqlMapClient;

public class WondergirlsApplication {

 public static void main(String[] args) throws SQLException, IOException {

  BufferedReader bufferedReader = new BufferedReader(
    new InputStreamReader(System.in));
  SqlMapClient sqlMap = MyAppSqlMapConfig.getSqlMapInstance();
  WondergirlsDao wondergirlsDao = new WondergirlsDao();
  WonderGirls wonderGirls = new WonderGirls();
  
  Integer age, num;
  String name;
  
  while (true) {
   try {
    System.out.println("\n1.멤버출력\n2.멤버추가\n3.멤버수정\n4.멤버삭제\n5.종료");
    System.out.print("--> ");
    String temp = bufferedReader.readLine();
    int MenuNum = Integer.parseInt(temp);
    switch (MenuNum) {
    case 1:
     wondergirlsDao.viewMember(sqlMap);
     break;
     
    case 2:
     wonderGirls = new WonderGirls();
     System.out.print("번호을 입력하세요 : ");
     wonderGirls.setNum(Integer.parseInt(bufferedReader.readLine()));
     System.out.print("이름을 입력하세요 : ");
     wonderGirls.setName(bufferedReader.readLine());
     System.out.print("나이을 입력하세요 : ");
     wonderGirls.setAge(Integer.parseInt(bufferedReader.readLine()));
     wondergirlsDao.insertMember(sqlMap, wonderGirls);
     break;
     
    case 3:
     Map<String, Object> map = new HashMap<String, Object>(3);

     System.out.print("수정할 번호을 입력하세요 : ");
     num = new Integer(Integer.parseInt(bufferedReader.readLine()));
     if (!wondergirlsDao.validateMember(sqlMap, num)) {
      System.out.println("없는 멤버번호입니다.");
      break;
     }
     System.out.print("이름을 입력하세요 : ");
     name = bufferedReader.readLine();
     System.out.print("나이을 입력하세요 : ");
     age = new Integer(Integer.parseInt(bufferedReader.readLine()));

     map.put("num", num);
     map.put("name", name);
     map.put("age", age);
     wondergirlsDao.modifyMember(sqlMap, map);
     break;
     
    case 4:
     System.out.print("삭제할 멤버번호를 입력하세요 : ");
     num = Integer.parseInt(bufferedReader.readLine());
     if (!wondergirlsDao.validateMember(sqlMap, num)) {
      System.out.println("없는 멤버번호입니다.");
      break;
     }
     wondergirlsDao.deleteMember(sqlMap, num);
     break;
     
    case 5:
     System.exit(0);
     
    default:
     System.out.println("1~5중에서 선택하세요!");
     break;
    }
   } catch (NumberFormatException e) {
    System.out.println("잘못 입력했습니다. 다시 입력하세요");
   }
  }
 }
}

자세히 보시면 뭐 별거없는데 거창하게 만들어놨군요(제가 좀 허접해서-_-) 아 참고로 예외처리 잘 안해놨으니 알아서 버그많아요 알아서 보세요-_-;

1번은 멤버 리스트를 가져오는군요.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 5

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 

2번은 멤버를 추가하는데 추가해봅시다-_-;

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 2
번호을 입력하세요 : 6
이름을 입력하세요 : 성종천
나이을 입력하세요 : 25
추가에 성공했습니다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 5
번호 : 6   이름 : 성종천   나이 : 25

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 

아 멤버가 추가되었어요-_-; (이거 욕먹겠군요-_-;)
멤버를 수정해봅시다. 유빈양의 나이가 잘못 되었으니 수정해봅시다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 3
수정할 번호을 입력하세요 : 5
이름을 입력하세요 : 유빈
나이을 입력하세요 : 20
수정에 성공했습니다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 20
번호 : 6   이름 : 성종천   나이 : 25

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 

아.... 유빈양이 제일 마지막에 들어왔는데 나이가 제일 많군요-_-;
그리고 이제 말도 안되는 성종천이라는 멤버를 지워봅시다-_-;

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 4
삭제할 멤버번호를 입력하세요 : 6
삭제에 성공했습니다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 20

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 

아.....잘 되는군요-_-; 난 왜 이짓을 하고 있는거지-_-;
Posted by 1010
반응형

The Calendar widget is a JavaScript-based calendar that can either be embedded within a page or popup when a trigger element is clicked. It is based very loosely on the Dynarch Calendar, only in the sense that it was used as a base, but has been almost entirely re-implemented.

Like CalendarView? Try my JavaScript list selection tool, MatrixView!

Features

  • Developed with CSS in Mind
    In CalendarView you will find no embedded style or presentation. You are encouraged to make it look how you want it to look so that it looks like it belongs in your project.
  • Embedded or Popup — You Decide!
    CalendarView can be either embedded into your page or used as a pop-up. The choice is yours!
  • Lightweight and Easy to Use
    Frustrated with the complexity and bloatedness of existing calendars, CalendarView was implemented as a lightweight alternative.
  • Utilizes the Prototype Framework
    CalendarView uses the Prototype JavaScript framework, lessening the overall JavaScript impact if Prototype is already being used in your project.

Examples

Embedded Calendar

April 2009
« Today »
S M T W T F S
29 30 31 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 1 2

2009-04-08

Popup Calendar

2009-03-08

Download

CalendarView requires Prototype 1.6.0 (or later).

You may also access the source code at GitHub.

Release History

Version 1.1 — October 19th, 2008

  • Upgraded to Prototype 1.6 and cleaned up code to take full advantage of its new API features.
  • Removed our dependency on Builder from script.aculo.us, as Prototype 1.6 has its own DOM Builder now
  • Fixed a bug where navigating through months of the calendar would display the wrong year when you reached December. Thanks to Dirk Koritnik for the fix and to everyone who reported the issue.

Version 1.0 — March 12th, 2007

  • Initial release

Development Roadmap

  • Add support for assigning an HTML ID and CSS classes to the Calendar at time of creation
  • Reuse Calendar objects for Popup Calendars instead of creating new objects
  • Cleanup, extract, or remove the Date object extensions

Support CalendarView Development

CalendarView is developed in the author's spare time. If you find it to be useful within your web application, please consider making a small donation to support and encourage future development.

Usage Instructions

Options

Option Description
dateField An HTML element (or DOM ID) that will be updated when a date is selected. Can be an INPUT field or any other JavaScript object that has either innerHTML or value attributes. The value of this field will also be parsed for setting the current date when the Calendar is initialized.
triggerElement An HTML element (or DOM ID) that will be observed for clicks to display a popup calendar. If a triggerElement is not specified, the dateField will be observed instead.
parentElement An HTML element (or DOM ID) that will receive the initialized embedded calendar.
selectHandler JavaScript function that will be called when a date is selected. Only define this if you want to override the default behavior.
closeHandler JavaScript function that will be called when the calendar should be closed (either after a selection has been made or if the user clicked outside of the calendar's container element). This only applies to popup calendars and should only be defined if you want to override the default behavior.

Setting up an Embedded Calendar

Embedded calendars require a parent element so that it can be appended to the DOM, such as a div element.

Embedded Calendar Example

<html>
  <head>
    ...
    <script type="text/javascript" src="prototype.js"></script>
    <script type="text/javascript" src="calendarview.js"></script>
    <script type="text/javascript">
      window.onload = function() {
        Calendar.setup({
          dateField     : 'date',
          parentElement : 'calendar'
        })
      }
    </script>
    ...
  </head>
  <body>
    ...
    <div id="calendar"></div>
    <div id="date">Select Date</div>
    ...
  </body>
</html>

Setting up a Popup Calendar

Popup calendars require a trigger element that will display the calendar when clicked. By default, the element defined as the dateField will trigger the calendar if a triggerElement has not been specified.

Popup Calendar Example

<html>
  <head>
    ...
    <script type="text/javascript" src="prototype.js"></script>
    <script type="text/javascript" src="calendarview.js"></script>
    <script type="text/javascript">
      window.onload = function() {
        Calendar.setup({
          dateField      : 'date',
          triggerElement : 'calendarButton'
        })
      }
    </script>
    ...
  </head>
  <body>
    ...
    <input type="text" name="date" id="date" />
    <input type="button" id="calendarButton" value="Show Calendar" />
    ...
  </body>
</html>

Styling the Calendar

The calendar is meant to be styled entirely with CSS. A few CSS classes are declared in the HTML output to assist in styling, but for the most part it should be styled with standard CSS element selectors.

Example HTML Output

<div class="calendar popup">
  <table>
    <thead>
      <tr>
        <td class="title" colspan="7">March 2007</td>
      </tr>
      <tr>
        <td class="button">«</td>
        <td class="button">‹</td>
        <td class="button" colspan="3">Today</td>
        <td class="button">›</td>
        <td class="button">»</td>
      </tr>
      <tr>
        <th class="weekend">S</th>
        <th>M</th>
        <th>T</th>
        <th>W</th>
        <th>T</th>
        <th>F</th>
        <th class="weekend">S</th>
      </tr>
    </thead>
    <tbody>
      <tr class="days">
        <td class="otherDay weekend">25</td>
        <td class="otherDay">26</td>
        <td class="otherDay">27</td>
        <td class="otherDay">28</td>
        <td>1</td>
        <td>2</td>
        <td class=" weekend">3</td>
      </tr>
      <tr class="days">
        <td class="weekend">4</td>
        <td>5</td>
        <td class="selected">6</td>
        <td>7</td>
        <td>8</td>
        <td>9</td>
        <td class="weekend">10</td>
      </tr>
      <tr class="days">
        <td class="weekend">11</td>
        <td class="today">12</td>
        <td>13</td>
        <td>14</td>
        <td>15</td>
        <td>16</td>
        <td class="weekend">17</td>
      </tr>
      <tr class="days">
        <td class="weekend">18</td>
        <td>19</td>
        <td>20</td>
        <td>21</td>
        <td>22</td>
        <td>23</td>
        <td class="weekend">24</td>
      </tr>
      <tr class="days">
        <td class="weekend">25</td>
        <td>26</td>
        <td>27</td>
        <td>28</td>
        <td>29</td>
        <td>30</td>
        <td class="weekend">31</td>
      </tr>
    </tbody>
  </table>
</div>
Posted by 1010
02.Oracle/DataBase2009. 4. 24. 12:19
02.Oracle/DataBase2009. 4. 24. 11:36
반응형
출처 : http://mudchobo.tomeii.com/tt/279?category=12

저는 전에 데이터를 SELECT해와서 데이터가 0개면 INSERT하고, 있는 놈이면 해당 값을 업데이트 하는 식으로 했었는데요. 우연히 MERGE INTO라는 것을 알게 되었네요.

ORACLE에서만 되는 듯 하네요-_-; 9i이상에서만 된다고 하네요.

MERGE INTO의 목적은 어떤 테이블이나 뷰테이블을 해당 목표테이블과 합체(MERGE)하기 위한 목적인데요. 이걸 이용해서 데이터가 들어왔을 때 있는 데이터면 UPDATE하고, 없는 데이터면 INSERT하는 형태로도 쓰일 수 있습니다.

http://radiocom.kunsan.ac.kr/lecture/oracle/sql/merge.html
위에는 MERGE INTO를 잘 설명한 사이트네요.

여기서 조금 응용하면 원하는 대로 구현할 수 있습니다-_-;

MERGE INTO 테이블명  별칭
USING 대상테이블/뷰  별칭
ON 조인조건
WHEN MATCHED THEN
  UPDATE SET
   컬럼1=값1
   컬럼2=값2
WHEN NOT MATCHED THEN
  INSERT (컬럼1,컬럼2,...)
       VALUES(값1,값2,...);

MERGE INTO다음에 나오는 테이블명은 실제로 데이터가 들어가거나 업데이트 되는 테이블이구요.
USING 다음에 나오는 테이블명은 실제 데이터를 가져오거나 할 테이블이구요.
ON은 WHERE과 같은 조건문이죠.
WHEN MATCHED THEN은 매치되는게 있으면 UPDATE하라는 얘기구요.
WHEN NOT MATCHED THEN은 매치되는게 없으면 INSERT하게 되죠.

응용해봅시다.

           MERGE INTO INSERTTABLE
           USING DUAL
           ON (ID = 1)
           WHEN MATCHED THEN
           UPDATE SET
           DATA = 'idoori'
           WHEN NOT MATCHED THEN
           INSERT (ID, DATA)
           VALUES (1, 'mudchobo')

INSERTTABLE이라는 곳에 USING은 DUAL이라고 했는데 DUAL은 dual은 1개의 레코드 만을 갖는 dummy 테이블이라고 합니다. select 1 from dual해버리면, 1이 나오죠. 대상테이블은 필요없으니 dual로 설정합니다.
ON에서 ID = 1은 ID가 1인게 만약 있으면, DATA부분을 idoori로 업데이트하고, 없으면 mudchobo로 넣게 되는겁니다.

아놔 별거 없는데 막 늘어썼네-_-;
Posted by 1010
02.Oracle/DataBase2009. 4. 24. 11:29
반응형
출처 : http://mudchobo.tomeii.com/tt/293?category=12

오라클 10G R2부터 TDE라는 기능을 제공합니다.
(참고로 Enterprise 이상의 버전에서만 됩니다. Oracle XE를 설치했는데 안되서 삽질했었습니다-_-)

말그대로 Transparent(투명한) Database Encryption(데이터베이스 암호화) 입니다-_-;
투명하다는 얘기는 암호화가 투명하게 되었다는 얘기인데요.
암호화가 되어서 DATABASE에 저장이 되지만, 암호화가 됐는지 확인 하는 방법은 며느리도 모릅니다.
해당 계정의 사용자는 데이터를 다 볼 수 있어야하니깐요 ^^
데이터베이스를 도난당했을 때 해당계정의 비밀번호를 모르는 이상 데이터베이스를 확인 할 수 없는 것 같습니다.
즉, SQL Injection같이 Application 단에서 발생하는 해킹은....소용없다는 얘기죠.

장점이 있다면 어플리케이션을 변경하지 않아도, 데이터베이스를 암호화 할 수 있습니다.

어쨌든 암호화가 필요해서 체험해봤습니다.
우선 ORACLE 10g이 필요하죠.

전자지갑을 생성해야합니다.
cd $ORACLE_HOME/network/admin
sqlnet.ora파일을 편집해서 아래와 같은 내용을 넣습니다.
ENCRYPTION_WALLET_LOCATION=
  (SOURCE=(METHOD=FILE)(METHOD_DATA=
   (DIRECTORY=/export/home/oracle/oracle/product/10.2.0/db_2/)))

전자지갑 저장소를 설정하는 듯하네요.

이제 오라클에 접속해봅시다.
-bash-3.00$ sqlplus /nolog

SQL*Plus: Release 10.2.0.2.0 - Production on Wed Jul 16 16:57:08 2008

Copyright (c) 1982, 2005, Oracle.  All Rights Reserved.

SQL> connect / as sysdba
Connected.
SQL> alter system set key identified by "welcome1";

System altered.

SQL>

이게 마스터키를 생성하는 부분인데요. 마스터키는 단 한번만 생성되어야 한다고 합니다. 다시 생성하게 되면 기존에 암호화 되어있던 데이터를 다시 암호화 해야한다는군요. 무슨 얘기지-_-;

저거대로 따라할라니까 힘들어서-_-; 그냥 insert해서 로그에 안찍히는 것만 보여주도록 해봅시다-_-;
Oracle LogMiner라는 놈을 이용해서 로그를 볼 수 있는데 이놈은 암호화된 데이터를 지원하지 않습니다. 그래서 암호화된 데이터는 보여지지가 않습니다.

테이블을 생성해봅시다.

connect oe/oe
create table cust_payment_info 
(first_name varchar2(11),
last_name varchar2(10),
order_number number(5),
credit_card_number varchar2(16) ENCRYPT NO SALT,
active_card varchar2(3));

Table created.
자세히 보면 credit_card_number 부분에만 ENCRYPT NO SALT라는 것을 적용했네요.
이렇게 해버리면 테이블 구조만 바꾸면서 암호화를 할 수 있습니다. 이미 만들어진 어플리케이션은 손대지 않아도 됩니다. 8i에서 제공하는 방법이 찾아보니까 있었는데 그건 키테이블을 따로 만들어서 데이터를 암호화시킨 상태에서 저장해버립니다. 나중에 데이터를 가져올 때 복호화 하고 그런식으로 하더라구요. 그거나 이거나 암호화해서 저장되는 법은 똑같습니다.

데이터를 insert해봅시다.
예제에는 조낸 많이 삽입하는데 조낸 귀찮으니까 1개만 삽입해봅시다-_-;
SQL> insert into cust_payment_info values
  2    ('Jon', 'Oldfield', 10001, '5446959708812985','YES');

1 row created.

SQL>

또 예제에서는 뭔가 삽질을 하는데 귀찮으니까 바로 로그확인에 들어갑시다-_-;
oradata에 있는 redo01~3으로 설정해주어야합니다.
SQL> connect / as sysdba;
Connected.
SQL> alter database add supplemental log data;

Database altered.

SQL> REM select member as LOG_FILE_LOCATION from v$logfile;
SQL> EXECUTE DBMS_LOGMNR.ADD_LOGFILE ('/export/home/oracle/oradata/orcl/redo03.log', DBMS_LOGMNR.NEW);

PL/SQL procedure successfully completed.

SQL> EXECUTE DBMS_LOGMNR.ADD_LOGFILE ('/export/home/oracle/oradata/orcl/redo02.log', DBMS_LOGMNR.ADDFILE);

PL/SQL procedure successfully completed.

SQL> EXECUTE DBMS_LOGMNR.ADD_LOGFILE ('/export/home/oracle/oradata/orcl/redo01.log', DBMS_LOGMNR.ADDFILE);

PL/SQL procedure successfully completed.

SQL> EXECUTE DBMS_LOGMNR.START_LOGMNR (options => DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG + DBMS_LOGMNR.COMMITTED_DATA_ONLY);

PL/SQL procedure successfully completed.

SQL> select sql_redo from v$logmnr_contents where
  2  table_name = 'CUST_PAYMENT_INFO' and operation='INSERT';

SQL_REDO
--------------------------------------------------------------------------------
insert into "OE"."CUST_PAYMENT_INFO"("FIRST_NAME","LAST_NAME","ORDER_NUMBER","CR
EDIT_CARD_NUMBER","ACTIVE_CARD") values ('Jon','Oldfield','10001',Unsupported Ty
pe,'YES');


SQL>

결과에 암호화된 칼럼은 Unsupported Type이라고 뜨는군요.
잘은 모르지만 암호화가 된 듯 합니다.

암튼 손쉽게 암호화 할 수 있게 해놨군요.
어플리케이션에 손대지 않는 방법을 찾다가 이짓까지 삽질해보는군요.
Posted by 1010
02.Oracle/DataBase2009. 4. 24. 11:25
반응형

ROWNUM의 동작 원리와 활용 방법
저자 - Tom Kyte

오라클 전문가 Tom Kyte가 ROWNUM의 동작 원리와 활용 방법에 대해 설명합니다.

이번 호의 Ask Tom 컬럼은 지금까지와는 조금 다른 내용을 담고 있습니다. 필자는 오라클 데이터베이스에서 Top-N 쿼리와 페이지네이션(pagination) 쿼리를 구현하는 방법에 대해 자주 질문을 받곤 합니다. 하나의 컬럼을 통해 이러한 질문에 한꺼번에 대답하기 위한 방편으로, < Effective Oracle by Design (Oracle Press, 2003)> 의 내용을 인용하기로 했습니다. 컬럼의 포맷에 맞게 책의 내용이 다소 수정되었음을 참고하시기 바랍니다.

결과 셋의 제한

ROWNUM은 오라클 데이터베이스가 제공하는 마술과도 같은 컬럼입니다. 이 때문에 많은 사용자들이 문제를 겪기도 합니다. 하지만 그 원리와 활용 방법을 이해한다면 매우 유용하게 사용할 수 있습니다. 필자는 주로 두 가지 목적으로 ROWNUM을 사용합니다.

  • Top-N 프로세싱: 이 기능은 다른 일부 데이터베이스가 제공하는 LIMIT 구문과 유사합니다.
  • 쿼리 내에서의 페이지네이션(pagination) – 특히 웹과 같은 "stateless" 환경에서 자주 활용됩니다. 필자는 asktom.oracle.com 웹 사이트에서도 이 테크닉을 사용하고 있습니다.

두 가지 활용 방안을 설명하기 전에, 먼저 ROWNUM의 동작 원리에 대해 살펴 보기로 하겠습니다

ROWNUM의 동작 원리

ROWNUM은 쿼리 내에서 사용 가능한 (실제 컬럼이 아닌) 가상 컬럼(pseudocolumn)입니다. ROWNUM에는 숫자 1, 2, 3, 4, ... N의 값이 할당됩니다. 여기서 N 은 ROWNUM과 함께 사용하는 로우의 수를 의미합니다. ROWNUM의 값은 로우에 영구적으로 할당되지 않습니다(이는 사람들이 많이 오해하는 부분이기도 합니다). 테이블의 로우는 숫자와 연계되어 참조될 수 없습니다. 따라서 테이블에서 "row 5"를 요청할 수 있는 방법은 없습니다. "row 5"라는 것은 존재하지 않기 때문입니다.

또 ROWNUM 값이 실제로 할당되는 방법에 대해서도 많은 사람들이 오해를 하고 있습니다. ROWNUM 값은 쿼리의 조건절이 처리되고 난 이후, 그리고 sort, aggregation이 수행되기 이전에 할당됩니다. 또 ROWNUM 값은 할당된 이후에만 증가(increment) 됩니다. 따라서 아래 쿼리는 로우를 반환하지 않습니다.

select * 
  from t 
 where ROWNUM > 1;

첫 번째 로우에 대해 ROWNUM > 1의 조건이 True가 아니기 때문에, ROWNUM은 2로 증가하지 않습니다. 아래와 같은 쿼리를 생각해 봅시다.

select ..., ROWNUM
  from t
 where <where clause>
 group by <columns>
having <having clause>
 order by <columns>;

이 쿼리는 다음과 같은 순서로 처리됩니다.

1. FROM/WHERE 절이 먼저 처리됩니다.
2. ROWNUM이 할당되고 FROM/WHERE 절에서 전달되는 각각의 출력 로우에 대해 증가(increment) 됩니다.
3. SELECT가 적용됩니다.
4. GROUP BY 조건이 적용됩니다.
5. HAVING이 적용됩니다.
6. ORDER BY 조건이 적용됩니다.

따라서 아래와 같은 쿼리는 에러가 발생할 수 밖에 없습니다.

select * 
  from emp 
 where ROWNUM <= 5 
 order by sal desc;

이 쿼리는 가장 높은 연봉을 받는 다섯 명의 직원을 조회하기 위한 Top-N 쿼리로 작성되었습니다. 하지만 실제로 쿼리는 5 개의 레코드를 랜덤하게(조회되는 순서대로) 반환하고 salary를 기준으로 정렬합니다. 이 쿼리를 위해서 사용되는 가상코드(pseudocode)가 아래와 같습니다.

ROWNUM = 1
for x in 
( select * from emp )
loop
    exit when NOT(ROWNUM <= 5)
    OUTPUT record to temp
    ROWNUM = ROWNUM+1
end loop
SORT TEMP

위에서 볼 수 있듯 처음의 5 개 레코드를 가져 온후 바로 sorting이 수행됩니다. 쿼리에서 "WHERE ROWNUM = 5" 또는 "WHERE ROWNUM > 5"와 같은 조건은 의미가 없습니다. 이는 ROWNUM 값이 조건자(predicate) 실행 과정에서 로우에 할당되며, 로우가 WHERE 조건에 의해 처리된 이후에만 increment 되기 때문입니다.

올바르게 작성된 쿼리가 아래와 같습니다.

select *
  from  
( select * 
    from emp 
   order by sal desc ) 
 where ROWNUM <= 5;

위 쿼리는 salary를 기준으로 EMP를 내림차순으로 정렬한 후, 상위의 5 개 레코드(Top-5 레코드)를 반환합니다. 아래에서 다시 설명되겠지만, 오라클 데이터베이스가 실제로 전체 결과 셋을 정렬하지 않습니다. (오라클 데이터베이스는 좀 더 지능적인 방식으로 동작합니다.) 하지만 사용자가 얻는 결과는 동일합니다.

ROWNUM을 이용한 Top-N 쿼리 프로세싱

일반적으로 Top-N 쿼리를 실행하는 사용자는 다소 복잡한 쿼리를 실행하고, 그 결과를 정렬한 뒤 상위의 N 개 로우만을 반환하는 방식을 사용합니다. ROWNUM은 Top- N쿼리를 위해 최적화된 기능을 제공합니다. ROWNUM을 사용하면 대량의 결과 셋을 정렬하는 번거로운 과정을 피할 수 있습니다. 먼저 그 개념을 살펴보고 예제를 통해 설명하기로 하겠습니다.

아래와 같은 쿼리가 있다고 가정해 봅시다.

select ... 
  from ... 
 where ... 
 order by columns;

또 이 쿼리가 반환하는 데이터가 수천 개, 수십만 개, 또는 그 이상에 달한다고 가정해 봅시다. 하지만 사용자가 실제로 관심 있는 것은 상위 N개(Top 10, Top 100)의 값입니다. 이 결과를 얻기 위한 방법에는 두 가지가 있습니다.

  • 클라이언트 애플리케이션에서 쿼리를 실행하고 상위 N 개의 로우만을 가져오도록 명령
  • • 쿼리를 인라인 뷰(inline view)로 활용하고, ROWNUM을 이용하여 결과 셋을 제한 (예: SELECT * FROM (your_query_here) WHERE ROWNUM <= N)

두 번째 접근법은 첫 번째에 비해 월등한 장점을 제공합니다. 그 이유는 두 가지입니다. 첫 번째로, ROWNUM을 사용하면 클라이언트의 부담이 줄어듭니다. 데이터베이스에서 제한된 결과 값만을 전송하기 때문입니다. 두 번째로, 데이터베이스에서 최적화된 프로세싱 방법을 이용하여 Top N 로우를 산출할 수 있습니다. Top-N 쿼리를 실행함으로써, 사용자는 데이터베이스에 추가적인 정보를 전달하게 됩니다. 그 정보란 바로 "나는N 개의 로우에만 관심이 있고, 나머지에 대해서는 관심이 없다"는 메시지입니다. 이제, 정렬(sorting) 작업이 데이터베이스 서버에서 어떤 원리로 실행되는지 설명을 듣고 나면 그 의미를 이해하실 수 있을 것입니다. 샘플 쿼리에 위에서 설명한 두 가지 접근법을 적용해 보기로 합시다.

select * 
  from t 
 order by unindexed_column;

여기서 T가 1백만 개 이상의 레코드를 저장한 큰 테이블이라고, 그리고 각각의 레코드가 100 바이트 이상으로 구성되어 있다고 가정해 봅시다. 그리고 UNINDEXED_COLUMN은 인덱스가 적용되지 않은 컬럼이라고, 또 사용자는 상위 10 개의 로우에만 관심이 있다고 가정하겠습니다. 오라클 데이터베이스는 아래와 같은 순서로 쿼리를 처리합니다.

1. T에 대해 풀 테이블 스캔을 실행합니다.
2. UNINDEXED_COLUMN을 기준으로 T를 정렬합니다. 이 작업은 "full sort"로 진행됩니다.
3. Sort 영역의 메모리가 부족한 경우 임시 익스텐트를 디스크에 스왑하는 작업이 수행됩니다.
4. 임시 익스텐트를 병합하여 상위 10 개의 레코드를 확인합니다.
5.쿼리가 종료되면 임시 익스텐트에 대한 클린업 작업을 수행합니다. .

결과적으로 매우 많은 I/O 작업이 발생합니다. 오라클 데이터베이스가 상위 10 개의 로우를 얻기 위해 전체 테이블을 TEMP 영역으로 복사했을 가능성이 높습니다.

그럼 다음으로, Top-N 쿼리를 오라클 데이터베이스가 개념적으로 어떻게 처리할 수 있는지 살펴 보기로 합시다.

select *
  from 
(select * 
   from t 
  order by unindexed_column)
 where ROWNUM < :N;

오라클 데이터베이스가 위 쿼리를 처리하는 방법이 아래와 같습니다.

1. 앞에서와 마찬가지로 T에 대해 풀-테이블 스캔을 수행합니다(이 과정은 피할 수 없습니다).
2. :N 엘리먼트의 어레이(이 어레이는 메모리에 저장되어 있을 가능성이 높습니다)에서 :N 로우만을 정렬합니다.

상위N 개의 로우는 이 어레이에 정렬된 순서로 입력됩니다. N +1 로우를 가져온 경우, 이 로우를 어레이의 마지막 로우와 비교합니다. 이 로우가 어레이의 N +1 슬롯에 들어가야 하는 것으로 판명되는 경우, 로우는 버려집니다. 그렇지 않은 경우, 로우를 어레이에 추가하여 정렬한 후 기존 로우 중 하나를 삭제합니다. Sort 영역에는 최대 N 개의 로우만이 저장되며, 따라서 1 백만 개의 로우를 정렬하는 대신N 개의 로우만을 정렬하면 됩니다.

이처럼 간단한 개념(어레이의 활용, N개 로우의 정렬)을 이용하여 성능 및 리소스 활용도 면에서 큰 이익을 볼 수 있습니다. (TEMP 공간을 사용하지 않아도 된다는 것을 차치하더라도) 1 백만 개의 로우를 정렬하는 것보다 10 개의 로우를 정렬하는 것이 메모리를 덜 먹는다는 것은 당연합니다.

아래의 테이블 T를 이용하면, 두 가지 접근법이 모두 동일한 결과를 제공하지만 사용되는 리소스는 극적인 차이를 보임을 확인할 수 있습니다.

create table t
as
select dbms_random.value(1,1000000) 
id, 
       rpad('*',40,'*' ) data
  from dual
connect by level <= 100000;

begin
dbms_stats.gather_table_stats
( user, 'T');
end;
/

Now enable tracing, via

exec 
dbms_monitor.session_trace_enable
(waits=>true);

And then run your top-N query with ROWNUM:

select *
  from
(select *
   from t
  order by id)
where rownum <= 10;
 

마지막으로 상위 10 개의 레코드만을 반환하는 쿼리를 실행합니다.

declare
cursor c is
select *
  from t
 order by id;
l_rec c%rowtype;
begin
    open c;
    for i in 1 .. 10
    loop
        fetch c into l_rec;
        exit when c%notfound;
    end loop;
    close c;
end;
/

이 쿼리를 실행한 후, TKPROF를 사용해서 트레이스 결과를 확인할 수 있습니다. 먼저 Top-N 쿼리 수행 후 확인한 트레이스 결과가 Listing 1과 같습니다.

Code Listing 1: ROWNUM을 이용한 Top-N 쿼리

select *
  from
(select *
   from t
  order by id)
where rownum <= 10

call         count     cpu	elapsed   disk     query      current    rows
--------     --------  -------  -------   -------  --------   --------   ------ 
Parse        1         0.00     0.00      0          0        0           0
Execute      1         0.00     0.00      0          0        0           0
Fetch        2         0.04     0.04      0        949        0          10
--------     --------  -------  -------   -------  --------   --------   ------ 
total        4         0.04     0.04      0        949        0          10

Rows                         Row          Source Operation
-----------------            ---------------------------------------------------
10                           COUNT STOPKEY (cr=949 pr=0 pw=0 time=46997 us)
10                           VIEW  (cr=949 pr=0 pw=0 time=46979 us)
10                           SORT ORDER BY STOPKEY (cr=949 pr=0 pw=0 time=46961 us)
100000                       TABLE ACCESS FULL T (cr=949 pr=0 pw=0 time=400066 us)

이 쿼리는 전체 테이블을 읽어 들인 후, SORT ORDER BY STOPKEY 단계를 이용해서 임시 공간에서 사용되는 로우를 10 개로 제한하고 있습니다. 마지막 Row Source Operation 라인을 주목하시기 바랍니다. 쿼리가 949 번의 논리적 I/O를 수행했으며(cr=949), 물리적 읽기/쓰기는 전혀 발생하지 않았고(pr=0, pw=0), 불과 400066 백만 분의 일초 (0.04 초) 밖에 걸리지 않았습니다. 이 결과를 Listing 2의 실행 결과와 비교해 보시기 바랍니다.

Code Listing 2: ROWNUM을 사용하지 않은 쿼리

SELECT * FROM T ORDER BY ID
call         count     cpu	elapsed   disk     query      current    rows
--------     --------  -------  -------   -------  --------   --------   ------ 
Parse         1        0.00     0.00        0        0        0           0
Execute       2        0.00     0.00        0        0        0           0
Fetch        10        0.35     0.40      155      949        6          10
--------     --------  -------  -------   -------  --------   --------   ------ 
total        13        0.36     0.40      155      949        6          10

Rows                         Row          Source Operation
-----------------            ---------------------------------------------------
10                           SORT ORDER BY (cr=949 pr=155 pw=891 time=401610 us)
100000                       TABLE ACCESS FULL T (cr=949 pr=0 pw=0 time=400060 us)

Elapsed times include waiting for the following events:

Event waited on                  Times
------------------------------   ------------
direct path write temp           33
direct path read temp             5

결과가 완전히 다른 것을 확인하실 수 있습니다. "elapsed/CPU time"이 크게 증가했으며, 마지막 Row Source Operation 라인을 보면 그 이유를 이해할 수 있습니다. 정렬 작업은 디스크 상에서 수행되었으며, 물리적 쓰기(physical write) 작업이 "pw=891"회 발생했습니다. 또 다이렉트 경로를 통한 읽기/쓰기 작업이 발생했습니다. (10 개가 아닌) 100,000 개의 레코드가 디스크 상에서 정렬되었으며, 이로 인해 쿼리의 실행 시간과 런타임 리소스가 급증하였습니다.

ROWNUM을 이용한 페이지네이션

필자가 ROWNUM을 가장 즐겨 사용하는 대상이 바로 페이지네이션(pagination)입니다. 필자는 결과 셋의 로우 N 에서 로우 M까지를 가져오기 위해 ROWNUM을 사용합니다. 쿼리의 일반적인 형식이 아래와 같습니다.

select * 
  from ( select /*+ FIRST_ROWS(n) */ 
  a.*, ROWNUM rnum 
      from ( your_query_goes_here, 
      with order by ) a 
      where ROWNUM <= 
      :MAX_ROW_TO_FETCH ) 
where rnum  >= :MIN_ROW_TO_FETCH;

where

여기서,

  • FIRST_ROWS(N)는 옵티마이저에게 "나는 앞부분의 로우에만 관심이 있고, 그 중 N 개를 최대한 빨리 가져오기를 원한다"는 메시지를 전달하는 의미를 갖습니다.
  • :MAX_ROW_TO_FETCH는 결과 셋에서 가져올 마지막 로우로 설정됩니다. 결과 셋에서 50 번째 – 60 번째 로우만을 가져오려 한다면 이 값은 60이 됩니다.
  • :MIN_ROW_TO_FETCH는 결과 셋에서 가져올 첫 번째 로우로 설정됩니다. 결과 셋에서 50 번째 – 60 번째 로우만을 가져오려 한다면 이 값은 50이 됩니다.

이 시나리오는 웹 브라우저를 통해 접속한 사용자가 검색을 마치고 그 결과를 기다리고 있는 상황을 가정하고 있습니다. 따라서 첫 번째 결과 페이지(그리고 이어서 두 번째, 세 번째 결과 페이지)를 최대한 빨리 반환해야 할 것입니다. 쿼리를 자세히 살펴 보면, (처음의 :MAX_ROW_TO_FETCH 로우를 반환하는) Top-N 쿼리가 사용되고 있으며, 따라서 위에서 설명한 최적화된 기능을 이용할 수 있음을 알 수 있습니다. 또 네트워크를 통해 클라이언트가 관심을 갖는 로우만을 반환하며, 조회 대상이 아닌 로우는 네트워크로 전송되지 않습니다.

페이지네이션 쿼리를 사용할 때 주의할 점이 하나 있습니다. ORDER BY 구문은 유니크한 컬럼을 대상으로 적용되어야 합니다. 유니크하지 않은 컬럼 값을 대상으로 정렬을 수행해야 한다면 ORDER BY 조건에 별도의 조건을 추가해 주어야 합니다. 예를 들어 SALARY를 기준으로 100 개의 레코드를 정렬하는 상황에서 100 개의 레코드가 모두 동일한 SALARY 값을 갖는다면, 로우의 수를 20-25 개로 제한하는 것은 의미가 없을 것입니다. 여러 개의 중복된 ID 값을 갖는 작은 테이블을 예로 들어 설명해 보겠습니다.

SQL> create table t
  2  as
  3  select mod(level,5) id, 
     trunc(dbms_random.value(1,100)) data 
  4    from dual
  5  connect by level <= 10000;
Table created.

ID 컬럼을 정렬한 후 148-150 번째 로우, 그리고 148–151 번째 로우를 쿼리해 보겠습니다.

SQL> select *
  2    from
  3  (select a.*, rownum rnum
  4     from
  5  (select id, data
  6     from t
  7   order by id) a
  8   where rownum <= 150
  9  )
 10   where rnum >= 148;

 ID           DATA           RNUM
-------       ----------     -----------
0             38             148
0             64             149
0             53             150

SQL>
SQL> select *
  2    from
  3  (select a.*, rownum rnum
  4     from
  5  (select id, data
  6     from t
  7   order by id) a
  8   where rownum <= 151
  9  )
 10   where rnum >= 148;

 ID           DATA           RNUM
-------       ----------     -----------
0             59             148
0             38             149
0             64             150
0             53             151

로우 148의 경우 DATA=38의 결과가 반환되었습니다. 두 번째 쿼리에서는 DATA=59의 결과가 반환되었습니다. 두 가지 쿼리 모두 올바른 결과를 반환하고 있습니다. 쿼리는 데이터를 ID 기준으로 정렬한 후 앞부분의 147 개 로우를 버린 후 그 다음의 3 개 또는 4 개의 로우를 반환합니다. 하지만 ID에 중복값이 너무 많기 때문에, 쿼리는 항상 동일한 결과를 반환함을 보장할 수 없습니다. 이 문제를 해결하려면 ORDER BY 조건에 유니크한 값을 추가해 주어야 합니다. 위의 경우에는 ROWID를 사용하면 됩니다.

SQL> select *
  2    from
  3  (select a.*, rownum rnum
  4     from
  5  (select id, data
  6     from t
  7   order by id, rowid) a
  8   where rownum <= 150
  9  )
 10   where rnum >= 148;

 ID           DATA           RNUM
-------       ----------     -----------
0             45             148
0             99             149
0             41             150

SQL>
SQL> select *
  2    from
  3  (select a.*, rownum rnum
  4     from
  5  (select id, data
  6     from t
  7   order by id, rowid) a
  8   where rownum <= 151
  9  )
 10   where rnum >= 148;

 ID           DATA           RNUM
-------       ----------     -----------
0             45             148
0             99             149
0             41             150
0             45             151

이제 쿼리를 반복 실행해도 동일한 결과를 보장할 수 있게 되었습니다. ROWID는 테이블 내에서 유니크한 값을 가집니다. 따라서 ORDER BY ID 조건과 ORDER BY ROWID 기준을 함께 사용함으로써 사용자가 기대한 순서대로 페이지네이션 쿼리의 결과를 확인할 수 있습니다.

다음 단계

ASK Tom
오라클 부사장 Tom Kyte가 까다로운 기술적 문제에 대한 답변을 제공해 드립니다. 포럼의 하이라이트 정보를 Tom의 컬럼에서 확인하실 수 있습니다.
asktom.oracle.com

추가 자료:
Expert Oracle Database Architecture: 9i and 10g Programming Techniques and Solutions
Effective Oracle By Design

ROWNUM 개념 정리

지금까지 ROWNUM에 관련하여 아래와 같은 개념을 설명하였습니다.

  • ROWNUM의 할당 원리와 잘못된 쿼리 작성을 피하는 방법
  • ROWNUM이 쿼리 프로세싱에 미치는 영향과 웹 환경의 페이지네이션을 위한 활용 방안
  • ROWNUM을 이용하여 Top N쿼리로 인한 TEMP 공간의 사용을 피하고 쿼리 응답 속도를 개선하는 방법
오라클 오픈월드 행사에 관련하여

이번 오라클 매거진은 오픈월드 특별호로 기획되었습니다. 필자는 이번 행사를 통해 많은 사람들과 직접 대면할 수 있는 기회를 얻게 된 것을 무척 기쁘게 생각하고 있습니다. 행사에 참석하신다면 필자가 진행하는 세션에도 참석해 주실 것을 기대합니다. (필자는 데이터베이스 및 개발에 관련한 세션을 진행할 예정입니다.) 또 OTN이 주최하는 "Meet the Expert" 행사에도 참가할 것입니다. 이미 오래 전부터 오라클 오픈월드 행사에 참여해 왔지만, 일대일 또는 그룹으로 사용자와 만날 수 있다는 것은 언제나 즐거운 경험입니다. 필자가 진행하는 세션과 OTN 이벤트의 스케줄을 한 번 확인해 보시기 바랍니다.

또 필자는 블로그(tkyte.blogspot.com)를 통해 행사 현장의 뉴스와 사진을 제공해 드리고 있습니다. 그 밖에 OTN 에서도 포드캐스트, 동영상, 프리젠테이션 자료와 같은 행사 컨텐트를 다운로드하실 수 있습니다.


Tom Kyte는 1993년부터 오라클에서 일해 왔습니다. 그는 현재 오라클 퍼블릭 섹터 그룹 담당 부사장으로 근무 중이며, Expert Oracle Database Architecture: 9i and 10g Programming Techniques and Solutions (Apress, 2005) , Effective Oracle by Design (Oracle Press, 2003)와 같은 전문서를 집필하였습니다.
Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 4. 24. 11:00
반응형

<sql /> 이라는 Element 를 사용한 것인데 꽤 괜찮은 기능이다..
예제는 다음과 같음.
조금만 살펴 보면 알수 있다.

<sql id="select-order">
 select * from order
</sql>

<sql id="select-count">
 select count(*) as value from order
</sql>

<sql id="where-shipped-after-value">
 <![CDATA[
  where shipDate > #value:DATE#
 ]]>
</sql>

<select
 id="getOrderShippedAfter"
 resultClass="map">
 <include refid="select-order" />
 <include refid="whre-shipped-after-value" />
</select>

<select
 id="getOrderCountShipped-after-value"
 resultClass="int">
 <include refid="select-count" />
 <include refid="whre-shipped-after-value" />
</select>

Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 4. 24. 10:45
반응형
출처 : http://openframework.or.kr/JSPWiki/Wiki.jsp?page=QueryForMapExample

SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE sqlMapConfig      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">

<sqlMapConfig>

  <!-- Configure a built-in transaction manager.  If you're using an
       app server, you probably want to use its transaction manager
       and a managed datasource -->
  <transactionManager type="JDBC" commitRequired="false">
    <dataSource type="SIMPLE">
      <property name="JDBC.Driver" value="com.mysql.jdbc.Driver"/>
      <property name="JDBC.ConnectionURL" value="jdbc:mysql://localhost/ibatis_sample"/>
      <property name="JDBC.Username" value="root"/>
      <property name="JDBC.Password" value="root"/>
    </dataSource>
  </transactionManager>  
  <!-- List the SQL Map XML files. They can be loaded from the
       classpath, as they are here (com.domain.data...) -->
  <sqlMap resource="com/mydomain/data/Account.xml"/>
</sqlMapConfig>

SimpleExample

package com.mydomain.data;

import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
import com.ibatis.common.resources.Resources;
import com.mydomain.domain.Account;

import java.io.Reader;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.sql.SQLException;

public class SimpleExample {

  private static SqlMapClient sqlMapper;

  static {
    try {
      Reader reader = Resources.getResourceAsReader("com/mydomain/data/SqlMapConfig.xml");
      sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);
      reader.close();
    } catch (IOException e) {
      // Fail fast.
      throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
    }
  }
 
............

  public static Map selectAccountMap() throws SQLException {
    return sqlMapper.queryForMap("selectAllAccounts", null, "id");
  }
 
  public static Map selectAccountMapValue() throws SQLException {
    return sqlMapper.queryForMap("selectAllAccounts", null, "id", "emailAddress");
  }
}

Account.java

package com.mydomain.domain;

public class Account {

  private int id;
  private String firstName;
  private String lastName;
  private String emailAddress;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public String getEmailAddress() {
    return emailAddress;
  }

  public void setEmailAddress(String emailAddress) {
    this.emailAddress = emailAddress;
  }

}

Account.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE sqlMap      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap namespace="Account">

  <!-- Use type aliases to avoid typing the full classname every time. -->
  <typeAlias alias="Account" type="com.mydomain.domain.Account"/>

  <!-- Result maps describe the mapping between the columns returned
       from a query, and the class properties.  A result map isn't
       necessary if the columns (or aliases) match to the properties
       exactly. -->
  <resultMap id="AccountResult" class="Account">
    <result property="id" column="ACC_ID"/>
    <result property="firstName" column="ACC_FIRST_NAME"/>
    <result property="lastName" column="ACC_LAST_NAME"/>
    <result property="emailAddress" column="ACC_EMAIL"/>
  </resultMap>

  <!-- Select with no parameters using the result map for Account class. -->
  <select id="selectAllAccounts" resultMap="AccountResult">
    select * from ACCOUNT
  </select>
</sqlMap>

설명

queryForMap() 메소드를 메소드 인자의 형태에 따라 두가지가 제공된다.
- queryForMap(String id, Object parameterObject, String keyProp)
- queryForMap(String id, Object parameterObject, String keyProp, String valueProp)

queryForMap(String id, Object parameterObject, String keyProp)

예제 소스의 SimpleExample에 보면 다음처럼 사용한 경우가 있다.

public static Map selectAccountMap() throws SQLException {
  return sqlMapper.queryForMap("selectAllAccounts", null, "id");
}

여기서 인자를 순서대로 보면 첫번째 인자는 Account.xml에서 "selectAllAccounts" 라는 id를 가리키는 것으로 다음 쿼리문을 실행하도록 지정하는 셈이다.

select * from ACCOUNT

두번째 인자는 parameter객체를 나타내는 것으로 현재 예제에서는 일단 전체 레코를 가져오는 것이라 조회조건을 나타내기 위한 파라미터 객체가 없다. 그래서 null로 지정했다. 조건문을 만들고자 할때는 이 객체를 적절히 셋팅해서 전달하면 된다.

세번째 인자는 queryForMap() 메소드 호출로 인해 반환되는 Map에서 key역할을 담당하는 것으로 여기서 반환되는 객체는 Account라는 객체를 가지는 Map객체이다. 현재 데이터가 다음과 같다면 두개의 Account객체가 넘어오는데 여기서 id가 1인 Account와 2인 Account를 구별하기 위한 Map의 키 역할을 담당할 칼럼을 지정하는 의미라고 보면 이해가 빠를듯 하다. 하지만 여기서 주의할 점은 세번째 인자의 값을 칼럼명이 아닌 해당 칼럼에 대응되는 Account객체의 변수명을 지정해줘야 한다는 것이다.

mysql> select * from account;
+--------+----------------+---------------+------------------+
| ACC_ID | ACC_FIRST_NAME | ACC_LAST_NAME | ACC_EMAIL        |
+--------+----------------+---------------+------------------+
| 1      | DongGuk        | Lee           | fromm0@gmail.com |
| 2      | DongGuk        | Lee           | fromm0@gmail.com |
+--------+----------------+---------------+------------------+
2 rows in set (0.00 sec)

이론적으로는 이렇게 실제 테스트 코드를 돌려보자.

Map map = SimpleExample.selectAccountMap();
System.out.println("# Map 정보 \n"+map);
     
account = (Account) map.get(1);
System.out.println("# id : " + account.getId());
System.out.println("# emailAddress : " + account.getEmailAddress());
System.out.println("# firstName : " + account.getFirstName());
System.out.println("# lastName : " + account.getLastName());

결과는 다음과 같다

# Map 정보
{2=com.mydomain.domain.Account@3eca90, 1=com.mydomain.domain.Account@64dc11}
# id : 1
# emailAddress : fromm0@gmail.com
# firstName : DongGuk
# lastName : Lee

queryForMap(String id, Object parameterObject, String keyProp, String valueProp)

예제 소스의 SimpleExample에 보면 다음처럼 사용한 경우가 있다.

public static Map selectAccountMapValue() throws SQLException {
  return sqlMapper.queryForMap("selectAllAccounts", null, "id", "emailAddress");
}

인자는 네번째를 제외하고는 앞의 예제와 동일하다. 네번째 인자는 Account객체에서 어떤값만 반환받을지는 결정하는 값이라고 보면 된다. 여기서는 selectAccountMapValue() 메소드에서 볼수 있듯이 emailAddress만을 반환받고자 했다.

이론적으로는 이렇게 실제 테스트 코드를 돌려보자.

map = SimpleExample.selectAccountMapValue();
System.out.println("# Map 정보 \n"+map);
String email = (String) map.get(1);      
System.out.println("# 반환값 : " + email);

결과는 다음과 같다.

# Map 정보
{2=fromm0@gmail.com, 1=fromm0@gmail.com}
# 반환값 : fromm0@gmail.com
Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 4. 24. 10:44
반응형

출처 : http://jnylove.tistory.com/249


iBatis
를 해보고자 예제 실습을 따라했다.

# 필요한 파일

1. Member.xml
2. SqlMapConfigExample.properties
3. SqlMapConfigExample.xml
4. Member.java
5. MemberApp.java

# 필요한 것들

1. MySql, Oracle, MSSQL 등등의 DB
2. 1번 DB에 접속할 수 있는 접속 정보(URL, ID, PASSWD, DB Name 등등)



# 실습 순서

1. DB에 접속하여 Member table의 schema를 확인한다
   SQL> desc MEMBER
   (#id#, #passwd#, #name#, #jumin1#, #jumin2#, #email#, #url#, #addr#, #tel_no#, #regdate#)
2. 1번에서 확인한 사항을 바탕으로 DTO(Member.java)를 생성한다.
    private String id;
    ... 중략...
    private String regdate;

    public String getId() {
        return id;
    }
    ...중략...
    public void setId(String id) {
        this.id = id;
    }
    ....중략...
3. SqlMapConfigExample.xml 파일을 만든다.
  <properties resource="SqlMapConfigExample.properties"/>
  <transactionManager type="JDBC" commitRequired="false">
    <dataSource type="SIMPLE">
       <property name="JDBC.Driver" value="${driver}"/>
       <property name="JDBC.ConnectionURL" value="${url}"/>
       <property name="JDBC.Username" value="${username}"/>
       <property name="JDBC.Password" value="${password}"/>
    </dataSource>
  </transactionManager>
  <sqlMap resource="Member.xml"/>
4. SqlMapConfigExample.properties 파일을 생성한다.
   driver=com.mysql.jdbc.Driver
   url=jdbc:mysql://localhost:3306/jsptest
   username=lucky
   password=lucky
5. Member.xml 파일을 생성하여 select, insert, update, delete 를 실행하기 위한 query를 정의한다.
 <select id="getMember" resultClass="Member">
  SELECT ID, PASSWD, NAME, JUMIN1,  ...중략... WHERE ID = #value#
 </select>
 <insert id="insertMember" parameterClass="Member">
  INSERT INTO MEMBER(ID, ...중략...) VALUES(#id#, ...중략...)
 </insert>
 <update id="updateMember" parameterClass="Member">
  UPDATE MEMBER ...중략...
 </update>
 <delete id="deleteMember" parameterClass="Member">
  DELETE ...중략...
 </delete>
6. compile
    $ javac -classpath .;..\..\..\lib\ibatis-2.3.0.677.jar *.java
7. run
    $ java -classpath .;..\..\..\lib\ibatis-2.3.0.677.jar;..\..\..\lib\mysql-connector-java-5.0.4-bin.jar MemberApp


# 주의사항
MemberApp.java 소스 중에

String resource = "SqlMapConfigExample.xml";
Reader reader = Resources.getResourceAsReader(resource);

이런 내용이 있는데, Resources.getResourceAsReader() 이 method가
resource를 classloader 기준으로 찾기 때문에
위의 xml 파일들이 class 파일이 위치한 곳에 같이 있어야 한다.

처음에 eclipse 에서 해보려고, package 만들어서 class 만들고 xml 파일은 다른 폴더에 저장하고 별~~ 삽질을 다 해봤으나 계속 "Could not found resource" 라는 exception만 뱉어냈었다.
eclipse에서의 문제 해결은 못하고, 우선 실행을 해봐야겠기에 전부 한 폴더에 몰아놓고 테스트 했다.











Posted by 1010
02.Oracle/DataBase2009. 4. 24. 10:18
반응형

거의 대부분이 오라클 계정설정이나 dbcp 관련 세팅부분이다..
아이피 주소를 로컬이나 127.0.0.1 이나 아님 고정주소값으로 바꾸어서 해보거나
설정된 계정정보로 sqlplus 에서 테스트해보면 답이 나온다..

Posted by 1010
56. Eclipse Etc.../Eclipse2009. 4. 23. 15:23
반응형
용도
.properties 파일에서 한글을 쉽게 사용할 수 있게 해준다.

설치방법
 - 자동설치

설치순서
Step1.이클립스를 실행시킨다.
Step2."Help > Software Updates > Find and Install..." 을 선택한다.
사용자 삽입 이미지

Step3.Search for new features to install"을 선택한다.
사용자 삽입 이미지


Step4.New Remote Site 를 클릭한 후 아래의 내용을 입력한 후 확인을 클릭한다.
사용자 삽입 이미지
Name: Properties Editor
URL: http://propedit.sourceforge.jp/eclipse/updates/

Step5.위에서 추가한 사이트가 보입니다.(1)
이제 Finish를 클릭하여 다음으로 진행합니다.
사용자 삽입 이미지

Step6. Update Manager가 실행되고 나면 아래의 화면이 표시되면
PropertiesEditorForEclipse3.x 와 viPlugin3.0.x에 체크한 후 Next를 클릭한다.
사용자 삽입 이미지

Step7.라이센스에 동의한 후 Next를 클릭한다.
사용자 삽입 이미지

Step8.설치경로와 기타 정보를 확인한 후 Finish를 클릭한다.
사용자 삽입 이미지

Step9.최종적으로 설치될 플러그인을 확인하고 Install All 을 클릭한다.
사용자 삽입 이미지

Step10.설치가 완료되면 아래의 화면이 표시된다.
사용자 삽입 이미지

확인을 클릭하면 자동으로 재시작한다.

환경설정
이클립스를 실행한 후 "Windows > Preference"를 클릭하면 아래와 같은 화면이 표시된다.
사용자 삽입 이미지
플러그인과 관련되어 새로 생성된 환경설정 메뉴이다.
Duplication Key 메뉴를 클릭한 후 Check a duplication key에 체크해준다.

사용예시
Properties Editor 사용전

Properties Editor 사용전


Properties Editor 사용후

Properties Editor 사용후

Posted by 1010
56. Eclipse Etc.../Eclipse2009. 4. 22. 16:31
반응형

이클립스 실행시 아래 에러문발생시


workspace in use or cannot be created, choose a different one



1. eclips 프로세스를 강제로 종료 시키거나

2. workspace/.metadata 폴더의 .lock 파일 삭제

출처 : Tong - [통]메탈퀸님의 Eclipse통

Posted by 1010