2011년 5월 23일 월요일

The Data Access Object pattern

8. The Data Access Object pattern
J2EE 개발자들은 Data Access Object (DAO) 디자인 패턴을 사용하여 저수준의 데이터 액세스 로직과 고급 비즈니스 로직을 분리한다. DAO 패턴을 구현하는 것은 단순히 데이터 액세스 코드를 작성하는 것 이상이다. 이 패턴을 사용하여 저수준 데이터 액세스와 고급 비지니스 로직을 분리한다. 전형적인 DAO 구현에는 다음과 같은 요소들이 있다.
  • DAO 팩토리 클래스
  • DAO 인터페이스
  • DAO 인터페이스를 구현하는 구체적 클래스
  • 데이터 전송 객체들(밸류(value) 객체)

구체적인 DAO 클래스에는 특정 데이터 소스로 부터 데이터에 액세스하는데 쓰이는 로직이 포함되어 있다.

구조

Data Access Object patterclass diagram

Data Access Object pattersequence diagram


역할

Business Object :
Business Object는 Data 클라이언트를 대표한다. 이것은 Data source에 접근하여 데이트를 얻거나 저장하는 것을 목표로하는 객체이다.Business Object는 SessioBean, Entity Bea또는 별도의 Java Object로 구현된다.

Data Access Object :
Data Access Object은 이 패턴에서 중요한 객체이다. Data source에 투명한 접근을 가능하게 하기 위하여Data AccessObject은 Business Object을 위해 근본적인 데이타 접근를 추상한다. BusinessObject은Data AccessObject에게 Data load와 store 행위를 Delegate한다.

DataSource :
Data source 구현체를 대표한다. Data source는 RDBMS, OODBMS, XML repository, flat file system 등등일 수있다.Data source는 또한 서로다른 시스템(legacy/mainframe), 서비스(B2B service) 또는 특정한 종류의 Repository(LDAP)일 수 있다.

Value Object(Transfer Object) :
Data 전송에 사용되는 전송객체를 대표한다. Data Access Object 패턴에서 클라이언트에게 전송되는 데이터의 왕복통신비용을 줄이고 객체화된 데이터를 전달하기 위해 사용되며 또, 클라이언트로부터 Data source의 Data을 갱신하기 위해 Data Access Object에게 변경데이터를 전달 할때도 사용된다.

의도
Data에 대한 접근 방법은 Data Base의 종류에 종속적이다. 그런 종속적인 영향이 다른 어플리케이션에 전파되지 않게 하기 위해 Data Source에 접근하는 추상화 객체를 사용한다.

예제소스
Class


소스
package dao;
public class Customer implements java.io.Serializable {
int CustomerNumber;
String name;
String streetAddress;
String city;
public Customer() {
}
public int getCustomerNumber() {
returCustomerNumber;
}
public void setCustomerNumber(int CustomerNumber) {
this.CustomerNumber = CustomerNumber;
}
public String getCity() {
returcity;
}
public void setCity(String city) {
this.city = city;
}
public String getName() {
returname;
}
public void setName(String name) {
this.name = name;
}
public String getStreetAddress() {
returstreetAddress;
}
public void setStreetAddress(String streetAddress) {
this.streetAddress = streetAddress;
}
}
package dao;
public abstract class DAOFactory {
public static final int CLOUDSCAPE = 1;
public static final int ORACLE = 2;
public static final int SYBASE = 3;
public abstract CustomerDAO getCustomerDAO();
//public abstract AccountDAO getAccountDAO();
//public abstract OrderDAO getOrderDAO();
public static DAOFactory getDAOFactory(int whichFactory) {
switch (whichFactory) {
case CLOUDSCAPE :
//returnew CloudscapeDAOFactory();
case ORACLE :
returnew OracleDAOFactory();
case SYBASE :
//returnew SybaseDAOFactory();
default :
returnull;
}
}
}
package dao;
import java.sql.*;
public class OracleDAOFactory extends DAOFactory {
public static final String DRIVER = " oracle.jdbc.driver.OracleDriver";
public static final String DBURL= "jdbc:oracle:thin:@localhost:1521:topaz";
public static ConnectiocreateConnection() {
// DataBase Connectio생성
returnull;
}
public CustomerDAO getCustomerDAO() {
returnew OracleCustomerDAO();
}
/*
public AccountDAO getAccountDAO() {
// Oracle AccountDAO 생성
returnew OracleAccountDAO();
}
public OrderDAO getOrderDAO() {
// Oracle OrderDAO 생성
returnew OracleOrderDAO();
}
*/
}
package dao;
import java.util.Collection;
import java.util.HashMap;
import javax.sql.RowSet;
public interface CustomerDAO {
public int insertCustomer(Customer customer);
public booleadeleteCustomer(int CustomerNumber);
public Customer findCustomer(int CustomerNumber);
public booleaupdateCustomer(Customer customer);
public RowSet selectCustomersRS(HashMap where);
public CollectioselectCustomersTO(HashMap where);
}
package dao;
import java.sql.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import javax.sql.RowSet;
public class OracleCustomerDAO implements CustomerDAO {
public OracleCustomerDAO() {
// 초기화
}
/**
* 아래의 메소드는 실행전에 Connection 받아와야 합니다.
* Data Source 필요한 작업을 작성합니다.
*/
public int insertCustomer(Customer customer) {
// 이렇게 받아와야 합니다.
Connectioconnectio=
OracleDAOFactory.createConnection();
retur0;
}
public booleadeleteCustomer(int CustomerNumber) {
returfalse;
}
public Customer findCustomer(int CustomerNumber) {
returnew Customer();
}
public booleaupdateCustomer(Customer customer) {
returfalse;
}
public RowSet selectCustomersRS(HashMap where) {
returnull;
}
public CollectioselectCustomersTO(HashMap where) {
returnull;
}
}
package dao;
import java.util.Collection;
import java.util.HashMap;
public class Client {
public static void main(String[] args) {
Client client = new Client();
client.exec();
}
public void exec() {
DAOFactory oracleFactory =
DAOFactory.getDAOFactory(DAOFactory.ORACLE);
CustomerDAO custDAO =
oracleFactory.getCustomerDAO();
Customer customer = new Customer();
customer.setCity("서울");
customer.setName("Lee YuChang");
int newCustNo = custDAO.insertCustomer(customer);
customer = custDAO.findCustomer(newCustNo);
customer.setStreetAddress("서울특별시 00 00 00번지");
customer.setName("후루꾸");
custDAO.updateCustomer(customer);
custDAO.deleteCustomer(newCustNo);
HashMap where = new HashMap();
where.put("city","서울");
CollectiocustomersList =
custDAO.selectCustomersTO(where);
}
}


참고사항
트랜잭션 경계설정(demarcation)
DAO는 트랜잭션 객체들이라는 것을 반드시 기억해야 한다. DAO에 의해 수행되는 각각의 작동(데이터 구현, 업데이트, 삭제)은 트랜잭션과 관련있다. 따라서 트랜잭션 경계설정(demarcation) 개념은 매우 중요하다.
선언적(Declarative) 트랜잭션 경계설정
프로그램에 입각한(Programmatic) 트랜잭션 경계설정
프로그래머는 EJB 전개 디스크립터를 사용하여 트랜잭션 애트리뷰트를 선언한다.
프로그래머는 트랜잭션 로직을 코딩해야한다.
런타임 환경(EJB 컨테이너)은 트랜잭션을 자동으로 관리하기 위해 이 애트리뷰트를 사용한다.
이 애플리케이션은 API를 통해 트랜잭션을 제어한다.


DAO를 설계할 때 고려 사항
  • 트랜잭션은 어떻게 시작하는가?
  • 트랜잭션은 어떻게 끝나는가?
  • 트랜잭션 시작을 담당하는 객체는 무엇인가?
  • 트랜잭션 종료를 담당하는 객체는 무엇인가?
  • DAO가 트랜잭션의 시작과 종료를 담당해야 하는가?
  • 이 애플리케이션이 다중의 DAO를 통해 데이터에 액세스해야 하는가?
  • 트랜잭션에 포함될 DAO의 수는?
  • 하나의 DAO가 또 DAO에 대한 메소드를 호출할 수 있는가?


JTA 개요
Java TransactioAPI (JTA)와 Java TransactioService (JTS)는 J2EE 플랫폼에 분산 트랜잭션 서비스를 제공한다. 분산 트랜잭션에는 트랜잭션 매니저와 한 개 이상의 리소스 매니저가 포함된다. 리소스 매니저는 일종의 영속데이터스토어이다. 트랜잭션 매니저는 모든 트랜잭션 참여자들 간 통신을 조정하는 역할을 담당한다. JTA 트랜잭션은 JDBC 트랜잭션 보다 강력하다. JDBC 트랜잭션이 하나의 데이터베이스 연결로 제한되어 있다면 JTA 트랜잭션은 다중의 참여자들을 가질 수 있다. 다음의 자바 플랫폼 컴포넌트 중 어떤 것이든지 JTA 트랜잭션에 참여할 수 있다.
  • JDBC 커넥션
  • JDO PersistenceManager 객체
  • JMS 큐
  • JMS 토픽
  • Enterprise JavaBeans
  • J2EE Connector Architecture 스팩에 호환하는 리소스 어댑터


JDBC를 이용한 트랜잭션 경계설정
JDBC 트랜잭션은 Connectio객체를 사용하여 제어된다. JDBC Connectio인터페이스(java.sql.Connection)는 두 개의 트랜잭션 모드를 제공한다. (auto-commit과 manual commit). java.sql.Connection은 트랜잭션 제어에 다음의 메소드를 제공한다.
  • public void setAutoCommit(boolean)
  • public booleagetAutoCommit()
  • public void commit()
  • public void rollback()

JDBC 트랜잭션 경계설정을 이용하면 여러 개의 SQL 문장을 하나의 트랜잭션으로 결합할 수 있다. JDBC 트랜잭션의 단점 중 하나는 트랜잭션 범위가 하나의 데이터베이스 연결로 제한되어 있다는 점이다. JDBC 트랜잭션은 다중 데이터베이스로 확장할 수 없다.

JTA를 이용한 트랜잭션 경계설정
JTA로 트랜잭션 경계를 설정하기 위해 애플리케이션은 javax.transaction.UserTransactio인터페이스에 대한 메소드를 호출한다. javax.transaction.UserTransactio인터페이스는 다음의 트랜잭션 제어 메소드를 제공한다.
  • public void begin()
  • public void commit()
  • public void rollback()
  • public int getStatus()
  • public void setRollbackOnly()
  • public void setTransactionTimeout(int)

JTA 트랜잭션을 시작할 때 이 애플리케이션은 begin()을 호출한다. 트랜잭션을 끝내려면 commit() 또는rollback()을 호출한다.
import javax.transaction.*;
import javax.naming.*;
// ...
InitialContext ctx = new InitialContext();
Object txObj = ctx.lookup("java:comp/UserTransaction");
UserTransactioutx = (UserTransaction) txObj;
utx.begin();
// ...
DataSource ds = obtainXADataSource();
Connectiocon= ds.getConnection();
pstmt = conn.prepareStatement("UPDATE MOVIES ...");
pstmt.setString(1, "Spinal Tap");
pstmt.executeUpdate();
// ...
utx.commit();
// ...


JTA와 JDBC 사용하기
개발자들은 DAO 클래스에서 저수준 데이터 작동에 JDBC를 사용하곤 한다. JTA를 이용하여 트랜잭션의 경계를 설정하려면 javax.sql.XADataSource, javax.sql.XAConnection, javax.sql.XAResource 인터페이스를 구현하는 JDBC 드라이버가 필요하다. 이러한 인터페이스를 구현하는 드라이버는 JTA 트랜잭션에 참여할 수 있게된다.
XADataSource 객체는 XAConnectio객체용 팩토리이다. XAConnection는 JTA 트랜잭션에 참여하는 JDBC 커넥션이다. 애플리케이션 서버의 관리 툴을 사용하여 XADataSource를 설정해야 한다.(애플리케이션 서버 문서와 JDBC 드라이버 문서 참조하여 설정한다.)
J2EE 애플리케이션은 JNDI를 사용하여 데이터 소스를 검색한다. 일단 애플리케이션이 데이터 소스 객체에 대한 레퍼런스를 갖게 되면 이것은 javax.sql.DataSource.getConnection()을 호출하여 데이터베이스로의 커넥션을 획득하게 된다. XA 커넥션은 비 XA 커넥션과는 다르다. XA 커넥션은 JTA 트랜잭션에 참여하고 있다는 것을 언제나 기억하라. XA커넥션은 JDBC의 자동 커밋 기능을 지원하지 않는다. 또한 이 애플리케이션은 XA 커넥션 상에서 java.sql.Connection.commit() 또는 java.sql.Connection.rollback()을 호출하지 않는다. 대신 UserTransaction.begin(), UserTransaction.commit(), UserTransaction.rollback()을 사용한다.

최상의 접근방법 선택하기
대부분의 프로젝트에서 대부분은 JDBC API를 사용하여 DAO 클래스를 구현한다.
이 DAO 클래스는 다음과 같이요약된다.
  • 트랜잭션 경계설정 코드는 DAO 클래스 안으로 임베딩된다.
  • DAO 클래스는 트랜잭션 경계설정에 JDBC API를 사용한다.
  • 콜러가 트랜잭션 경계를 설정할 방법은 없다.
  • 트랜잭션 범위는 하나의 JDBC Connection으로 제한된다.

JDBC 트랜잭션이 복잡한 엔터프라이즈 애플리케이션에 언제나 적합한 것은 아니다. 트랜잭션이 다중 DAO 또는 다중 데이터페이스로 확장한다면 다음과 같은 구현 전략이 보다 적합하다.
  • 트랜잭션은 JTA로 경계 설정된다.
  • 트랜잭션 경계설정 코드는 DAO와 분리되어 있다.
  • 콜러가 트랜잭션 경계설정을 담당하고 있다.
  • DAO는 글로벌 트랜잭션에 참여한다.

JDBC 접근방법은 간단함이 매력이다. JTA 접근방법은 유연성이 무기이다. 애플리케이션에 따라 구현 방법을 선택해야 한다.

DAO에서의 예외 핸들링
DAO 패턴을 구현할 때 다음 사항을 자문해보와야 한다.
  • DAO의 퍼블릭 인터페이스의 메소드들이 검사된 예외를 던지는가?
  • 그렇다면 어떤 예외들인가?
  • DAO 구현 클래스에서 예외는 어떻게 처리되는가?
  • DAO 메소드는 의미있는 예외를 던져야한다.
  • DAO 메소드는 java.lang.Exception을 던져서는 안된다. java.lang.Exception은 너무 일반적이다. 근본 문제에 대한 정보를 제공하지 않을 것이다.
  • DAO 메소드는 java.sql.SQLExceptio메소드를 던져서는 안된다. SQLException은 저급 JDBC 예외이다. DAO는 JDBC를 나머지 애플리케이션에 노출하는 것이 아니라 JDBC를 캡슐화 해야한다.
  • DAO 인터페이스의 메소드들은 콜러가 예외를 처리할 수 있다고 합리적으로 판단될 때 에만 검사된예외를 던져야 한다. 콜러가 예외를 핸들할 수 없다면 검사되지 않은(런타임) 예외를 던질 것을 고려하라.
  • 데이터 액세스 코드가 예외를 잡으면 이를 무시하지 말아라. 잡힌 예외를 무시하는 DAO는 문제해결에 애를 먹는다.
  • 연쇄 예외를 사용하여 저급 예외를 고급 예외로 트랜슬레이팅 한다.
  • 표준 DAO 예외 클래스를 정의하라.

댓글 없음:

댓글 쓰기

ETL 솔루션 환경

ETL 솔루션 환경 하둡은 대용량 데이터를 값싸고 빠르게 분석할 수 있는 길을 만들어줬다. 통계분석 엔진인 “R”역시 하둡 못지 않게 관심을 받고 있다. 빅데이터 역시 데이터라는 점을 볼때 분산처리와 분석 그 이전에 데이터 품질 등 데이...