오늘은 어디로 갈까...

2. DAMO 살펴보기 1부 본문

ORM 만들기

2. DAMO 살펴보기 1부

剛宇 2009. 8. 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)
)
;
  



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

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


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>

 



 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>

그리고 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번으로 줄어든것을 알 수 있다.