2011년 6월 6일 월요일

iBATIS in Action (10/14)

10. iBATIS 데이터 접근 객체 (DAO)

10.1. 상세한 구현 숨기기

객체지향 프로그래밍의 핵심 원리 중 하나는 구현과 public 인터페이스의 분리를 나타내는 캡슐화이다. DAO 패턴은 애플리케이션의 캡슐화를 도와주는 또 하나의 도구이다. DAO 패턴을 더 자세히 알아보기 전에 그림에서 DAO의 한 형태를 보자.

그림 10 1. 간단하게 만든 DAO
앞의 그림을 DAO보다는 JDBC 같다고 생각했다면 절반만 맞다. 이것은 JDBC 이지만 자바의 JDBC API는 DAO 패턴의 훌륭하고 실용적인 예제이기도 하다.
가장 상위에 Connection 인터페이스를 구현한 객체의 인스턴스를 생성하는 팩토리(DataSource 인터페이스)가 있다. 일단 Connection을 획득하면 PreparedStatement나 CallableStatement 객체도 생성할 수 있게 되고, 그 다음으로 ResultSet 객체를 제공받게 된다. DataSource가 커넥션을 생성하는 방법, Connection이 PreparedStatement를 생성하는 방법, PreparedStatement가 쿼리에 파라미터를 바인딩하는 방법, ResultSet이 생성되는 방법 등을 개발자가 알아야 할 필요는 없다. Connection의 prepareStatement() 메서드를 호출하면 PreparedStatement 를 반환 받을 수 있다는 것만 알고 있다면, 그 외의 세세한 사항들은 알 필요가 없다.

10.1.1 왜 분리하는가?

데이터 접근 인터페이스로부터 데이터 접근 구현체를 분리하면, 다양한 데이터에 대해 동일한 인터페이스를 제공할 수 있다. 애플리케이션은 하나의 일관성 있는 데이터 접근 API를 사용하여 내부적으로는 서로 다른 데이터 접근 기법을 사용하는 여러 데이터베이스의 데이터에 접근할 수 있다.
JDBC API는 DAO 패턴의 한 형태이다. JDBC API 영역에서 개발자는 인터페이스들을 사용할 수 있고, 데이터 베이스 벤더들은 상호 교환하여 사용할 수 있도록 이 인터페이스들을 구현한다. 가끔씩 벤더가 생각하기에 인터페이스가 충분한 기능을 제공하지 못한다면, 인터페이스와 상관없이 별도로 구현하기도 한다.
JDBC 드라이버의 구현체를 살펴보면서 JDBC API의 DAO 패턴 덕분에 얼마나 JDBC 를 사용하기가 쉬워졌는지를 확인해 보라. 예를 들면 어떤 대형 데이터베이스 벤더가 그림 10.1의 5개의 인터페이스들의 기능을 구현했는데, 5 개의 JDBC API 인터페이스를 구현하기 전에는 9개 더 많은 (벤더에 종속적인) 인터페이스들로 구성돼 있었다. (이는 실질적인 JDBC 구현 클래스들의 영역 밖에 있는 것은 계산에 포함시키지 않은 것이다.). 사실은 이 책의 저자들 중 한 명이 이 구현체에 대한 UML을 그려서 이 부분에 예제로 넣으려고 했었는데, 이번 장의 내용이 이미 너무 많고 다른 내용들도 추가해야 하기 때문에 하지 않았다. 그렇다. DAO를 사용하지 않으면 그만큼 복잡했었다.
DAO 패턴이 있기 때문에 iBATIS SQL Maps 와 ORM 과 같은 툴이 존재할 수 있다. 모든 데이터베이스 업체들이 JDBC API를 매우 정확하게 구현하기 때문에, 이러한 툴을 작성할 때 내부적인 구현에 신경 쓰지 않고 이러한 인터페이스만 참조하면 된다. 대부분의 경우 이러한 툴은 거의 모든 벤더들이 제공하는 구현체들과 문제없이 작동하며, 개발자는 오직 DataSource 를 생성할 때만 벤더가 제공하는 소프트웨어를 참조하면 된다. 그 이후부터는 공통 API로만 작업해도 된다.
바로 이러한, 즉 데이터에 접근하는 내부적인 상세 구현을 숨기고 인터페이스만을 제공하여 애플리케이션을 작성할 수 있도록 도와 주는 것이 iBATIS DAO의 목표 중의 하나이기도 하다. 따라서 하이버네이트 기반 애플리케이션을 DAO 패턴을 사용하여 작성하였다면, 이를 전체 애플리케이션을 다시 작성할 필요 없이 SQL Maps 가반의 구현이나 혹은 JDBC 기반 구현으로 바꿔 치기 하는 것도 가능하다. 대신 바꿔야 할 부분은 단지 애플리케이션이 사용하는 인터페이스의 구현체뿐이다. DAO 인터페이스를 제대로 구현만 했다면, 이 애플리케이션은 문제없이 작동할 것이다.
DAO는 java.sql 혹은 javax.sql 패키지에 관련된 인터페이스나 객체를 외부로 드러내면 안된다는 규칙을 지켜야 한다. 이는 DAO가 애플리케이션에서 데이터 소스를 통합하는 계층이며, 이 DAO에 접근하는 계층은 저수준의 상세한 구현은 고려하지 않아도 됨을 의미한다. 이는 또한 추가적으로 데이터 접근 메커니즘(예를 들어 SQL Maps, 하이버네이트, JDBC, 기타 등등…)을 변경하는 것이 가능해짐을 의미한다. DAO 패턴을 사용하면 비슷한 방식으로 데이터 소스를 변경할 수 있다. 왜냐하면 인터페이스를 만들 때 데이터 소스 관련 정보를 감추도록 작성하기 때문에 애플리케이션은 데이터가 Oracle이나 PostgreSQL 혹은 SQL을 기반으로 하지 않은 데이터베이스에서 오는지 여부 등은 전혀 알 필요가 없다. 애플리케이션은 단지 자바빈즈만 신경쓰면 된다. 이 빈즈가 어디에서 생성되었는지는 애플리케이션 입장에서는 아무 의미도 없다.
이런 방식의 분리를 통해 얻을 수 있는 추가적인 이점 하나가 더 있는데, 테스트가 훨씬 더 쉬워진다는 점이다. 개발자는 DAO가 사용하는 특정 데이터 접근 방식에 국한된 인터페이스를 사용하지 않고, List나 Map 같은 더 일반적인 객체는 혹은 애플리케이션에 국한된 자바빈즈만 사용해서 작업하기 때문이다.

10.1.2 간단한 예제
iBATIS DAO를 설정하고 사용하는 간단한 예제로 시작해보자. 그 전에 우리가 설정할 DAO를 먼저보자. 이 (JDBC 예제보다는 훨씬 더 단순한) DAO는 한 개의 인터페이스(AccountDao)와 그 구현(AccountDaoImpl) 으로 이뤄져 있다. 다른 두 클래스 중 하나는 인터페이스를 사용하는 것(AccountService)이고 DaoManager 클래스는 iBATIS가 DAO를 생성할 때 사용하는 팩토리 클래스이다. DaoManager 클래스는 dao.xml 설정 파일을 사용하여 설정한다.
- Dao.xml 설정파일
DaoManager는 보통 dao.xml 이라는 이름의 XML 파일로 설정한다. 이 파일에는 iBATIS가 DAO를 어떻게 조직화할지 설정하는 데 필요한 정보가 들어간다.다음은 SQL Maps에 기반한 DAO 계층을 구성하는 XML 설정 파일의 예제이다.

PUBLIC
"-//ibatis.apache.org//DTD DAO Configuration 2.0//EN"
"http://ibatis.apache.org/dtd/dao-2.dtd">



name="SqlMapConfigResource"
value="examples/SqlMapConfig.xml"/>

interface="examples.dao.AccountDao"
implementation="examples.dao.impl.AccountDao"/>


위에서 example 이라는 이름의 DAO 컨텍스트를 생성하고 트랜잭션은 SQL Maps가 관리하도록 설정한다. 그리고 Account 라는 한 개의 DAO 만을 설정했다. 다음절에서 이 설정 파일의 내용을 좀 더 자세히 알아볼 것이다. 그러니 지금은 잘 이해가 안되더라도 괜찮다.

DaoManager 생성하기
다음은 (JDBC의 DataSource 처름 데이터 접근 계층의 시작점 역할을 하는) DaoManager 인스턴스를 생성할 차례이다. 개발자는 DaoManager에서 DAO를 가져온다. DAO 관리자를 구성하는데 약간은 시간이 걸리기 때문에 이 객체를 생성하고 나서 나중에 재사용할수 있는 위치에 저장해두는 것이 좋다. 10.4 절에서 이에 대해 다시 알아볼 예정이고, 지금 당장은 간단한 DaoService라는 클래스를 만들어서 DaoManager 인스턴스를 생성하고 저장하게 하자.
package org.apache.mapper2.examples.chapter10.dao;
import com.ibatis.dao.client.DaoManager;
import com.ibatis.dao.client.DaoManagerBuilder;
import com.ibatis.common.resources.Resources;
import java.io.Reader;
import java.io.IOException;
public class DaoService {
private static DaoManager daoManager;
public static synchronized DaoManager getDaoManager(){
String daoXmlResource = "dao.xml";
Reader reader;
if (null == daoManager){
try {
reader =
Resources.getResourceAsReader(daoXmlResource);
daoManager =
DaoManagerBuilder.buildDaoManager(reader);
return daoManager;
} catch (IOException e) {
throw new RuntimeException(
"Unable to create DAO manager.", e);
}
} else {
return daoManager;
}
}
public static Dao getDao(Class interfaceClass){
return getDaoManager().getDao(interfaceClass);
}
}
나중에 관련사항이 또 나오기 때문에, 지금은 이것을 간략하게만 살펴보자. 첫 번째 중요한 점은 위에서 본 dao.xml 파일의 위치를 가리키는 daoXmlResource 변수이다. 이것이 중요한 이유는 파일 경로가 아니라 클래스패스의 리소스 위치를 의미하기 때문이다. 예를 들어 웹 애플리케이션에서 작동한다면, 이 파일은 WEB-INF/classed 디렉터리에 있을 것이다. Resources 클래스는 그 파일을 클래스 패스에서 찾아서 Reader로 만들어서 DaoManagerBuilder 클래스에 전달한다.
DaoManagerBuilder는 DaoManager 인스턴스를 생성한다. 이제는 다음과 같은 간단한 호출만으로 DAO 객체를 가져올 수 있다.
AccountDao accoutDao = (AccountDao) DaoService.getDao (AccountDao.class);
자, Dao 패턴 사용의 강점에 대한 예제를 보았다면 이제는 이것을 어떻게 사용할지에 대한 의문을 품게 될 것이다. 첫째로 바로 다음에 알아볼 주제인 설정을 해야 한다.

10.2. DAO 설정하기

Dao.xml 파일은 iBATIS DAO 프레임워크를 사용할 때 필요한 유일한 설정 파일이다. 이것은 매우 단순한 파일로 , DAO 클래스의 트랜잭션 관리에 필요한 정보와 개발자가 만든 인터페이스에 맞는 DAO 구현체를 가져오는 방법을 DAO 관리자에게 제공하기 위해 사용한다.
먼저 설정 요소들을 살펴보고 일반적인 문제들을 해결하는 데 이를 어떻게 사용할지 몇 가지 방법을 알아보자.

10.2.1 요소

Properties 요소는 SqlMapConfig.xml 파일에 있는 요소와 같은 방법으로 사용한다. 이 요소에 프로퍼티 파일을 지정하면 그 프로퍼티 파일에 있는 모든 프로퍼티를 DAO 계층 설정에서 ${name} 문법을 통해 사용할 수 있다.
이러한 접근 방식은 개발과 운영(그리고 어쩌면 이관 및 서버 테스팅)을 위해 서버를 분리하는 경우에 매우 유용하다. 이러한 경우, 모든 DAO 클래스를 설정하고서 환경에 따라 다른 항목들만 properties 파일에 둘 수 있다. 그러고 나서 각각의 환경에 배포할 때 properties 파일만 변경하면 된다.

10.2.2 요소

DAO 컨텍스트는 서로 관련된 설정 정보와 DAO 구현체를 묶어주는 역할을 한다.

컨텍스트는 언제나 관계형 데이터베이스나 일반적인 파일과 같은 한 개의 데이터 소스와 엮이게 된다. 다중 컨텍스트를 설정하여, 다중 데이터베이스에 대한 접근 설정을 손쉽게 중앙 집중화할 수도 있다.
10.3 절에서는 서로 다른 데이터 접근 모델, 즉 하나는 SQL Maps를 사용하고 다른 하나는 하이버네이트, 그리고 마지막은 일반적인 JDBC를 사용하여 다중 DAO 그룹 생성하는 컨텍스트 사용 예를 볼 것이다.
각각의 컨텍스트에는 자체적인 트랜잭션 관리자와 DAO 구현체들이 있다. 다음의 두 절에서, 각각의 항목을 설정하는 방법을 살펴볼 것이다.

10.2.3 요소

이름이 암시하는 것처럼, 트랜잭션 관리자는 DAO 클래스의 트랜잭션(7장에서 상세히 다루었다.)을 관리한다. 요소의 type 속성에 사용하고자 하는 DaoTransactionManager 구현체의 이름을 명시한다. iBATIS DAO 프레임워크는 기본적으로 각각 서로 다른 데이터 접근 툴을 지원하는 일곱 가지의 서로 다른 트랜잭션 관리자를 제공해준다. 표에서 살펴보자.
타입 별칭 트랜잭션 관리자/프로퍼티 설명
EXTERNAL ExternalDaoTransactionManager iBATIS DAO 프레임워크 외부에서 개발자가 자체적으로 트랜잭션을 관리하도록 하는 “아무것도 하지 않는 트랜잭션
HIBERNATE HibernateDaoTransactionManager 하이버네이트의 트랜잭션 관리 기능에 위임
JDBC JdbcDaoTransactionManager
* DataSource
* JDBC.Driver
* JDBC.ConnectionURL
* JDBC.Username
* JDBC.Password
* JDBC.DefaultAutoCommit Datasource API 를 통해 커넥션 풀링을 처리하고자 할 때 JDBC 타입을 사용한다. SIMPLE, DBCP 그리고 JNDI 이렇게 세개의 DataSource 구현체가 지원된다.
SIMPLE은 iBATIS의 SimpleDataSource의 구현체로 부하와 의존성이 가장 적은 독립적인 구현체이다.
DBCP는 Jakarta DBCP DataSource 를 사용하는 구현체이다.
마지막으로, JNDI는 DataSource 참조를 JNDI 디렉터리에서 가져오는 구현체이다. 이는 가장 일반적이고 유연한 설정 방식이다. DataSource 설정을 애플리케이션 서버에서 중앙 집중적으로 해줄 수 있기 때문이다.
JTA JtaDaoTransactionManager
* DBJndiContext
* UserTransaction Java Transaction Architecture (JTA) API를 사용하여 트랜잭션을 관리.
DataSource 구현체를 JNDI 를 통해 가져오고 또한 UserTransaction 인스턴스도 JNDI를 통해 접근 가능해야 한다.
OJB OjbBrokerTransactionManager OJB 트랜잭션 관리 기능으로 위임
SQLMAP SqlMapDaoTransactionManager SQL Maps 트랜잭션 관리 기능으로 위임
TOPLINK ToplinkDaoTransactionManager TopLink 트랜잭션 관리 기능으로 위임

이제는 무슨 옵션이 있는지 알았을 것이다. 각각을 좀 더 자세히 살펴보고 무엇 때문에 이들이 필요한지 알아보자.

- EXTERNAL 트랜잭션 관리자
EXTERNAL 트랜잭션 관리자는 설정하기가 가장 쉽지만(프로퍼티가 없으며 전달된 프로퍼티는 무시된다.) 애플리케이션에서 모든 트랜잭션 관리를 스스로 해야 하기 때문에 잠재적으로는 사용하기 가장 어렵다.
- HIBERNATE 트랜잭션 관리자
이 트랜잭션 관리자는 설정이 매우 간단하다. 전달되는 모든 프로퍼티를 가지고 하이버네이트 세션 팩토리에 그 프로퍼티들을 전달한다. 또 “class.”로 시작하는 이름을 가진 프로퍼티는 하이버네이트가 관리하는 클래스가 된다고 가정하고 세션 팩토리를 구축하는 데 사용할 설정 객체에 추가한다. 그리고 “map.”으로 시작하는 이름으로 전달되는 프로퍼티는 매핑 파일로 가정하고 설정 객체에 전달한다.
- JDBC 트랜잭션 관리자
이 트랜잭션 관리자는 아마도 설정하기가 가장 까다로울 것이다. 이 트랜잭션 관리자는 DataSource 프로퍼티가 꼭 있어야 하며 “SIMPLE”, “DBCP”, 또는 “JNDI”중에 하나로 지정해줘야 한다.
- SIMPLE 데이터 소스
최소한의 부하와 의존성을 위한 독립적인 구현체인 SIMPLE 데이터소스는 iBATIS SimpleDataSource의 구현체다. 이것은 5개의 프로퍼티를 필요로 한다.
* JDBC.Driver – 이 DAO 컨텍스트의 트랜잭션을 관리하는 데 사용할 JDBC 드라이버의 완전한 패키지 경로를 포함한 클래스명.
* JDBC.ConnectionURL – 데이터베이스에 접속할 때 사용하는 JDBC URL
*JDBC.Username – 데이터베이스에 접속할 때 사용하는 사용자명
*JDBC.Password – 데이터베이스에 접속할 때 사용하는 비밀번호
*JDBC.DefaultAutoCommit – “true”(또는 자바의 Boolean 클래스가 true로 간주하는 모든 표현식)로 설정한다면, 데이터 소스가 반환하는 커넥션의 autoCommit 프로퍼티도 DAO 컨텍스트에서 true로 설정될 것이다.
이러한 필수 프로퍼티에 추가적으로, 커넥션 풀을 설정하기 위한 여덟개의 선택적인 프로퍼티가 있다.
* Pool.MaximumActiveConnections – 커넥션 풀이 동시에 활성화(active)할 수 있는 커넥션의 개수. 디폴트 값은 10개의 커넥션이다.
* Pool.MaximumIdleConnections – 커넥션 풀이 동시에 유휴상태(idle)로 둘 수 있는 커넥션의 개수. 디폴트 값은 5개의 커넥션이다.
* Pool.MaximumCheckoutTime – 커넥션을 요청하고서 획득할 때까지 기다리는 최대시간. 디폴트 값은 20,000 밀리초, 즉 20초이다.
* Pool.TimeToWati – 사용 불가능한 커넥션을 요청했을 때 기다리는 최대 시간을 밀리초로 지정한다. 디폴트 값은 20,000 밀리초 즉, 20초이다.
* Pool.PingEnabled – 만약 true(또는 자바의 Boolean 클래스가 true로 간주하는 모든 표현식)라면, (시간이 오래되어) 커넥션이 닫힐 수 있는 상태가 되면 Pool.PingQuery 프로퍼티에 지정된 쿼리를 이용해 커넥션을 테스트한다. 디폴트 값은 false이며 이는 커넥션을 사용하기 전에는 테스트하지 않음을 의미한다. 아래의 세가지 프로퍼티로 커넥션 핑 테스트의 행동 방식을 결정한다.
* PoolPingQuery – 커넥션이 아직 살아 있는지 테스트할 필요가 있을 때 실행하는 쿼리를 지정한다. 값을 매우 빨리 반환하는 쿼리로 지정해야 한다. 예를 들면 Oracle 에서는 ‘select 0 from dual’ 같은 쿼리로 지정하면 좋다.
* Pool.PingConnectionOlderThan – 어떤 커넥션이 닫힐 수 있는 상태가 되었는지 판단하는 기준 시간(밀리초)을 지정한다. 디폴트 값은 0이며 이는 PingEnabled가 true 일 때 커넥션을 사용 할 때마다 검사한다는 의미이다. 트랜잭션이 많은 환경에서는 성능에 심각하게 악영향을 줄 수 있다.
* Pool.PingConnectionsNotUsedFor – 커넥션이 얼마동안 유휴 상태이면 닫힐 수 있는 상태가 되었다고 간주하는지를 밀리초로 지정한다.
- DBCP 데이터 소스
DBCP 데이터 소소는 Jakarta Commons Database Connection Pooling(DBCP) 프로젝트의 데이터 소스 구현체에 대한 래퍼이다. Jar 파일과 추가 설정이 필요하지만 이를 통해 좀 더 견고한 구현체를 사용할 수 있다. 이 트랜잭션 관리자의 프로퍼티는 어떤 프로퍼티를 사용할 수 있느냐에 따라 다르게 다루어진다. DBCP 트랜잭션 관리자를 설정하는 기존의 방법(여전히 지원된다.) 은 다음의 여덟 개의 프로퍼티를 설정하는 것이다.
* JDBC.Driver – DAO 컨텍스트가 사용할 JDBC 드라이버를 지정한다.
* JDBC.ConnectionURL – DAO 컨텍스트의 데이터베이스 접속 JDBC URL이다.
* JDBC.Username – 데이터 베이스에 연결할 때 사용할 사용자명
* JDBC.Password – 데이터베이스에 연결할 때 사용할 비밀번호
* Pool.ValidationQuery – 데이터베이스 연결이 유효한지 검사하는 데 사용하는 쿼리
* Pool.MaximumActiveConnections – 커넥션 풀에서 유휴 상태로 존재할 수 있는 커넥션의 최대 개수를 지정한다.
* Pool.MaximumWait – 커넥션을 요청하고서 기다릴 최대 시간(밀리초)을 지정한다. 해당 시간이 지나도 못 가져오면 그 커넥션은 포기한다.
DBCP 데이터 소스를 설정하는 새로운 방법은 이를 간단히 자바 빈즈로 여기고 처리하기 때문에 좀 더 유연하다. 따라서 모든 프로퍼티는 iBATIS에서 사용 가능한 get/set 메서드로 나타낸다. 예를 들어 데이터 소스에 “driverClassName”을 설정하려면 다음과 같이 하면 된다.
name=”draverClassName”
value=”com.mysql.jdbc.Driver” />
[참고] DBCP 데이터 소스를 사용하고 설정하는 것에 대해 좀 더 공부해 보고자 한다면, Jakarta 프로젝트의 공식 사이트 http://jakarta.apache.org/commons/dbcp/ 를 방문해 보길 바란다.
- JNDI 데이터 소스
JNDI 데이터 소스는 애플리케이션 컨테이너에서 제공하는 모든 JNDI 컨텍스트를 사용하려고 나온 것이다. 아마도 설정은 이것이 가장 간단할 것이다. 이는 DBJndiContext 라는 단 한 개의 프로퍼티만 있으면 되며, 이 프로퍼티에 데이터 소스를 포함하고 있는 JNDI 컨텍스트 이름을 지정한다.
이 데이터 소스 요소의 “context”로 시작하는 자식 요소를 사용하여 InitialContext 생성자에 다른 프로퍼티를 넘겨주는 것도 가능하다.
예를 들어 “someProperty” 라는 이름의 프로퍼티를 전달하려면 다음 구문을 사용하면 된다.


- JTA 트랜잭션 관리자
Java Transaction API (JTA) 트랜잭션 관리자를 통해서 여러 데이터 베이스간의 분산 트랜잭션을 사용할 수 있다. 이것은 마치 한 개의 데이터베이스로 작업하는 것처럼 쉽게 다중 데이터베이스의 데이터에 가해진 변경을 커밋이나 롤백 할 수 있다는 것을 의미한다.
대부분의 설정 작업이 JNDI에서 이뤄지기 때문에 이 트랜잭션 관리자를 설정하는 데는 두 개의 프로퍼티만 지정하면 된다. 첫 번째 프로퍼티는 DBJndiContext로 트랜잭션 관리자에서 사용할 데이터 소스를 포함하고 있는 JNDI 컨텍스트의 이름을 지정한다. 다른 프로퍼티는 UserTransaction 으로 사용자 트랜잭션을 포함하고 있는 컨텍스트의 이름을 지정한다.
- QJB 트랜잭션 관리자
ObJectRelationalBridge(OJB)는 또 다른 객체 관계 매핑 툴로 관계형 데이터베이스를 사용하여 자바 객체를 영구 저장하는 기능을 제공한다. OJB 트랜잭션 관리자는 OJB 가 제공하는 트랜잭션 관리자 인터페이스의 래퍼이다.
모든 OJB 트랜잭션 관리자의 설정은 iBATIS DAO 를 사용하지 않을 경우와 동일하게 하면 된다.
[참고] OJB 툴에 대한 좀 더 다양한 정보와 이 트랜잭션 관리자를 설정하는 방법을 보려면 http://db.apache.org/ojb/ 를 방문해 보길 바란다.
- SQLMAP 트랜잭션 관리자
SQLMAP은 iBATIS DAO를 사용할 때 가장 일반적으로 사용하게 될 트랜잭션 관리자일 것이다. SQLMAP 트랜잭션 관리자는 “SqlMapConfigURL” 이나 “SqlMapConfigResource” 프로퍼티 중 하나를 필요로 한다. 이것은 iBATIS SQL Maps가 트랜잭션 관리에 사용하는 것과 동일한 트랜잭션 관리자를 사용한다.
SqlMapConfigURL 프로퍼티는 java.net.URL 클래스가 파싱해서 리소스를 가져올 수 있는 문자열로 지정한다. http: 혹은 file: 프로토콜 등이 이에 해당한다. SqlMapConfigResource 프로퍼티는 현재 클래스패스에 존재하는 자원을 참조하고자 할 때 사용한다.
- TOPLINK 트랜잭션 관리자
Oracle 의 제품인 Toplink는 또다른 ORM 툴이다. TOPLINK 트랜잭션 관리자에 필요한 프로퍼티는 “session.name” 프로퍼티 하나뿐이다. 이 값을 사용하여 DAO 컨텍스트에서 사용할 세션을 가져온다.
- 자체적으로 생성한 트랜잭션 관리자나 다른 트랜잭션 관리자를 사용하기
위의 트랜잭션 관리자 구현체들과 더불어, DaoTransactionManager는 개발자가 직접 구현할 수 있는 인터페이스이다. 구현체의 완전한 클래스 이름을 트랜잭션 관리자 설정 요소의 type 속성에 지정해서 DAO 설정에 넣으면 된다. 요소에 포함된 모든 프로퍼티는 트랜잭션 관리자 클래스의 configure() 메서드에 전달된다.

type=”com.mycompany.MyNiftyTransactionManager”>



위 예에서 iBATIS DAO 프레임워크는 MyNiftyTransactionManager 클래스의 인스턴스를 생성하고, someProp과 someOtherProp 프로퍼티를 포함하고 있는 Properties 객체를 파라미터로 넘긴다. 11장에서 기존 DaoTransactionManager의 구현체들에 대해 상세히 공부해 볼 것이다.
10.2.4 DAO 요소
일단 트랜잭션 관리자를 선택하고 설정하고 나면, DAO 컨텍스트에 DAO 요소를 추가할 수 있다. 이를 통해 DAO 인터페이스를 정의하고 구현한다. DAO 컨텍스트는 이 인터페이스와 구현체를 애플리케이션에서 사용할 수 있게 만들어 준다.
요소는 오직 두 가지 프로퍼티만을 가지고 있다. 바로 interface와 implementation 이다.
[참고] 속성 명인 “interface”는 DAO 구현체를 구분하는 역할을 할 뿐이다. 따라서 실제로는 인터페이스를 사용할 필요는 없다. 위의 경우, 두 속성 (interface와 implementation)은 구현체의 패키지 경로를 포함한 클래스명을 지정하면 된다. 이것이 (인터페이스를 사용하지 않는 것) 코드를 약간 줄여줄 수는 있다. 하지만 결코 권장하지는 않는다. 인터페이스를 사용하지 않으면 DAO 계층을 추가함으로써 얻게 되는 이점이 사라지기 때문이다. 바로 인터페이스와 구현의 분리라는 이점 말이다. 코드의 양이 줄어드는 것에 관해서는, 최근 거의 모든 IDE들이 구현체로부터 인터페이스를 분리해 내는 리팩터링 툴을 제공하기 때문에, 인터페이스를 만들기 전에 먼저 DAO 구현체를 작성하고 테스트 한 뒤에 나중에 몇 번의 마우스 클릭만으로도 인터페이스를 생성해 낼 수 있다.
Interface 프로퍼티는 DAO 맵 안의 DAO를 구분하기 위해 사용하고 대개 다음과 같은 방식으로 사용한다.
Implementation=”com.mycompany.system.dao.impl.AccountDaoImpl”/>

이 예제를 살펴보자. 클래스의 관계가 앞의 10.1.2 에서 묘사했던 것과 같다고 가정하다. 여기서는 AccountDao 인터페이스가 있고 AccountDaoImpl 클래스가 이 인터페이스를 구현하고 있다. DaoManager를 사용하여 DAO를 가져오려면 다음의 코드를 사용하면 된다.
AccountDao accountDao = (AccountDao)
daoManager.getDao(AccountDao.class);
이 코드에서는 AccountDao 변수를 선언하고 DaoManager 인스턴스에서 인터페이스 이름을 이용하여 변수 값을 요청한다. 그리고는 DaoManager는 단지 Object 형으로 값을 반환하기 때문에 그 값을 AccountDao 인터페이스로 형 변환한다.
이전 버전의 DAO 에서는 인터페이스 클래스 대신에 String 타입으로 파라미터를 전달하는 것도 가능했었다. 하지만 이 기능이 잠재적으로 오류가 발생해야 하는 부분에서도 오류가 발생하지 않게 막아버리기 때문에 2.x 버전에서는 이를 제거하였다. DAO 구현체를 구분하기 위해 클래스 명을 강제로 사용하게 하여, 자바 환경에서 오타를 피할 수 있게 된다. 이는 인터페이스 명을 잘못 쓴다면 코드가 컴파일조차도 되지 않을 것이기 때문이다. 오류는 일찍 발생해서 알아챌수록 좋다.
자 이제 iBATIS DAO 프레임워크로 무엇을 할 수 있는지에 대한 기본 이해를 다졌으니, 좀 더 고급 사용 기법들을 살펴볼 시간이 되었다.

10.3. 설정 팁들
DAO 설정이 표면적으로는 매우 간단하게 보이지만, 그럼에도 상당한 유연성을 제공해준다. DAO 관리자를 독창적으로 설정하면, 일반적인 문제들을 꽤 세련된 접근법을 사용하여 해결할 수 있다. 그런 몇 가지 접근법을 보자.
10.3.1 다중 서버
앞에서 이야기했듯이 개발 업체들이 개발, QC 테스트(Quality Control Test), UA 테스트 (User Acceptance Test) 그리고 운영 환경에서 서로 다른 여러 서버를 사용하는 것은 흔히 볼 수 있는 일이다.
이러한 경우, dao.xml 파일에서 환경 정보를 제거하고 외부 파일에 정보를 둘 수 있다는 점이 매우 유용하게 작용한다. 바로 그러한 역할을 하기 위해 properties 요소를 만들어 두었다. 다음은 JDBC 설정 정보를 dao.xml 파일 외부에 두는 예제 dao.xml 파일이다.

PUBLIC
"-//ibatis.apache.org//DTD DAO Configuration 2.0//EN"
"http://ibatis.apache.org/dtd/dao-2.dtd">






value="${jdbcUrl}" />

value="${jdbcPassword}" />
value="${jdbcAutoCommit}" />





이 예제에서 모든 프로퍼티 값은 클래스패스의 가장 상위에서 적재되는 “server.properties” 파일에 저장돼 있다.
이것이 우리가 선호하는 방식이다. 왜냐하면 모든 파일을 (CVS, Subversion 등을 이용하여) 버전 관리할 수 있고, 프로퍼티 파일의 서로 다른 버전들을 환경에 따라 서로 다른 이름(예를 들어 server-production.properties, server-user.properties, 기타 등등)을 주어 구분하여 (Ant 나 셸 스크립트 등을 이용한) 빌드 처리기가 자동으로 올바른 위치에 올바른 버전의 파일을 복사할 수 있게 되기 때문이다.
이 접근 방식은 설정 파일에 보안을 엄격히 적용하여 버전 관리하에 파일을 두지 않는 훨씬 민감한 환경에서도 잘 작동한다. 그런 환경에서 이 방식을 사용하면 수작업을 통한 설정이 훨씬 단순해진다. 환경에 따라 변경되는 설정 파일이 항상 동일하기 때문이다.
10.3.2 다중 데이터베이스의 방언(dialect)
만약 서로 다른 코드를 사용해야 할 만큼 서로 상이한 여러 데이터베이스 플랫폼(예를 들면, MySQL 을 저장 프로시저 없이 사용하고 Oracle 을 저장 프로시저와 함께 사용할 때)을 지원하기 위해서 DAO 패턴을 사용한다면, JDBC 설정에서 했던 것과 유사한 방식으로 처리하고 패키지 이름을 프로퍼티 파일에 지정해 줄 수 있다. (다음을 보라.)

PUBLIC
"-//ibatis.apache.org//DTD DAO Configuration 2.0//EN"
"http://ibatis.apache.org/dtd/dao-2.dtd">






value="${jdbcUrl}" />

value="${jdbcPassword}" />
value="${jdbcAutoCommit}" />

implementation="${impl}.AccountDaoImpl"/>



위에서는 모든 서버 설정과 데이터 접근 구현체들이 주 설정 파일 외부에 빠져있다.

10.3.3 실행시에 설정 변경하기
지금까지 본 것만으로는 충분히 유연하지 못한 것인 양, DAO 관리자는 실행시에 값이 결정되는 프로퍼티를 DAO 관리자가 생성될 때 전달하는 기능도 제공한다.
10.1.2 절에서 살펴 본 코드의 두 번째 형태는 실행시에 설정 정보를 전달할 수 있도록 다음과 같은 메서드를 제공해준다.
public static DaoManager getDaoManager(Properties props)
{
String daoXml = "/org/apache/mapper2/examples/Dao.xml";
Reader reader;
DaoManager localDaoManager;
try {
reader = Resources.getResourceAsReader(daoXml);
localDaoManager =
DaoManagerBuilder.buildDaoManager(reader, props);
} catch (IOException e) {
throw new RuntimeException(
"Unable to create DAO manager.", e);
}
return localDaoManager;
}

위의 코드는 평소처럼 애플리케이션 전체적으로 공유하는 DAO 관리자를 반환하는 대신, 실행시간에 프로퍼티를 넘겨주어 동적으로 설정되는 DAO 관리자를 생성한다. 비록 이 기능이 훨씬 더 유연하긴 하지만, 이는 또한 DAO 관리자가 필요할 때마다 매번 생성하는 대신(객체를 매번 생성하면 성능 저하가 오기 때문에) 이를 사용하는 사람이 직접 스스로 DAO 관리자 객체의 사본을 저장해놓고 재사용해야만 한다.
지금까지 iBATIS DAO 프레임워크를 설정하는 방법의 예를 보았다. 다음 단계로 가서 실제로 사용하는 방법을 살펴보고 프레임워크가 우리 대신 관리해줄 DAO 클래스 몇 개를 생성해보자.

10.4. SQL Maps DAO 구현체 예제
DAO 패턴이란 인터페이스 뒤에 데이터 접근에 관한 구현을 숨기는 것이 거의 전부이다. 하지만 여전히 내부적으로 구현체를 만들어야 한다는 점에는 변함이 없다. 이번 절에서는 DAO 인터페이스의 구현체를 SQL Maps로 구현해 볼 것이다. DAO 패턴의 자세한 사용법은 11장에서 더 배울 것이다. 11장에서는 이번과 동일한 인터페이스를 하이버네이트와 JDBC를 직접 이용하는 방식으로 구현해 볼 것이다.
구현체를 작성하기 전에 DAO 인터페이스를 먼저 구축해보자.
package org.apache.mapper2.examples.chapter10.dao;
import org.apache.mapper2.examples.bean.Account;
import org.apache.mapper2.examples.bean.IdDescription;
import java.util.List;
import java.util.Map;
public interface AccountDao {
public void insert(Account account);
public void update(Account account);
public int delete(Account account);
public int delete(Integer accountId);
public List getAccountListByExample(
Account account);

getMapListByExample(Account account);
public List
getIdDescriptionListByExample(Account account);
public Account getById(Integer accountId);
public Account getById(Account account);
}

이 인터페이스는 account 테이블에서 사용하기에 적합한 것 같다. 모든 기본적인 CRUD 인터페이스를 포함하고 있으며, API를 좀 더 편하게 사용할 수 있게 해주는 메서드도 몇 개 추가하였다. iBATIS 를 중심으로 이야기를 할 것이기 때문에 위에서 정의한 인터페이스를 SQL Maps 기반의 버전으로 구현해보자. 첫째로, iBATIS를 사용할 수 있게 설정하는 dao.xml을 살펴보자.

10.4.1 iBATIS를 사용하는 DAO 설정
다음은 한 개의 SQL Map 기반 DAO를 사용하는 DAO 설정을 보여준다. sqlmap이라는 컨텍스트를 정의하였다. 이 컨텍스트는 클래스패스상에 존재하는 SqlMapConfig.xml 파일을 사용한다(예를 들어 웹 애플리케이션이라면 이 파일은 /WEB-INF/classes/ 디렉터리에 있을 것이다).

PUBLIC
"-//ibatis.apache.org//DTD DAO Configuration 2.0//EN"
"http://ibatis.apache.org/dtd/dao-2.dtd">



value="SqlMapConfig.xml"/>

implementation=
"com.mycompany.system.dao.sqlmap.AccountDaoImpl"/>


PUBLIC
"-//ibatis.apache.org//DTD DAO Configuration 2.0//EN"
"http://ibatis.apache.org/dtd/dao-2.dtd">



value="SqlMapConfig.xml"/>

implementation=
"com.mycompany.system.dao.sqlmap.AccountDaoImpl"/>



DaoManagerBuilder는 dao.xml 파일을 사용하여 DaoManager 인스턴스를 생성한다. 다음을 살펴보자.

10.4.2 DaoManager 인스턴스 생성하기
방금 정의한 DAO 관리자를 사용하려면 DaoManagerBuilder를 사용하여 이것의 인스턴스를 생성해야 한다. 다음에서 DaoManager 인스턴스를 생성할 때 사용하는 코드 조각을 볼 수 있다.
private DaoManager getDaoManager() {
DaoManager tempDaoManager = null;
Reader reader;
try {
reader = Resources.getResourceAsReader("Dao.xml");
tempDaoManager =
DaoManagerBuilder.buildDaoManager(reader);
} catch (Exception e) {
e.printStackTrace();
fail("Cannot load dao.xml file.");
}
return tempDaoManager;
}

이제 약간의 코드와 설정 요소들을 갖추었으니, 이것들이 뭘 하는 것인지 좀 더 자세히 살펴보자.
이 코드는 클래스로더가 탐색하는 몇몇 클래스패스 경로의 가장 상위에 있는 Dao.xml 이라는 리소스를 찾는다. 예를 들어, 톰캣에서는 웹 애플리케이션의 WEB-INF/classes 디렉터리나 WEB-INF/lib 디렉터리 안의 JAR 파일(JAR 파일의 최상위에 있다면)에 있을 것이다.
일단 설정 파일을 읽어 들이면, DaoManagerBuilder 에 파일의 데이터를 전달하고 DaoManager 인스턴스를 생성하라는 요청을 보낸다.
이 코드는 우리가 보고 있는 DAO 구현체를 구성하고 테스트하는 데 사용하는 JUnit 테스트에서 따온 것이다. 그래서 예외 처리가 다소 빈약하다. 실제 제품이 되는 애플리케이션에서는 이런 식으로 예외를 다루어서는 안 된다.

10.4.3 트랜잭션 관리자 설정하기
다음으로 트랜잭션 관리자를 정의하였다. 이 트랜잭션 관리자는 요소의 SqlMapConfigResource 프로퍼티에서 지정한 SQL Maps 설정 파일의 트랜잭션 관리자 정의를 기반으로 하고 있다. SQL Maps 를 직접 사용할 때 사용 가능한 모든 트랜잭션 관리 기능은 여전히 우리의 DAO 구현체에서도 사용 가능하다. 다음은 이 예제에서 사용하는 SQLMapConfig.xml 이다.

PUBLIC
"-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">


errorTracingEnabled="true"
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true"
maxRequests="32"
maxSessions="10"
maxTransactions="5"
useStatementNamespaces="true"
/>



value="${connectionUrl}"/>







이 파일 설정에 관해서는 4장에서 자세히 다루었으므로 여기서는 다시 다루지 않겠다.

10.4.4 맵 읽어들이기
트랜잭션 관리를 정의함과 동시에, SQL Maps 설정 파일에 정의된 모든 맵을 읽어 들인다. 이 예제에서는 간단히 Account.xml 파일만을 사용하며, 이 파일에서 DAO 클래스에 필요한 모든 매핑 구문을 정의한다. 다음에서 볼 수 있다.

PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">

type="${BeanPackage}.Account" />
type="${BeanPackage}.IdDescription" />


SELECT nextVal('account_accountid_seq')

INSERT INTO Account (
accountId,
username,
password,
firstName,
lastName,
address1,
address2,
city,
state,
postalCode,
country
) VALUES(
#accountId#,
#username:varchar#,
#password:varchar#,
#firstName:varchar#,
#lastName:varchar#,
#address1:varchar#,
#address2:varchar#,
#city:varchar#,
#state:varchar#,
#postalCode:varchar#,
#country:varchar#
)


update Account set
username = #username:varchar#,
password = #password:varchar#,
firstName = #firstName:varchar#,
lastName = #lastName:varchar#,
address1 = #address1:varchar#,
address2 = #address2:varchar#,
city = #city:varchar#,
state = #state:varchar#,
postalCode = #postalCode:varchar#,
country = #country:varchar#
where accountId = #accountId#


delete from Account
where accountId = #accountId#


accountId as "accountId",
username,
password,
firstName as "firstName",
lastName as "lastName",
address1,
address2,
city,
state,
postalCode as "postalCode",
country




city like #city#


accountId = #accountId#




select

from Account








이 SQL Map에서는 모든 필드를 나열하고 있는 SQL 조각을 정의하고 있다. 이런 경우, JDBC 드라이버가 칼럼 이름의 대소문자를 혼동하는 경우가 생길 수 있기 때문에, 자동 결과 매핑을 올바르게 수행할 수 있도록 칼럼 이름에 명시적으로 별칭을 지정한다. 그 다음 SQL 조각에서는 나중에 사용할 복잡한 WHERE 절을 정의한다. 세 번째 SQL 조각을 사용하여 다른 두개의 조각을 하나의 조각에 함께 넣고 그 결과로 나오는 SQL 조각은 서로 다른 두개의 select 구문에서 사용한다. 두 select 구문 중 하나는 자바빈즈의 List를 생성하고, 다른 하나는 Map의 List를 생성한다. getIdDescriptionListByExample 매핑 구문에서는 복잡한 WHERE 절을 다시 한 번 사용하여 서로 다른 형태의 빈즈의 List를 가져온다.

10.4.5 DAO 구현체 코딩하기
마침내 DAO 구현체를 만들 차례이다. 이전에 언급한 바와 같이 DAO를 생성하려면 인터페이스와 구현체 모두를 제공한다. 이 경우, 인터페이스는 “com.mycompany.system.dao.AccountDao”로 정의하고 구현체는 “com.mycompany.system.dao.sqlmap.AccountDaoImpl”로 정의한다.
이미 10.3 절에서 이 인터페이스를 보았다. 그래서 여기서는 인터페이스 말고 DAO 구현 클래스를 살펴볼 것이다.
package org.apache.mapper2.examples.chapter10.dao.sqlmap;
import com.ibatis.dao.client.DaoManager;
import com.ibatis.dao.client.template.SqlMapDaoTemplate;
import org.apache.mapper2.examples.bean.Account;
import org.apache.mapper2.examples.bean.IdDescription;
import
org.apache.mapper2.examples.chapter10.dao.AccountDao;
import java.util.List;
import java.util.Map;
public class AccountDaoImpl extends SqlMapDaoTemplate
implements AccountDao {
public AccountDaoImpl(DaoManager daoManager) {
super(daoManager);
}
public Integer insert(Account account) {
return (Integer) insert("Account.insert", account);
}
public int update(Account account) {
return update("Account.update", account);
}
public int delete(Account account) {
return delete(account.getAccountId());
}
public int delete(Integer accountId) {
return delete("Account.delete", accountId);
}
public List getAccountListByExample(
Account account) {
return queryForList("Account.getAccountListByExample",
account);
}
public List>
getMapListByExample(Account account) {
return queryForList("Account.getMapListByExample",
account);
}
public List
getIdDescriptionListByExample(
Account account) {
return
queryForList("Account.getIdDescriptionListByExample",
account);
}
public Account getById(Integer accountId) {
return (Account)queryForObject("Account.getById",
accountId);
}
public Account getById(Account account) {
return getById(account.getAccountId());
}
}

표면적으로 봐서는 별로 대단해 보이지 않는다. 클래스의 선언부에서 AccountDao 인터페이스를 구현하고 SqlMapDaoTemplate 클래스를 상속받는 것을 볼 수 있다.
SqlMapDaoTemplate 클래스는 하나의 작은 패키지 안에서 모든 SQL Map API의 컴포넌트들을 제공해 주는 무거운 짐을 지고 있다. 게다가 로컬 메서드를 호출하면 SqlMapExecutor를 대신 호출해 주는 기능도 제공하다. 따라서 SqlMapClient나 SqlMapExecutor의 인스턴스를 가져오지 않고도, 마치 DAO 클래스의 일부분인 양 이들의 메서드를 호출할 수 있다.
비록 구현체와 DAO 클래스를 분리하는 것이 많은 작업을 필요로 하긴 하지만, 그 다음부터는 인터페이스를 생성하고, 구현하고, SQL Map을 만들고 마지막으로 Dao.xml 파일에 단 한 줄만 추가해 주면 DAO 클래스를 만들 수 있다. 11장에서는 동일한 DAO 인터페이스를 하이버네이트와 JDBC를 직접 사용하는 방식으로 구현할 것이다. 이 세 가지 구현체는 근본적으로 서로 다른 데이터베이스 접근 방식을 사용함에도 불구하고, 모두 동일한 API(AccountDao 인터페이스)를 사용한다.

댓글 없음:

댓글 쓰기