일단 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.이 SQL 출력 부분은 사용자가 자기 입맛데로 구현할 수 있도록 분리되어 있다. 현재는 아래처럼 SQL문과 파라메터들을 같이 출력한다. 취향에 따라서는 따로 분리된 형태가 더 좋아 보일 수도 있는데, 어떤것을 기본형식으로 할 지는 아직 고민중이라서 언제 바뀔지는 모르겠다.(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 ----------------------------------------------
가) 결합된 형태 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.로그를 보면 알 수 있듯이, 대상 데이터가 캐시에 존재하면 캐시에서 가져오고, 없으면 데이터베이스에서 가져오는 것을 알 수 있다. 즉 지금 같은 조건에서는 SQL 실행이 6번에서 2번으로 줄어든것을 알 수 있다.(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]] ----------------------------------------------
table.sql