오늘은 어디로 갈까...

2. DAMO 살펴보기 2부 본문

ORM 만들기

2. DAMO 살펴보기 2부

剛宇 2009. 8. 22. 16:04
 오늘은 지난 시간에 이어 DAMO의 기능적인 부분을 살펴보도록 하겠다. 지난 시간에는 EO(EntityObject)를 가지고 요리하는 방법을 배웠으니, 오늘은 SQL 맵핑을 이용한 요리방법을 배워보도록 하자.

 8) SQLMap XML 만들기
  damo.t02 패키지를 생성한 다음, emp.xml을 생성한다. 우선 간단한 select 문을 사용해보도록 하겠다.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE queryMap SYSTEM "queryMap.dtd">
<queryMap namespace="damo.t02">

    <select id="countEmp">
        SELECT COUNT(*) FROM EMP
		<dynamic prepend=" WHERE " >
			<if test='${not empty deptno}' prepend=" AND ">
			        DEPTNO = ${deptno}
			</if>
			<if test='${not empty empno}' prepend=" AND ">
				EMPNO = ${empno}
			</if>
		</dynamic>
    </select>
    
    <select id="findEmp">
        SELECT 
        	EMPNO, ENAME, JOB, MGR, HIREDATE, SAL,
        	COMM, DEPTNO
        FROM EMP
		<dynamic prepend=" WHERE " >
			<if test='${not empty deptno}' prepend=" AND ">
			        DEPTNO = ${deptno}
			</if>
			<if test='${not empty empno}' prepend=" AND ">
				EMPNO = ${empno}
			</if>
		</dynamic>
    </select>
    
</queryMap>

  xml 생성이 완료되면, 만들 xml을 DAMO에서 사용가능하도록 damo.properties에 추가해주도록 하자.
#SqlFiles
QueryLoader=kr.kangwoo.damo.engine.provider.loader.file.ReloadableQueryFileLoader
QueryLoader.Files[0]=classpath:/damo/t02/emp.xml


 9) SQLMAP XML 사용하기
 xml에 정의된 SQL들은 ID로 구분된다. xml의 namespace 값 + 각 쿼리의 id가 합쳐져서 고유의 식별자가 되는것이다. 조금전에 만든 xml에는 "damo.t02.countEmp"와 "damo.t02.findEmp" 두가지 쿼리 아이디가 존재하는 것이다.
 그럼 xml에 정의한 SQL을 사용해서 데이터를 조회해보도록 하자.

 * Sample01.java
package damo.t02;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import damo.t01.Emp;

import kr.kangwoo.damo.PersistenceManager;

public class Sample01 {

	public static void main(String[] args) throws Exception {
		PersistenceManager pm = PersistenceManager.getPersistenceManager();
		
		Emp param = new Emp();
		param.setDeptno((short)20);
		
		int count = pm.getCount("damo.t02.countEmp", param, Integer.class);
		System.out.println(count + "건의 데이터가 발견되었습니다.");
		
		List<Emp> list = pm.getList("damo.t02.findEmp", param, Emp.class);
		
		for (Emp e : list) {
			System.out.println(e);
		}

		System.out.println("------------------------------------");
		List<Emp> paginedList = pm.getList("damo.t02.findEmp", param, Emp.class, 2, 2);
		
		for (Emp e : paginedList) {
			System.out.println(e);
		}
		
	}
}



 여기서는 count(쿼리 아이디, 파라메터 객체, 결과 클래스)와, getList(쿼리 아이디, 파라메터 객체, 결과 클래스)를 사용하였다. 그리고 저번 시간에 만들어놓은 Emp 객체를 이용해서 파라메터 값을 넘기고 결과값을 받게 구현하였다.
 실행해보면 다음과 같은 결과를 볼 수 있을것이다.
 - 실행 결과
[14:00:55.890] INFO  DamoConfig.(DamoConfig.java:99) - Damo 설정파일(/D:/jaru/workspace/damo/bin/damo.properties)을 사용합니다.
[14:00:56.062] INFO  DamoConfig.init(DamoConfig.java:292) - SimeDataSource가 성공적으로 초기화 되었습니다. (kr.kangwoo.damo.SimpleDataSource@cdedfd)
[14:00:56.125] INFO  QueryFile.loadQueryMap(QueryFile.java:66) - D:\jaru\workspace\damo\bin\damo\t02\emp.xml 파일 분석을 성공하였습니다.
[14:00:56.500] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@2a15cd
/* damo.t02.countEmp */

        SELECT COUNT(*) FROM EMP
		 WHERE 
			        DEPTNO = 20
			
[14:00:56.531] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@2a15cd Elapsed Time : 31ms
[14:00:56.609] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@1a786c3
[14:00:56.609] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@1a786c3
5건의 데이터가 발견되었습니다.
[14:00:56.687] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@f1bb78
/* damo.t02.findEmp */

        SELECT 
        	EMPNO, ENAME, JOB, MGR, HIREDATE, SAL,
        	COMM, DEPTNO
        FROM EMP
		 WHERE 
			        DEPTNO = 20
			
[14:00:56.718] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@f1bb78 Elapsed Time : 31ms
[14:00:56.718] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@9f671b
[14:00:56.718] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@9f671b
[empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=1980/12/17 00:00:00, sal=800.0, comm=null, deptno=20, dept=null]
[empno=7566, ename=JONES, job=MANAGER, mgr=7839, hiredate=1981/04/02 00:00:00, sal=2975.0, comm=null, deptno=20, dept=null]
[empno=7788, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=1987/04/19 00:00:00, sal=3000.0, comm=null, deptno=20, dept=null]
[empno=7876, ename=ADAMS, job=CLERK, mgr=7788, hiredate=1987/05/23 00:00:00, sal=1100.0, comm=null, deptno=20, dept=null]
[empno=7902, ename=FORD, job=ANALYST, mgr=7566, hiredate=1981/12/03 00:00:00, sal=3000.0, comm=null, deptno=20, dept=null]
------------------------------------
[14:00:56.781] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@1cbfe9d
/* damo.t02.findEmp */

        SELECT 
        	EMPNO, ENAME, JOB, MGR, HIREDATE, SAL,
        	COMM, DEPTNO
        FROM EMP
		 WHERE 
			        DEPTNO = 20
			
[14:00:56.796] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@1cbfe9d Elapsed Time : 15ms
[14:00:56.796] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@1394894
[empno=7788, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=1987/04/19 00:00:00, sal=3000.0, comm=null, deptno=20, dept=null]
[empno=7876, ename=ADAMS, job=CLERK, mgr=7788, hiredate=1987/05/23 00:00:00, sal=1100.0, comm=null, deptno=20, dept=null]


 소스에는 설명하지 않은 getList(쿼리 아이디, 파라메터 객체, 결과 클래스, 페이지번호, 페이지크기) 메소드도 구현되어 있는데, 이것은 페이징처리를 해서 원하는 페이지만 가져오게 하는 기능이다. 페이징 처리하는것은 각 데이터베이스마다 조금씩 다르기 때문에 차후 QueryExecutor를 상속받아 따로 구현해야하고, 기본적으로는 커서를 이용해서 페이징하게 되어있다.

10) ValueObject vs Collection
 개발 할때 끊임없는 토론의 대상이 되는것이 ValueObject 와 Collection이다. 혹자는 전자가 좋다. 다른 사람은 후자가 좋다고 하는데, 정답이 없는게 정답이다. 상황에 따라서 둘다 장단점을 가지고 있기에, 어느것이 더 좋다고 할 수 없는것이다. 그리고 취향에 따라서도 달라지기 때문에 뭐 자신만의 스타일로 밀고 나가는것이 맞을것이다.
 본인은 중간자(?)적 입장을 가지고 있다. 등록/수정/삭제 시에는 VO를 좋아하고 조회시에는 Collection을 좋아한다. 그래서 DAMO가 OR Mapping과 SQL Mapping을 동시에 지원하는지도 모른다. 그러면 앞에 만든 Sample01.java를 Collection으로 변경해보자.
 - Sample02.java
package damo.t02;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import kr.kangwoo.damo.PersistenceManager;

public class Sample02 {

	public static void main(String[] args) throws Exception {
		PersistenceManager pm = PersistenceManager.getPersistenceManager();
		
		Map<String, Object> paramMap = new HashMap<String, Object>();
		paramMap.put("deptno", 20);
		
		int count = pm.getCount("damo.t02.countEmp", paramMap, Integer.class);
		System.out.println(count + "건의 데이터가 발견되었습니다.");
		
		List<LinkedHashMap> list = pm.getList("damo.t02.findEmp", paramMap, LinkedHashMap.class);
		
		for (Map map : list) {
			System.out.println(map);
		}

		System.out.println("------------------------------------");
		List<LinkedHashMap> paginedList = pm.getList("damo.t02.findEmp", paramMap, LinkedHashMap.class, 2, 2);
		
		for (Map map : paginedList) {
			System.out.println(map);
		}
		
	}
}


소스를 보면 알겠지만, 크게 변경된것은 없다. 단지 EO로 구현했던 부분을 Map(Collection)으로 대체한것뿐이다. 여기서는 파라메터객체와 결과클래스에 모드 Collectoin을 사용했지만, EO/Collection, Collection/EO로 얼마든지 사용이 가능하다.

 11) include
  emp.xml을 보면 두 쿼리의 조건절이 동일한것을 알 수 있다. 이럴 경우 조건절만을 분리해서 재 사용하면 좋다.
 아래처렴 만들면 되는것이다.
 * emp02.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE queryMap SYSTEM "queryMap.dtd">
<queryMap namespace="damo.t02">

	<query id="whereClause" xxx="xxx">
		<dynamic prepend=" WHERE " >
			<if test='${not empty deptno}' prepend=" AND ">
			        DEPTNO = ${deptno}
			</if>
			<if test='${not empty empno}' prepend=" AND ">
				EMPNO = ${empno}
			</if>
		</dynamic>
	</query>

    <select id="countEmp2">
        SELECT COUNT(*) FROM EMP
        <include id="whereClause" />
    </select>
    
    <select id="findEmp2">
        SELECT 
        	EMPNO, ENAME, JOB, MGR, HIREDATE, SAL,
        	COMM, DEPTNO
        FROM EMP
        <include id="whereClause" />
    </select>

</queryMap>

 13) SQLMap 살펴보기 
  눈썰미가 좋으신분은 이미 알아차렸겠지만, 파라메터를 선언한 부분이 왠지 모르게 익숙할것이다. 이걸 알아차린다면... 당신은 폐인인것이다. --; 바로 EL(Expression Language)인것이다. JSTL 1.0 스펙이 소개되었든것으로, 간단히 말하면 자바코드를 대신하는 새로운 언어인것이다. JSTL을 많이 사용해보신분 EL의 우수성(?)을 아실것이다. 여기에 EL을 사용한다는것은 어찌보면 "돼지목에 진주목걸이"일 수도 있으나, 가끔 아주 요긴하게 쓰일데가 있다. 그것은 아마 살다보면 경험을 할것이다. 현재 0.4버젼에 포함되어 있는 EL은 간단한 표현식과 기본적으로 제공되어진 function 기능까지 포함하고 있다.
 간단히 예를 들자면, 파라메터에 a, b 두중에서 큰 수를 설정하고 싶을 경우는, ${a > b ? a : b} 이럭식으로 하면 되고, 데이터베이스에서 제공하기는 하지만 trim 같은 것을 사용하고 싶다면 ${fn:trim(str)} 이런식으로 하면 된다. (아마, 0.5버젼에는 사용자 Function을 추가할 수 있는 기능을 넣을거 같기도 하다.)
 그리고 앞에서 본 "if" 태그처럼, "if", "choose/when", "forEach" 태그등을 지원하고 있다. "forEach" '태그는 다음과 같은 경우에 사용하면 아주 유용하다.
 다음 쿼리를 emp.xml에 추가하자.
    <select id="findEmpByNo">
        SELECT 
        	EMPNO, ENAME, JOB, MGR, HIREDATE, SAL,
        	COMM, DEPTNO
        FROM EMP
		<dynamic prepend=" WHERE " >
			<forEach 
				var='empno' items='${empnos}' 
				prepend="EMPNO IN " open="(" close=")" 
				conjunction=", ">${empno}</forEach>
		</dynamic>
    </select>


 * Sample03.java
package damo.t02;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import kr.kangwoo.damo.PersistenceManager;
import damo.t01.Emp;

public class Sample03 {

	public static void main(String[] args) throws Exception {
		PersistenceManager pm = PersistenceManager.getPersistenceManager();
		
		Map<String, Object> paramMap = new HashMap<String, Object>();
		paramMap.put("empnos", new int[] {7369, 7521, 7654, 7900});
		
		List<Emp> list = pm.getList("damo.t02.findEmpByNo", paramMap, Emp.class);
		
		for (Emp emp : list) {
			System.out.println(emp);
		}
		
	}
}



 - 실행 결과
[15:43:47.125] INFO  DamoConfig.(DamoConfig.java:99) - Damo 설정파일(/D:/jaru/workspace/damo/bin/damo.properties)을 사용합니다.
[15:43:47.296] INFO  DamoConfig.init(DamoConfig.java:292) - SimeDataSource가 성공적으로 초기화 되었습니다. (kr.kangwoo.damo.SimpleDataSource@cdedfd)
[15:43:47.359] INFO  QueryFile.loadQueryMap(QueryFile.java:66) - D:\jaru\workspace\damo\bin\damo\t02\emp.xml 파일 분석을 성공하였습니다.
[15:43:47.734] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:106) - execute oracle.jdbc.driver.T4CPreparedStatement@15e9756
/* damo.t02.findEmpByNo */

        SELECT 
        	EMPNO, ENAME, JOB, MGR, HIREDATE, SAL,
        	COMM, DEPTNO
        FROM EMP
		 WHERE EMPNO IN (7369, 7521, 7654, 7900)
[15:43:47.765] DEBUG PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:126) - execute oracle.jdbc.driver.T4CPreparedStatement@15e9756 Elapsed Time : 31ms
[15:43:47.812] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:50) - commit oracle.jdbc.driver.T4CConnection@1922221
[15:43:47.812] DEBUG ConnectionLogProxy.invoke(ConnectionLogProxy.java:54) - close oracle.jdbc.driver.T4CConnection@1922221
[empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=1980/12/17 00:00:00, sal=800.0, comm=null, deptno=20, dept=null]
[empno=7521, ename=WARD, job=SALESMAN, mgr=7698, hiredate=1981/02/22 00:00:00, sal=1250.0, comm=500.0, deptno=30, dept=null]
[empno=7654, ename=MARTIN, job=SALESMAN, mgr=7698, hiredate=1981/09/28 00:00:00, sal=1250.0, comm=1400.0, deptno=30, dept=null]
[empno=7900, ename=JAMES, job=CLERK, mgr=7698, hiredate=1981/12/03 00:00:00, sal=950.0, comm=null, deptno=30, dept=null]



* 아마 실행결과가 엉뚱하게 나오는 분도 있을것이다. 저번에 올려놓은 damo.jar에 버그가 있었다. 새로운걸로 받으시면 잘 되리라 믿는다. ^^;;