2008년 9월 29일 월요일

Derby 사용하기

가볍고 빠른 DB를 쓸 일이 있었는데 마침 딱 맞을 것 같아서 고른 Derby.

올해 초에 간단하게 써 보긴 했는데, 제대로 알고 쓰려고 하니 공부할 것이 꽤 많았다. 그래서, 열심히 삽질하면서 공부해서 필요한 걸 거의 3주만에 만들었는데, 그동안 공부한 것을 혹시 그냥 잊어 버리지 않을까 하는 마음에 정리도 할겸, 혹시 필요한 분들이 있으면 도움이 될까 해서 이렇게 글을 남겨 본다.

단, 여기서는 Embedded는 배제하고, Network Server를 중심으로 설명한다.


1. Derby

Derby는 관계형 데이터베이스의 하나로 맨 처음 IBMCloudscape라는 이름으로 WebSphere 등에서 사용하다가 Apache에 오픈소스로 공개되면서 Derby라는 이름을 갖게 되었다. 그후 Sun에서 JavaDB라는 이름을 붙여서 배포하게 되었고, JDK 6에 기본으로 포함되어 있다.

무료로 가볍고 간단하게 구성할 수 있으며 안정성이 높다. 다른 오픈소스 DB들에 비해 성능이 좀 떨어지지만 안정성이 우수하.


2. Derby의 연결방식

- Embedded 방식 : 하나의 JVM 내에 어플리케이션과 DB가 함께 존재하도록 구성하는 방식으로 다른 JVM이나 머신에서는 이 DB에 접근이 불가능하다.

- Network Server 방식 : 어플리케이션과 DB가 다른 JVM 또는 프로세스에 존재하도록 구성하는 방식으로 각 인스턴스가 하나의 JVM 내에서 동작하고, 일반적인 JDBC와 똑같은 방식으로 접근이 가능하다.

FISS의 경우, FISS_RULE을 비롯해 많은 커넥터들이 각기 다른 JVM으로 떠서 동작하고, 여기서 접근이 가능해야 하므로 Network Server 방식을 사용한다.

그러므로, 이하는 Network Server 방식을 기반으로 설명한다.

참고로, embedded 방식과 Network Server 방식은 JVM 외부에서의 접근가능여부와 Connection URL에서 IP가 들어가는 것 빼곤 사용방법이 동일하다.


3. Derby 인스턴스 기동

org.apache.derby.drda.NetworkServerControl 클래스를 실행시켜 기동(start), 정지(shutdown), (ping) 등의 작업을 수행할 수 있다.

java $JAVA_OPTION -classpath $CLASSPATH org.apache.derby.drda.NetworkServerControl start -p 54321 -h 192.168.1.123

-p 옵션은 인스턴스가 사용할 포트를 의미하며, 사용하지 않으면 Derbydefault 포트인 1527을 사용한다.

-h 옵션은 인스턴스가 요청을 받을 호스트를 나타내며, 호스트를 설정하면 그 외의 호스트명으로는 접근이 불가능하다. 제한을 두지 않으려면 이 옵션을 사용하지 않는다.


4. DB 연결 및 생성

사용자 계정을 이용한 연결기능도 지원하지만 여기서는 사용하지 않으므로 설명을 생략하고, 계정정보 없이 접속하면 defaultAPP라는 스키마를 사용한다. (계정정보를 이용해 접속하면 그 사용자 ID에 해당하는 스키마를 사용하게 된다.)

Derby는 특별히 DB를 생성하는 명령이 있지 않고, Connection URL의 끝에 “create=true”를 붙여 주었을 때 이 Connection URL에 해당하는 DB가 있으면 그냥 연결만 하고, 해당 DB가 존재하지 않으면 새롭게 생성한다. Connection URL에 “create=true”가 없을 때 해당 DB가 없으면 연결 오류가 발생한다.

DerbyJDBC 방식으로 연결하기 위한 Connection URL은 다음과 같다.


jdbc:derby://{IP}:{Port}/{DB생성디렉토리}/{DB};create=true

- IP : Derby Network Server가 떠 있는 머신의 IP를 나타내는데, 기동시 -h 옵션을 사용했다면 여기에 사용한 호스트명과 똑같이 입력해야 연결이 가능하다.

- Port : Derby Network Server기동할 때 -p 옵션을 사용했다면 여기서 사용한 포트번호를 입력한다. -p 옵션을 사용하지 않았다면 default 포트인 1527을 사용한다.

- DB생성디렉토리 : DB파일이 생성되는 디렉토리를 지정하고 싶으면 여기에 설정한다. , 디렉토리는 루트부터 시작하는 절대경로를 입력해야 하고, 그렇지 않으면 JVM을 실행하는 디렉토리의 하위디렉토리를 생성하고 거기에 DB파일을 생성한다. 루트부터 입력하므로 유닉스는 슬래쉬(/)2번 반복된다. 생략하면 JVM이 실행되는 디렉토리가 default이다.

- DB: DB명도 되고, DB데이터파일이 저장되는 디렉토리명도 된다. DB생성디렉토리를 설정했으면 그 하위에 디렉토리로 생기고, 그렇지 않으면 JVM이 실행된 디렉토리 아래에 생성된다.

- create=true : DB생성디렉토리에 해당 DB명이 있으면 상관없는데, 해당 DB명이 없으면 그 위치에 DB데이터 디렉토리를 자동으로 생성한다. 이 옵션을 사용하지 않으면 해당디렉토리에 해당 DB가 있으면 연결하고, 없으면 오류가 발생한다.


윈도우 예> jdbc:derby://localhost:1527/C:/derby/RULEDB1;create=true

UNIX > jdbc:derby://localhost:1527//usr/derby/RULEDB1;create=true


JDBC 방식의 연결은 DerbyJDBC Client 드라이버인 org.apache.derby.jdbc.ClientDriver를 로딩하고 위의 URL을 이용해서 DriverManager.getConnection을 호출하면 된다.


예제 코드>

Class.forName(org.apache.derby.jdbc.ClientDriver).newInstance();

DriverManager.getConnection(“jdbc:derby://localhost:1527/C:/derby/RULEDB1;create=true”);


그 밖에도 Derby에서 제공하는 DataSource도 이용 가능하다.


참고사항 : 하나의 인스턴스를 통해서 여러 DB에 접근할 수 있다. 하지만, 여러 인스턴스가 동시에 하나의 DB를 사용할 수는 없다. 그러므로, 어떤 DB를 어떤 인스턴스가 사용할지 명확하게 해 두어야 한다.

1527포트의 인스턴스를 통해 ruledb1ruledb2를 접근할 수는 있지만, 1527포트의 인스턴스와 1528포트의 인스턴스가 동시에 ruledb1이라는 DB를 접근할 수는 없다는 뜻이다.

※ Derby에 connection을 만들자 마자 autocommit을 false로 설정하는 것이 좋다. 그렇지 않으면, CUD 뿐만 아니라 R 처리에서도 부하가 가해지면 오류가 잔뜩 떨어지면서 문제가 발생한다. 하지만, autocommit을 false로 해 놓으면 꽤 많은 부하가 가해져도 오류없이 잘 처리해낸다. 단, 이렇게 되면 CUD는 무조건 commit, rollback을 해 줘야 한다.


5. 쿼리 수행

일반 JDBC동일하.


참고사항 : Derby에서 가장 빨리 수행될 수 있는 쿼리는 ‘SELECT 1 FROM SYSIBM.SYSDUMMY1’ 이다. SYSIBM.SYSDUMMY1 테이블은 오라클의 DUAL과 비슷한 기능이다.


- 오늘날짜 : SELECT CURRENT_DATE FROM SYSIBM.SYSDUMMY1 → 2008-09-29

- 현재시간 : SELECT CURRENT_TIME FROM SYSIBM.SYSDUMMY1 → 17:42:06

- 타임스탬프 : SELECT CURRENT_TIMESTAMP FROM SYSIBM.SYSDUMMY1 → 2008-09-29 17:42:13.328

시간이나 날짜를 문자열로 포맷팅하는 함수는 제공하지 않는 것 같다. 아마도 위에서 나오는 문자열을 substring으로 잘라서 쓰거나, 원래의 데이터타입을 이용해 setDategetDate를 이용해야 할 것 같다.


6. ij (Derby용 유틸리티 프로그램) 사용

DerbyNetwork Server로 기동시키면 EclipseData Source Explorer로 접속도 가능하고, Derby와 함께 제공되는 ij라는 유틸리티로도 접근이 가능하다.

- ij 실행

명령프롬프트에서 $DERBY_HOME/binij 를 실행한다.

C:\derby\bin> ij

- DB 연결

connect 명령과 connection URL을 이용해서 DB에 연결한다.

ij> connect 'jdbc:derby://localhost:1527/C:/derby/RULEDB1';

- ij 명령어 보기

ij> help;

- 스키마 목록 보기

ij> show schemas;

- 현재 연결된 DB들에 대한 연결정보 보기

ij> show connections;

- 테이블 리스트 보기

ij> show tables;

- 테이블 구조 보기

ij> describe {테이블명};

- ij 끝내기

ij> exit;

- 그 밖에 모든 DDL, DML 수행 가능하며, 끝은 반드시 세미콜론(;)으로 끝나고 엔터를 쳐야 실행된다.




Derby를 이용해서 프로그램을 다 만든 다음에 용도에 딱 맞는 프로그램을 하나 찾았다.

Memcached(멤캐쉬디)라고 아마도 Memory Cache Daemon의 줄임말이 아닐까 생각이 된다.

웹페이지 로딩을 빠르게 하기 위해 만들어졌다고 본 것 같은데,

키와 Object의 pair로 저장이 되고, 정해진 메모리양을 넘어가면 LRU 방식으로 비워 가면서 정보를 추가한다.

데몬을 띄울 때 메모리의 양을 할당하는 옵션이 있고,

2개 이상의 동일한 내용을 담을 데몬을 띄워 놓을 수 있는데

클라이언트 객체를 만들 때 이들 데몬의 호스트명과 포트명을 한꺼번에 주면

데몬 하나가 죽을 때 다른 살아있는 데몬에 읽고 쓸 수 있기 때문에 장애에 대한 대처도 가능하다.

단, 소스가 C로 작성되어 있어서 실행하려면 OS에 따라 컴파일해서 사용해야 한다.

유명한 OS(윈도우 포함)인 경우는 컴파일해서 올려 놓은 프로그램을 다운받아서 사용해도 된다.

클라이언트는 다양한 언어로 구현된 것이 올라와 있기 때문에 골라서 사용하면 된다.

나는 윈도우용으로 컴파일된 프로그램을 설치하고 띄운 후에,

자바 클라이언트로 접속해서 사용해 봤는데 잘 작동했고 성능도 괜찮았던 것 같았다.

(몇 달 만에 기억을 더듬으며 쓰려니 가물가물하넹.)

참, 이것도 오픈소스이다.

참고로 테스트용으로 사용한 자바 소스파일 첨부했으니 참고하시길...

여러 데몬에 한꺼번에 연결하려면 아래와 같이 고치면 됨.

MemcachedClient c = new MemcachedClient(AddrUtil.getAddresses("server1:11211 server2:11211"));



자세한 내용은 아래의 홈페이지 참조 요망~

www.danga.com/memcached/


자바 클라이언트 download 및 설명

http://code.google.com/p/spymemcached/




2008년 7월 1일 화요일

자바에서 바이트 단위길이로 문자열 자르기

모 이런 거 어딘가 있다는 얘길 들었는데, 내가 만들면 더 잘 할 수 있을 거 같아서 한번 만들어 봤다.
하다가 귀찮아져서 겨우 문제 없이 돌아가는 수준에서 멈췄으니 고쳐 쓸 사람은 얼마든지 고쳐서 쓰시길...

용도는 아무래도 바이트 단위의 길이로 정의된 DB 컬럼에 데이타를 넣을 때,
어쩔 수 없이 잘라서 넣어야 하는 경우가 생기는데 이럴 때 이 메소드 한방으로 해결.
바이트 단위라는 부분에서 냄새를 맡았겠지만, 한글을 비롯한 한글자가 2바이트 이상을 사용할 때에 유용하겠다.
오라클 같은 경우 VARCHAR2 길이가 4000바이트 밖에 안 되다 보니 긴 내용을 넣기에 좀 부족한 느낌이 있는데,
그렇다고 LONG이나 CLOB를 쓰는 건 성능도 안 좋고, 다루기도 불편하니
이런 식으로 잘라서 VARCHAR2 컬럼에 넣는 방법을 종종 사용한다고 한다.

첫번째 메소드는 원하는 바이트수만큼만으로 이루어진 String 리턴
getMaxByteString(str, 400) 을 호출하면 str이라는 문자열에서 앞에서 최대 400바이트만큼 잘라서 문자열으로 만들어 리턴
즉, 리턴한 문자열을 보면 str이 400바이트 이상이면 399바이트 아니면 400바이트이겠고, 그보다 짧으면 통째로 리턴할 것이다.
[code java] // str이라는 문자열의 맨앞부터 최대 maxLen만큼의 바이트수로 이루어진 문자열을 잘라서 리턴 public static StringgetMaxByteString(String str, int maxLen) { StringBuilder sb = new StringBuilder(); int curLen = 0; String curChar; for (int i = 0; i < str.length(); i++) { curChar = str.substring(i, i+1); curLen += curChar.getBytes().length; if (curLen > maxLen) break; else sb.append(curChar); } return sb.toString(); } [/code]
두번 째 메소드는 원하는 바이트수(maxLen)만큼만으로 이루어진 String 배열을 str 문자열 끝까지 처리해서 리턴
getMaxByteStringArray(str, 400) 을 호출하면 str이라는 문자열에서 앞에서 최대 400바이트만큼 잘라서 문자열로 만들고
그 후에 문자열이 또 있으면 또 이어서 최대 400바이트만큼 잘라서 문자열을 만들고. 이걸 문자열 끝까지...
만들어진 문자열을 배열로 만들어서 리턴
별도 테이블을 만들어서 각 문자열을 하나의 로우에 저장하도록 구성한다면 이것도 괜찮을 듯.
[code java] // str이라는 문자열의 맨앞부터 끝까지 최대 maxLen만큼의 바이트수로 이루어진 문자열을 // 연속으로 잘라서 String 배열로 리턴 public static String[]getMaxByteStringArray(String str, int maxLen) { return getMaxByteStringArray(str, maxLen, -1); } [/code]
세번째 메소드는 원하는 바이트수(maxLen)만큼만으로 이루어진 String 배열을 최대 maxArrays만큼 길이로 리턴
getMaxByteStringArray(str, 400, 3) 을 호출하면 str이라는 문자열에서 앞에서 최대 400바이트만큼 잘라서 문자열로 만들고
그 후에 문자열이 또 있으면 또 이어서 최대 400바이트만큼 잘라서 문자열을 만들고. 이걸 문자열을 최대 3번까지...
만들어진 문자열을 배열로 만들어서 리턴
한 테이블의 3개의 컬럼에 문자열을 쪼개서 저장한다면 유용할 듯.
[code java] // str이라는 문자열의 맨앞부터 최대 maxLen만큼의 바이트수로 이루어진 문자열을 // 연속으로 잘라서 최대 maxArrays 길이의 배열로 리턴 public static String[]getMaxByteStringArray(String str, int maxLen, int maxArrays) { StringBuilder sb = new StringBuilder(); ArrayList strList = new ArrayList(); int curLen = 0; String curChar; for (int i = 0; i < str.length(); i++) { curChar = str.substring(i, i+1); curLen += curChar.getBytes().length; if (curLen > maxLen) { if (maxArrays == -1 || strList.size() <= maxArrays-2) { strList.add(sb.toString()); sb = new StringBuilder(); curLen = 0; i--; } else break; } else sb.append(curChar); } strList.add(sb.toString()); String[] strArr = new String[strList.size()]; for (int i = 0; i < strList.size(); i++) { strArr[i] = strList.get(i); } return strArr; } [/code]
아래는 위의 메소드를 호출하는 샘플 코드
이 코드 돌려 보고 싶으면, 위의 메소드들과 아래의 main 메소드 합쳐서 한 클래스에 넣고 돌리면 된다.
[code java] public static void main(String[] args) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append("codePointAt을 사용하면 문자의 유니코드값을 구할 수 있잖아요.. 근데.. 문자의 유니코드값을 다시 문자로 변환해"); } Stringstr = sb.toString(); String newStr = getMaxByteString(str, 4000); System.out.println(newStr + ":" + newStr.getBytes().length); System.out.println("-----------------------------------------"); String[] strArr = getMaxByteStringArray(str, 4000); for (int i = 0; i < strArr.length; i++) { System.out.println(strArr[i] + ":" + strArr[i].getBytes().length); } System.out.println("-----------------------------------------"); String[] strArr2 = getMaxByteStringArray(str, 4000, 3); for (int i = 0; i < strArr2.length; i++) { System.out.println(strArr2[i] + ":" + strArr2[i].getBytes().length); } } [/code]
한 글자씩 잘라 붙이기라서 오래 걸릴 줄 알았는데, 꽤 긴 문자열도 0.1초 이내에 잘라 내니 쓸 만하군...

2008년 3월 7일 금요일

ASM - 자바 바이트코드 분석하기

전부터 자바소스코드를 읽어들여 분석할 수 있는 방법이 없을까 하는 생각을 많이 했었다.

그러려면 거의 자바 컴파일러 수준이 되어야 할 것 같아서 엄두를 못 내다가 얼마전에 자바 바이트코드를 다룰 수 있는 라이브러리가 있다는 사실을 알게 되었다. 대표적인 것이 아파치의 BCEL과 지금 살펴 보려고 하는 ASM이다.

근데 이런 게 있다는 걸 알긴 했는데 도무지 사람들이 관심이 없어서인지 쓸만한 참고자료 찾기가 너무 힘들었다.

결국 ASM 사이트에서 이것저것 파일을 다운받아서 보다보니 내가 딱 원하는 기능의 예제까지 찾아낼 수가 있었다.

 

일단, BCEL(Byte Code Engineering Library)과 ASM은 자바소스파일을 컴파일해서 얻은 클래스파일(바이트코드)을 읽어 들여 변경하거나 분석하는 데 사용하는 라이브러리다. 자바소스파일 같은 경우는 코드상에 문제가 있을 수 있으니까 이걸 분석하는 건 문제가 있을 가능성이 있지만, 클래스파일은 자바소스코드상에 오류가 없어야 컴파일을 통해 만들 수 있는 것이므로 클래스파일을 분석하는 것이 보다 신뢰성 있는 분석결과를 얻을 수 있지 않겠는가? (나는 아직 클래스파일을 동적으로 변경하거나 이런 건 별로 관심이 없다 보니...)

여기저기 자료를 찾다 보니 BCEL보다 ASM이 속도가 빠르다고 해서 나는 ASM을 써 보기로 결정했다.

 

1. 라이브러리 준비

http://asm.objectweb.org/download/index.html에 가서 ASM 관련 파일을 다운받는다.

여기서는 asm관련 모든 패키지가 들어 있는 asm-all-3.1.jar를 사용하는데, 그러려면 asm-3.1-bin.zip을 다운받아서 압축을 풀면 그 안의 lib/all에 이 파일이 있다.

이 파일 외에도 다른 가이드나 예제 같은 것이 많으니 필요에 따라 함께 다운받으면 된다.

 

2. 분석프로그램 생성

※ 이 클래스가 컴파일되려면 컴파일할 때 클래스패스에 1에서 받은 asm-all-3.1.jar를 등록시켜야 한다.


[code java] import java.util.List; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; public class ClassInfo implements Opcodes { public static void main(final String[] args) throws Exception { ClassReader cr = new ClassReader("test.Test"); ClassNode cn = new ClassNode(); cr.accept(cn, ClassReader.SKIP_CODE); System.out.println("Class Name : " + cn.name + "\n"); System.out.println("Super Class : " + cn.superName + "\n"); System.out.println("Interfaces :"); List interfaces = cn.interfaces; for (int i = 0; i < interfaces.size(); i++) { System.out.println(interfaces.get(i)); } System.out.println("\nMethods :"); List methods = cn.methods; for (int i = 0; i < methods.size(); ++i) { MethodNode method = (MethodNode) methods.get(i); System.out.println(method.name + method.desc); } } } [/code]

ClassInfo.java


 

코드설명 :

9: 분석하려는 클래스의 패캐지를 포함한 full-name을 인자로 ClassReader 객체를 생성한다.

    여기서 읽기 위해서는 분석하려는 클래스가 클래스패스 경로 안에 반드시 있어야 한다.

12: 아마 클래스파일을 파싱하는 작업이 아닐까 생각이 된다.(아직 잘 몰라서 그만...)

14: 클래스 이름 출력

16: 클래스가 상속한 클래스 출력

18~22: 클래스가 구현한 인터페이스 리스트 출력

24~29: 클래스 내의 메소드 정보 출력

 

3. 분석 대상 클래스를 생성 및 컴파일

위의 ClassInfo 클래스가 test.Test라는 클래스를 분석하려고 하는데 그러려면 아래와 같이 미리 Test를 만들어서 컴파일해서 클래스파일이 위의 클래스가 실행하는 클래스패스 안에 들어가 있어야 한다.

[code java] package test; import java.io.Serializable; import java.util.HashMap; public class Test extends HashMap implements Serializable { public static String str; static { str = "abced"; } public Test() { super(); } public static void main(String[] args) throws Exception { String str = "abc.dfd.efef.fjskdl"; System.out.println(str.replace(".", "/")); Test test = new Test(); String msg = "message to be printed"; int count = 5; double d = 1.3; float f = 1.3f; long l = 170343702; HashMap map = new HashMap(); test.printMsg(msg, count, d, f, l, map); } public void printMsg(String msg, int count, double d, float f, long l, HashMap map) { for (int i = 0; i < count; i++) System.out.println(msg); } public String getString(int i, String str) { return "abc"; } } [/code]

Test.java


 

4. 분석 실행 및 결과 확인

 

이렇게 한 다음 ClassInfo 클래스를 실행하면 아래와 같은 결과가 나온다.


[code] Class Name : test/Test Super Class : java/util/HashMap Interfaces : java/io/Serializable Methods : <clinit>()V <init>()V main([Ljava/lang/String;)V printMsg(Ljava/lang/String;IDFJLjav getString(ILjava/lang/String;)Ljava [/code]

클래스나 수퍼클래스, 인터페이스는 무슨 내용인지 바로 알겠는데, 메소드 부분은 바로 안 들어온다.

짧은 지식으로 대강 설명을 드리자면,

9: <clinit>는 클래스의 static 블럭. ()는 인자가 없음. V는 void로 리턴타입이 없다는 뜻

10: <init>는 constructor. 나머지는 위와 동일

11: main은 main 메소드.[Ljava/lang/String;에서 [로 시작하니까 배열이고, L로 시작하니까 자바클래스일 것 같고, V니까 void

      다 합치면 void main(String[]) 이런 형태가 되려나?

12: 인자에서 L로 시작하는 String과 HashMap은 감이 오는데, IDFJ는 뭔가?

        Test.java 소스를 보면서 맞추면 I는 int, D는 double, F는 float, J는 long가 될 것 같네.

       primitive type은 바이트코드에서 한글자로 줄여서 표현하는 모양이다.

13: 맨 마지막이 V가 아니고 Ljava/lang/String; 이니까 String을 리턴하는 메소드로군...

 

 

이렇게 해서 간단하게 클래스파일에서 간단한 정보를 뽑아 보는 방법을 알아 보았다.

여기서 Test.java를 클래스패스에 연결이 영 안 되는 분은 ClassInfo.java의 8라인에 "test.Test" 대신 "java.lang.String"을 입력해서 실행해 보면 String 클래스의 정보가 쭉 나오는 것을 확인할 수 있다.

 

클래스파일을 이용한 분석의 좋은 점은 JSP 파일을 분석할 수도 있다는 것이다.

JSP를 실행하려면 웹컨테이너가 java파일로 변환하고, 이걸 다시 클래스파일로 컴파일해서 실행하는데 JSP를 실행하고 나면 웹컨테이너 디렉토리 어딘가에 클래스파일이 남아 있다. 그러니까 이 경로만 찾아서 적당히 클래스패스로 잡아 주면 JSP도 분석이 가능한 것이다.

참고로 톰캣의 경우는 {Tomcat_Root}/work/Catalina/localhost 디렉토리 아래에 생기니 클래스패스 잘 잡아서 테스트해 보는 것도 재미있을 수 있겠다.

 

사실, 이 정도 정보 외에도 메소드 내의 각 코드 라인별로도 분석이 가능하다.

그런데 이 정도까지 가게 되면 거의 어셈블리 언어 수준까지 읽을 수 있는 능력이 필요하다.

(ASM이 무엇의 약자인지 아무리 해도 못 찾겠는데, 내 생각에는 ASM이 어셈블리의 약자가 아닌가 하는 생각도 든다.)

나도 학교 다닐 때 보던 기억을 더듬어 어느정도 읽어내긴 했지만 그런 걸 모르는 사람은 거의 암호 수준일 것 같다.

모 어려운 것도 있고, 호기심이 발동한 분들이 열심히 공부하길 바라는 마음도 있고, 이걸 보면서 머리 속에 굉장한 아이디어가 떠오른 것도 있고 해서 더 이상 심화된 내용은 다루지 않아야겠다.

 

궁금하면 공부합시다!

2008년 1월 17일 목요일

DB 테이블 복사 유틸

간단하게 어떤 DB에 있는 테이블 내용을 다른 DB에 있는 테이블로 그대로 복사하는 유틸입니다.

일단은 오라클만 되게 만들었는데, 테이블의 스키마, 데이터타입, 컬럼길이만 맞는다면 다른 DB도 가능할 것 같네요. 아직 미확인 ^^;

컴파일한 클래스파일과 실행스크립트를 첨부할까 하다가, 간단히 이클립스에서 돌리시라고 그냥 소스를 올립니다.

뭐, 오류 생기면 알아서 고쳐 쓰시라는 의미도 되겠네요. 일단 이건 잘 돌아갔던 소스입니다.

정 못 고치시겠으면 저한테 메일이나 쪽지 남겨 주시면 해결해 드리겠습니다. 이건 유료일지도.. ㅋㅋ

 

 

장점

 

1. 데이터 내용에 작은따옴표(')가 있어도 옮길 수 있다.

   어떤 방법이든 export 받아서 insert문 만들면 작은따옴표 때문에 안 되더군요. 제가 아는 한도 내에서는... ^^

   그런데, 이건 JDBC 방식에 PreparedStatement로 처리해서 insert문을 안 만드니까 문제가 없어요.

   -> 이건 방법이 있었네요. ^^; 작은따옴표를 연속 2개 사용하면 하나인 걸로 인식해서 DB에 들어가네요.

       예를 들어, sometable 테이블의 somecol 컬럼에 123'456 이라는 값을 넣으려면 아래와 같이 하면 돼요.

       insert into sometable(somecol) values ('123''456');

 

2. LONG 타입도 옮길 수 있다.

   LONG 타입은 덤프 뜰 때 안 된다고 하더군요. 이것도 역시 그냥 들은 내용 ^^

   이 프로그램엔 LONG 타입도 옮길 수 있도록 처리해 놨습니다.

   INTEGER와 LONG 타입 외에는 모두 STRING으로 처리하게 만들었으니까 혹시 에러나면 다른 타입 처리코드를 추가해서 사용하세요.

 

3. DB연결정보만 알면 빠르게 옮길 수 있다.

   아래에 설명할 세팅정보만 넣고 실행하면 export, import 과정없이 즉석에서 옮길 수 있어요.

 

 

환경세팅

 

사용자 삽입 이미지

 

1. allColumns : 테이블의 모든 컬럼을 복사할지 설정한다.

true로 하면 모든 컬럼을, false로 하면 아래의 colArray에 설정한 컬럼만 복사한다.

 

2. colArray : 모든 컬럼을 복사하지 않을 경우, 복사할 컬럼을 정의하는 배열

이 값을 적용시키려면 allColumns를 false로 해야 한다.

 

3. source 테이블 연결정보

source로 시작하는 변수들에는 원본 데이터가 있는 테이블에 관한 정보를 입력한다.

 

4. target 테이블 연결정보

target로 시작하는 변수들에는 복사한 데이터를 insert할 테이블에 관한 정보를 입력한다.

 

 

※ 주의사항

source 테이블과 target 테이블의 스키마, 데이타타입, 컬럼길이는 호환가능해야 합니다.

target에는 무조건 insert를 하기 때문에, 원래 있던 데이터는 전혀 건드리지 않습니다.

JDBC 사용하니까 실행할 때 클래스패스에 DB JDBC 드라이버는 당근 포함되어 있어야겠죠.

예전에 VARCHAR2에 2,000 바이트 넘는 데이터 읽고 쓸 때 getString, setString으로 안 됐는데 지금은 되나 모르겠네요.

혹시 이런 케이스로 안 되는 분 있으시면 알려 주세요.

 

마지막으로,

마음껏 배포 가능합니다. 단, 소스 맨위의 주석은 건드리지 말아 주세요.

 

 

편리하게 이용하시길...