ORM 만들기2009/08/21 14:43

 일단 DAMO를 만들어보기 앞서서, 어떤 기능을 제공하고 있는지, 실질적으로 살펴보도록 하자.
 여기서는 오라클을 사용하도록 하겠다. 원래는 국내 DBMS의 보급을 위해 큐브리드를 사용하려고 했으나, 큐브리드 티셔츠를 못받은 관계로.... 마음이 삐뚫어져 버렸다. --; JDBC를 지원하기만 한다면 어떠한 DBMS를 사용해도 무방할것이다.

 1) 테스트용 테이블 만들기
  테스트할 테이블을 만들어보도록 하자. 오라클에서 널리 쓰이는 DEPT와 EMP 테이블을 생성한다. 그리고 테스트용 데이터도 알아서 넣어주자. ^^;
CREATE TABLE DEPT 
(
	DEPTNO   NUMBER (2) NOT NULL,
	DNAME    VARCHAR2 (14),
	LOC      VARCHAR2 (13)
)
;

CREATE TABLE EMP 
(
	EMPNO      NUMBER (4) NOT NULL,
	ENAME      VARCHAR2 (10),
	JOB        VARCHAR2 (9),
	MGR        NUMBER (4),
	HIREDATE   DATE,
	SAL        NUMBER (7,2),
	COMM       NUMBER (7,2),
	DEPTNO     NUMBER (2),
	CONSTRAINT FK_DEPTNO FOREIGN KEY (DEPTNO) REFERENCES DEPT (DEPTNO)
)
;
  

table.sql
emp.sql
dept.sql


 2) 자바 프로젝트 생성하기
 본인은 이클립스를 사용하는 관계로, 이클립스 기준으로 설명을 하겠다. 도구가 뭐가 되었던 그 근본원리만 이해하면 상관없기에 잘 헤쳐나가리라 믿어 의심치 않는다.
 일단 테스트할 자바 프로젝트를 만든후 라이브러리 폴더를 생성하고, 필요한 라이브러리들을 추가한다.

 DAMO를 사용하기 위해서는 당연히 kangwoo-damo.jar가 필요하다. 그리고 유릴티티성 kangwoo.util.jar도 꼭 필요하다.
 당연한 것이겠지만 해당 데이터베이스의 JDBC 드라이버도 필요하다. 본인은 오라클을 사용함으로 ojdb14.jar를 사용했다.
 부가적으로 캐시 기능을 이용하기 위해 ehcache-1.6.0.jar도 있어야하지만, 필수 요소는 아니다.
 로그는 log4j를 통해 출력하겠다. 그러므로 log4j.jar로 추가해줘야한다.  따로 지정하지 않는다면, jdk14 logger를 통해 출력될것이다.
 
kangwoo-util.jar
kangwoo-damo.jar
ojdbc14.jar
log4j-1.2.15.jar
ehcache-1.6.0.jar



3) 설정 파일 추가하기
 클래스패스내에 damo.properties 파일을 만들면 된다. 본인은 src 밑에 바로 만들었다.
  여러가지 설정을 할 수 있으나, 현재 시점에서는 큰 의미가 없고 데이터 소스를 정의해주는 부분과 JDBC 연결 정보를 잘 설정해주면 된다. 그리고 log4j를 사용하기로 했으므로, log4j.xml도 만들어주자.

* damo.properties
#SessionProvider
SessionMaxActive=100
SessionMaxWait=5000

#TransactionManager
TransactionMaxActive=100
TransactionMaxWait=5000

#DataSource
#DataSource.Name=DbcpDataSource
DataSource.Name=SimpleDataSource

#JDBC
Jdbc.Driver=oracle.jdbc.driver.OracleDriver
Jdbc.Url=jdbc:oracle:thin:@whatagreat.kangwoo.kr:1521:SID
Jdbc.User=SCOTT
Jdbc.Password=PASSWORD

ConnectionLogManager=kr.kangwoo.damo.support.logging.simple.ConnectionLogManager

* log4j.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
    	<param name="Target" value="System.out" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%d{HH:mm:ss.SSS}] %-5p %C{1}.%M(%F:%L) - %m%n"/>
        </layout>
    </appender>
 
    <!-- Root -->
    <root>
        <level value="INFO" />
        <appender-ref ref="stdout" />
    </root>
    
</log4j:configuration>

 

damo.properties
log4j.xml


 4) EO(EntityObject) 만들기
  데이터베이스의 해당 테이블과 대칭되게 클래스를 만들자. 필드를 선언하고 get/set 메소드를 만들면 된다. 만든 클래스가 DAMO에서 사용되어지기 위해서는 @Table이란 어노테이션을 부여해줘야한다.
 클래스 선언부 위에 @Table(tableName="테이블명") 이런 식으로 작성해주자.

@Table(tableName="EMP")
public class Emp implements Serializable {
}
 필드 선언부에도 어노테이션을 추가해주자. 현재 필요한것은 @PrimaryKey 어노테이션뿐이다. 이 어노테이션을 말 그대로 해당 필드(컬럼)이 Primary Key 역할을 한다고 정의해주는것이다. 꼭 데이터베이스 테이블의 제약속성과 일치할 필요는 없고, DAMO가 필요한 SQL을 생성해낼때 참조하게 된다.
@Table(tableName="EMP")
public class Emp implements Serializable {

    @PrimaryKey
    private Short empno;
}
 기본적으로는 필드명이 컬럼명으로 사용된다. 즉, "deptno"(클래스) <-> "DEPTNO"(테이블)가 되는것이다. 지금 예제에서는 나타나지 않지만 테이블의 컬럼명이 "DEPT_NO" 라면, 클래스의 필드명은 deptNo로 된다. 그리고 필드명을 컬럼명으로 사용하고 싶지 않으면, @Column(name="컬럼명") 어노테이션을 사용하여 따로 정의해줄 수 있다
* Emp.java
package damo.t01;

import java.io.Serializable;
import java.util.Date;

import kr.kangwoo.damo.annotation.PrimaryKey;
import kr.kangwoo.damo.annotation.Table;
import kr.kangwoo.util.ObjectUtils;

@Table(tableName="EMP")
public class Emp implements Serializable {

    @PrimaryKey
    private Short empno;
    private String ename;
    private String job;
    private Short mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Short deptno;

    public Short getEmpno() {
        return this.empno;
    }

    public void setEmpno(Short empno) {
        this.empno = empno;
    }

    public String getEname() {
        return this.ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public String getJob() {
        return this.job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Short getMgr() {
        return this.mgr;
    }

    public void setMgr(Short mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return this.hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Double getSal() {
        return this.sal;
    }

    public void setSal(Double sal) {
        this.sal = sal;
    }

    public Double getComm() {
        return this.comm;
    }

    public void setComm(Double comm) {
        this.comm = comm;
    }

    public Short getDeptno() {
        return this.deptno;
    }

    public void setDeptno(Short deptno) {
        this.deptno = deptno;
    }


    @Override
    public String toString() {
        return ObjectUtils.reflectionToString(this);
    }

}

* Dept.java
package damo.t01;

import java.io.Serializable;

import kr.kangwoo.damo.annotation.PrimaryKey;
import kr.kangwoo.damo.annotation.Table;
import kr.kangwoo.util.ObjectUtils;


@Table(tableName="DEPT")
public class Dept implements Serializable {

	@PrimaryKey
    private Short deptno;
    private String dname;
    private String loc;


    public Short getDeptno() {
        return this.deptno;
    }

    public void setDeptno(Short deptno) {
        this.deptno = deptno;
    }

    public String getDname() {
        return this.dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public String getLoc() {
        return this.loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }

    public String toString() {
        return ObjectUtils.reflectionToString(this);
    }

}

 5) EO(EntityObject)를 이용한 INSERT/UPDATE/DELETE/SELECT 해보기
 DAMO의 모든 기능을 관리하는것은 PersistenceManager이다. 이 PersistenceManager를 생성하면 앞에 설정한 damo.properties를 읽어와서 초기화작업을 하고, 세션 생성, 트랜잭션 관리등 여러가지 작업들을 도와준다.
PersistenceManager pm = PersistenceManager.getPersistenceManager();
 getPersistenceManager() 메소드를 사용해서 PersistenceManager를 가져오기만 하면, INSERT/UPDATE/DELETE/SELECT를 할 모든 준비가 끝난것이다.
 - 등록하기
		// 등록하기
		Emp newEmp = new Emp();
		newEmp.setEmpno((short)1001);
		newEmp.setEname("바보");
		newEmp.setDeptno((short)10);
		newEmp.setJob("개발자 ");
		newEmp.setHiredate(new Date());
		newEmp.setComm(100d);
		
		System.out.println("[1] Insert ");
		int updateCount = pm.insert(newEmp);
		System.out.println(updateCount + " inserted");

 - 수정하기
   수정하기는 두가지 종류가 있다. update와 updateSelective다. 두가지 모두 PK(PrimaryKey) 기반으로 조건절을 생성하는데, 업데이트 되는 대상 컬럼이, update문일 경우에는 EO(EntityObject)에 정의한 모든 필드이고, updateSelective문일 경우는 값이 null이 아닌 필드만 속한다.
		//  수정하기
		System.out.println("[2] UpdateSelective ");
		Emp updateEmp = new Emp();
		updateEmp.setEmpno((short)1001); //  PK
		updateEmp.setJob("창조자");
		int updateCount = pm.updateSelective(updateEmp);
		System.out.println(updateCount + " updated");

 - 삭제하기
   삭제하기도 두가지 종류가 있다. delete와 deleteByPrimaryKey이다. delete는 EO의 null이 아닌 값들로 조건절을 생성하고, deleteByPrimaryKey는 PK 기반으로 조건절을 생성한다.
		//  삭제하기
		System.out.println("[3] Delete ");
		Emp param = new Emp();
		param.setEmpno((short)1001);
		int updateCount = pm.delete(param);
		System.out.println(updateCount + " deleted");

 - 단일 조회하기
   단일 조회하기 두가지 종류가 있다. getObject와 getObjectByPrimaryKey이다. getObject는 EO의 null이 아닌 값들로 조건절을 생성하고, getObjectByPrimaryKey는 PK 기반으로 조건절을 생성한다.
		Emp param = new Emp();
		param.setEmpno(newEmp.getEmpno());
		System.out.println("getObject(" + param + ")");
		Emp insertedEmp = pm.getObject(param);
		System.out.println("Result : " + insertedEmp);
		System.out.println("----------------------------------------------");

  INSERT/UPDATE/DELETE/SELECT 한 클래스 안에서 모두 실행보도록 하겠다.
* Sample01.java
package damo.t01;

import java.util.Date;

import kr.kangwoo.damo.PersistenceManager;

public class Sample01 {

	public static void main(String[] args) throws Exception {
		PersistenceManager pm = PersistenceManager.getPersistenceManager();
		
		// 등록하기
		Emp newEmp = new Emp();
		newEmp.setEmpno((short)1001);
		newEmp.setEname("바보");
		newEmp.setDeptno((short)10);
		newEmp.setJob("개발자 ");
		newEmp.setHiredate(new Date());
		newEmp.setComm(100d);
		
		System.out.println("[1] Insert ");
		int updateCount = pm.insert(newEmp);
		System.out.println(updateCount + " inserted");
		
		Emp param = new Emp();
		param.setEmpno(newEmp.getEmpno());
		System.out.println("getObject(" + param + ")");
		Emp insertedEmp = pm.getObject(param);
		System.out.println("Result : " + insertedEmp);
		System.out.println("----------------------------------------------");
		
		//  수정하기
		System.out.println("[2] UpdateSelective ");
		Emp updateEmp = new Emp();
		updateEmp.setEmpno(newEmp.getEmpno()); //  PK
		updateEmp.setJob("창조자");
		updateCount = pm.updateSelective(updateEmp);
		System.out.println(updateCount + " updated");

		System.out.println("getObject(" + param + ")");
		Emp updatedEmp = pm.getObject(param);
		System.out.println("Result : " + updatedEmp);
		System.out.println("----------------------------------------------");
		
		//  삭제하기
		System.out.println("[3] Delete ");
		updateCount = pm.delete(param);
		System.out.println(updateCount + " deleted");

		System.out.println("getObject(" + param + ")");
		Emp deletedEmp = pm.getObject(param);
		System.out.println("Result : " + deletedEmp);
		System.out.println("----------------------------------------------");
		
	}
}

- 실행결과
[13:33:37.750] INFO  DamoConfig.(DamoConfig.java:99) - Damo 설정파일(/D:/jaru/workspace/damo/bin/damo.properties)을 사용합니다.
[13:33:38.031] INFO  DamoConfig.init(DamoConfig.java:292) - SimeDataSource가 성공적으로 초기화 되었습니다. (kr.kangwoo.damo.SimpleDataSource@cdedfd)
[1] Insert 
1 inserted
getObject([empno=1001, ename=null, job=null, mgr=null, hiredate=null, sal=null, comm=null, deptno=null])
Result : [empno=1001, ename=바보, job=개발자 , mgr=null, hiredate=2009/08/21 13:33:38, sal=null, comm=100.0, deptno=10]
----------------------------------------------
[2] UpdateSelective 
1 updated
getObject([empno=1001, ename=null, job=null, mgr=null, hiredate=null, sal=null, comm=null, deptno=null])
Result : [empno=1001, ename=바보, job=창조자, mgr=null, hiredate=2009/08/21 13:33:38, sal=null, comm=100.0, deptno=10]
----------------------------------------------
[3] Delete 
1 deleted
getObject([empno=1001, ename=null, job=null, mgr=null, hiredate=null, sal=null, comm=null, deptno=null])
Result : null
----------------------------------------------

  6) 만들어진 SQL 출력해보기
  DAMO는 대상 EO를 분석하여 SQL 문장을 생성한 후 실행하도록 되어있다. 앞에서 실행해본 INSERT/UPDATE/DELETE/SELECT 문이 실제적으로 어떻게 만들어졌는지 궁금할것이다.
실행되어진 SQL문장을 출력해주려면 log4j.xml에 java.sql 로그의 출력 레벨을 DEBUG로 수정해주면 된다. log4j.xml에 다음을 추가해주자.
    
        
        
    

 Sample01을 다시 실행해보자. 그러면 아래처럼 SQL문이 출력될것이다.
- 실행 결과
[13:43:59.796] INFO  DamoConfig.(DamoConfig.java:99) - Damo 설정파일(/D:/jaru/workspace/damo/bin/damo.properties)을 사용합니다.
[13:44:00.015] INFO  DamoConfig.init(DamoConfig.java:292) - SimeDataSource가 성공적으로 초기화 되었습니다. (kr.kangwoo.damo.SimpleDataSource@cdedfd)
[1] Insert 
[13:44:00.406] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@cbf30e
/* damo.t01.Emp.Insert generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
INSERT INTO EMP(EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO) VALUES (1001, '바보', '개발자 ', null, 2009-08-21 13:44:00.015, null, 100.0, 10)
[13:44:00.421] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@cbf30e Elapsed Time : 15ms
[13:44:00.421] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@1b8e059
[13:44:00.421] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@1b8e059
1 inserted
getObject([empno=1001, ename=null, job=null, mgr=null, hiredate=null, sal=null, comm=null, deptno=null])
[13:44:00.578] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@1c695a6
/* damo.t01.Emp.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO FROM EMP WHERE EMPNO = 1001
[13:44:00.609] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@1c695a6 Elapsed Time : 31ms
[13:44:00.640] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@1d009b4
[13:44:00.640] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@1d009b4
Result : [empno=1001, ename=바보, job=개발자 , mgr=null, hiredate=2009/08/21 13:44:00, sal=null, comm=100.0, deptno=10]
----------------------------------------------
[2] UpdateSelective 
[13:44:00.718] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@140c281
/* damo.t01.Emp.UpdateSelectvice generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
UPDATE EMP SET JOB = '창조자' WHERE EMPNO = 1001
[13:44:00.718] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@140c281 Elapsed Time : 0ms
[13:44:00.718] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@c24c0
[13:44:00.718] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@c24c0
1 updated
getObject([empno=1001, ename=null, job=null, mgr=null, hiredate=null, sal=null, comm=null, deptno=null])
[13:44:00.796] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@1df280b
/* damo.t01.Emp.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO FROM EMP WHERE EMPNO = 1001
[13:44:00.796] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@1df280b Elapsed Time : 0ms
[13:44:00.796] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@a1d1f4
[13:44:00.812] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@a1d1f4
Result : [empno=1001, ename=바보, job=창조자, mgr=null, hiredate=2009/08/21 13:44:00, sal=null, comm=100.0, deptno=10]
----------------------------------------------
[3] Delete 
[13:44:00.875] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@157aa53
/* damo.t01.Emp.Delete generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
DELETE FROM EMP WHERE EMPNO = 1001
[13:44:00.890] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@157aa53 Elapsed Time : 15ms
[13:44:00.890] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@13adc56
[13:44:00.890] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@13adc56
1 deleted
getObject([empno=1001, ename=null, job=null, mgr=null, hiredate=null, sal=null, comm=null, deptno=null])
[13:44:00.968] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@187814
/* damo.t01.Emp.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO FROM EMP WHERE EMPNO = 1001
[13:44:00.968] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@187814 Elapsed Time : 0ms
[13:44:00.968] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@6f50a8
[13:44:00.968] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@6f50a8
Result : null
----------------------------------------------
이 SQL 출력 부분은 사용자가 자기 입맛데로 구현할 수 있도록 분리되어 있다. 현재는 아래처럼 SQL문과 파라메터들을 같이 출력한다. 취향에 따라서는 따로 분리된 형태가 더 좋아 보일 수도 있는데, 어떤것을 기본형식으로 할 지는 아직 고민중이라서 언제 바뀔지는 모르겠다.
 가) 결합된 형태
SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO FROM EMP WHERE EMPNO = 1001
나) 분리된 형태 SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO FROM EMP WHERE EMPNO = ? [1] 1001

 7) 간단한 Join
  EMP 테이블에는 DEPTNO란 컬럼이 있다. 이 DEPTNO를 통해 DEPT 테이블에 있는 부서명(DNAME)을 가져올 수있다. 일반적으로는 SQL문에서 JOIN을 걸어서 사용하는데, 현재는 EO를 사용중이기에, @JoinColumn 어노테이션을 이용해 보도록하자.
 일단 테스트 데이터를 입력한 후 한 개의 대상을 조회해 보자. 여기서는 EMPNO가 7369인 대상을 조회하도록 하겠다.
package damo.t01;

import kr.kangwoo.damo.PersistenceManager;

public class Sample02 {

	public static void main(String[] args) throws Exception {
		PersistenceManager pm = PersistenceManager.getPersistenceManager();
		
		Emp param = new Emp();
		param.setEmpno((short)7369);
		System.out.println("getObject(" + param + ")");
		Emp insertedEmp = pm.getObject(param);
		System.out.println("Result : " + insertedEmp);
		System.out.println("----------------------------------------------");
		
	}
}

 - 실행결과
[14:13:15.125] INFO  DamoConfig.(DamoConfig.java:99) - Damo 설정파일(/D:/jaru/workspace/damo/bin/damo.properties)을 사용합니다.
[14:13:15.343] INFO  DamoConfig.init(DamoConfig.java:292) - SimeDataSource가 성공적으로 초기화 되었습니다. (kr.kangwoo.damo.SimpleDataSource@cdedfd)
getObject([empno=7369, ename=null, job=null, mgr=null, hiredate=null, sal=null, comm=null, deptno=null])
[14:13:15.843] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@193722c
/* damo.t01.Emp.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO FROM EMP WHERE EMPNO = 7369
[14:13:15.875] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@193722c Elapsed Time : 32ms
[14:13:15.906] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@64f6cd
[14:13:15.906] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@64f6cd
Result : [empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=1980/12/17 00:00:00, sal=800.0, comm=null, deptno=20]


 Emp.java 에 Dept를 자동으로 가져오기 위해서 @JoinColumn을 추가해 보도록 하자
@Table(tableName="EMP")
public class Emp implements Serializable {
    ... 생략 ...
    @JoinColumn(keys={"deptno"}, useCache=false)
    private Dept dept;

	public Dept getDept() {
		return dept;
	}

	public void setDept(Dept dept) {
		this.dept = dept;
	}
}

 다시 Sample02를 실행해보자
[14:13:31.015] INFO  DamoConfig.(DamoConfig.java:99) - Damo 설정파일(/D:/jaru/workspace/damo/bin/damo.properties)을 사용합니다.
[14:13:31.234] INFO  DamoConfig.init(DamoConfig.java:292) - SimeDataSource가 성공적으로 초기화 되었습니다. (kr.kangwoo.damo.SimpleDataSource@cdedfd)
getObject([empno=7369, ename=null, job=null, mgr=null, hiredate=null, sal=null, comm=null, deptno=null, dept=null])
[14:13:31.718] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@1ea0252
/* damo.t01.Emp.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO FROM EMP WHERE EMPNO = 7369
[14:13:31.750] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@1ea0252 Elapsed Time : 32ms
[14:13:31.796] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e
/* damo.t01.Dept.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE DEPTNO = 20
[14:13:31.796] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e Elapsed Time : 0ms
[14:13:31.796] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@95c083
[14:13:31.796] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@95c083
Result : [empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=1980/12/17 00:00:00, sal=800.0, comm=null, deptno=20, dept=[deptno=20, dname=RESEARCH, loc=DALLAS]]
----------------------------------------------

 부서(dept) 정보도 함께 가져오는것을 알 수 있다. 이게 상당히 편하기는 하지만 한가지 문제점이 있다. 흔히 말하는 1:n의 문제라는 것이다. 로그를 보면 알 수 있겠지만, 여기서 SQL 실행이 2번 되었다. 1개의 행(row)을 가져올 경우는 그리 크게 문제가 되지 않을 수도 있지만, 다수의 행 즉, 목록을 가져와버리면, 조회된 EMP 숫자만큼 DEPT 테이블에 쿼리를 날리게 되는것이다.
 부서번호(deptno)가 "20"인 직원(emp)들을 가져와보자.
package damo.t01;

import java.util.List;

import kr.kangwoo.damo.PersistenceManager;

public class Sample03 {

	public static void main(String[] args) throws Exception {
		PersistenceManager pm = PersistenceManager.getPersistenceManager();
		
		Emp param = new Emp();
		param.setDeptno((short)20);
		System.out.println("getList(" + param + ")");
		List empList = pm.getList(param);
		System.out.println("Result : " + empList.size());
		for (Emp emp : empList) {
			System.out.println(emp);
		}
		System.out.println("----------------------------------------------");
		
	}
}

- 실행 결과
[14:17:57.453] INFO  DamoConfig.(DamoConfig.java:99) - Damo 설정파일(/D:/jaru/workspace/damo/bin/damo.properties)을 사용합니다.
[14:17:57.671] INFO  DamoConfig.init(DamoConfig.java:292) - SimeDataSource가 성공적으로 초기화 되었습니다. (kr.kangwoo.damo.SimpleDataSource@cdedfd)
getList([empno=null, ename=null, job=null, mgr=null, hiredate=null, sal=null, comm=null, deptno=20, dept=null])
[14:17:58.171] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@1ea0252
/* damo.t01.Emp.getList generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO FROM EMP WHERE DEPTNO = 20
[14:17:58.234] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@1ea0252 Elapsed Time : 47ms
[14:17:58.265] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e
/* damo.t01.Dept.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE DEPTNO = 20
[14:17:58.281] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e Elapsed Time : 0ms
[14:17:58.281] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e
/* damo.t01.Dept.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE DEPTNO = 20
[14:17:58.281] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e Elapsed Time : 0ms
[14:17:58.281] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e
/* damo.t01.Dept.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE DEPTNO = 20
[14:17:58.281] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e Elapsed Time : 0ms
[14:17:58.281] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e
/* damo.t01.Dept.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE DEPTNO = 20
[14:17:58.296] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e Elapsed Time : 15ms
[14:17:58.296] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e
/* damo.t01.Dept.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE DEPTNO = 20
[14:17:58.296] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@9ced8e Elapsed Time : 0ms
[14:17:58.296] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@95c083
[14:17:58.296] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@95c083
Result : 5
[empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=1980/12/17 00:00:00, sal=800.0, comm=null, deptno=20, dept=[deptno=20, dname=RESEARCH, loc=DALLAS]]
[empno=7566, ename=JONES, job=MANAGER, mgr=7839, hiredate=1981/04/02 00:00:00, sal=2975.0, comm=null, deptno=20, dept=[deptno=20, dname=RESEARCH, loc=DALLAS]]
[empno=7788, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=1987/04/19 00:00:00, sal=3000.0, comm=null, deptno=20, dept=[deptno=20, dname=RESEARCH, loc=DALLAS]]
[empno=7876, ename=ADAMS, job=CLERK, mgr=7788, hiredate=1987/05/23 00:00:00, sal=1100.0, comm=null, deptno=20, dept=[deptno=20, dname=RESEARCH, loc=DALLAS]]
[empno=7902, ename=FORD, job=ANALYST, mgr=7566, hiredate=1981/12/03 00:00:00, sal=3000.0, comm=null, deptno=20, dept=[deptno=20, dname=RESEARCH, loc=DALLAS]]
----------------------------------------------


 5건의 EMP가 조회되었으면 총 6번의 쿼리가 날아가게 되는것이다. 뭐, DBMS가 성능이 무지 좋고, 데이터량이 적다면 필요한 응답시간을 만족시킬수도 있지만, 상당히 안이한 생각임에 틀림없다. 이 문제점을 근본적으로 해결할 수 있는 방법을 본인의 머리로는 생각해낼 수 없었다. 그냥 join 쿼리를 만들어 사용하던지, 대충 묻어가는 수밖에 없는것이다. 혹시 좋은 방법이 있으면 알려주시길 바란다. 한가지 편법은 캐시(cache)를 이용하는것이다. 코드성 테이블이나, DEPT같이 잘 변하지 않는 테이블이라면 캐시에 해당 데이터를 상주시켜서 재사용하면 되는것이다. 그러면 간편함과 성능 두가지를 모두 만족시킬 수 있는것이다. 물론 완벽한 방법은 아니지만 말이다.
 DAMO에서는 자체적인 캐시를 구현하지 않고, 다른 캐시 프레임워크를 사용해서 작동할 수 있는 고리를 제공해주고 있다. 기본적으로는 EHCache를 사용하도록 되어있다. 캐시 설정 파일을 생성하고, 부서(dept) 조회시 캐쉬를 사용하도록 하자
캐시 설정은 damo-cache.xml을 이용해서 할 수 있다. 현재는 EHCache를 사용하므로 그 형식은 EHCache의 형식과 동일하다. 지금은 defaultCache만 정의해 주도록 하겠다.
* damo-cache.xml
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache>
    <diskStore path="java.io.tmpdir"/>

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />

</ehcache>
damo-cache.xml

그리고 Dept.java가 사용할 캐시를 정의해주자.
마지막으로, Emp.java의 useCache=true를 바꿔주자.
@Table(tableName="DEPT")
@Cache(cacheName = "damo.t01.Dept")
public class Dept implements Serializable {
    ... 생략 ...
}
@Table(tableName="EMP")
public class Emp implements Serializable {
    ... 생략 ...
    @JoinColumn(keys={"deptno"}, useCache=true)
    private Dept dept;

	public Dept getDept() {
		return dept;
	}

	public void setDept(Dept dept) {
		this.dept = dept;
	}
}
자, Sample03을 다시 실행보자.
- 실행 결과
[14:32:20.765] INFO  DamoConfig.(DamoConfig.java:99) - Damo 설정파일(/D:/jaru/workspace/damo/bin/damo.properties)을 사용합니다.
[14:32:20.968] INFO  DamoConfig.init(DamoConfig.java:292) - SimeDataSource가 성공적으로 초기화 되었습니다. (kr.kangwoo.damo.SimpleDataSource@1e4457d)
getList([empno=null, ename=null, job=null, mgr=null, hiredate=null, sal=null, comm=null, deptno=20, dept=null])
[14:32:21.421] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@15663a2
/* damo.t01.Emp.getList generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO FROM EMP WHERE DEPTNO = 20
[14:32:21.453] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@15663a2 Elapsed Time : 32ms
[14:32:21.546] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@dc57db
/* damo.t01.Dept.getObject generated by kr.kangwoo.damo.engine.provider.generator.SimpleQueryGenerator */
SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE DEPTNO = 20
[14:32:21.578] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@dc57db Elapsed Time : 32ms
[14:32:21.578] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@1b8e059
[14:32:21.578] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@1b8e059
Result : 5
[empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=1980/12/17 00:00:00, sal=800.0, comm=null, deptno=20, dept=[deptno=20, dname=RESEARCH, loc=DALLAS]]
[empno=7566, ename=JONES, job=MANAGER, mgr=7839, hiredate=1981/04/02 00:00:00, sal=2975.0, comm=null, deptno=20, dept=[deptno=20, dname=RESEARCH, loc=DALLAS]]
[empno=7788, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=1987/04/19 00:00:00, sal=3000.0, comm=null, deptno=20, dept=[deptno=20, dname=RESEARCH, loc=DALLAS]]
[empno=7876, ename=ADAMS, job=CLERK, mgr=7788, hiredate=1987/05/23 00:00:00, sal=1100.0, comm=null, deptno=20, dept=[deptno=20, dname=RESEARCH, loc=DALLAS]]
[empno=7902, ename=FORD, job=ANALYST, mgr=7566, hiredate=1981/12/03 00:00:00, sal=3000.0, comm=null, deptno=20, dept=[deptno=20, dname=RESEARCH, loc=DALLAS]]
----------------------------------------------
로그를 보면 알 수 있듯이, 대상 데이터가 캐시에 존재하면 캐시에서 가져오고, 없으면 데이터베이스에서 가져오는 것을 알 수 있다. 즉 지금 같은 조건에서는 SQL 실행이 6번에서 2번으로 줄어든것을 알 수 있다.
저작자 표시 비영리 변경 금지
Posted by 랑우 剛宇
TAG , ,