Framework/Database2009. 2. 21. 05:21

사실 개체로서의 인식과 개념적으로의 인식은 생각보다 큰 차이가 아니다. 손 안에 붕어빵에 대해서 끝업이 사고를 발전시켜보면 붕어빵의 공통점을 생각하게 되고 그 생각에서 좀 더 나아가면 개념으로서의 붕어빵을 생각할 수 있듯 이도 결국 추상화의 정도의 문제라고 생각한다.

개념에 대한 이야기는 잠시 접고 실제적인 이야기를 해보자. 그래. 개념으로서 서비스를 인식하게되면 어떤 잇점이 있는건가? 아니 그 전에 개체로서 인식하면 무슨 문제가 있는건가? 에 대한 답을 먼저 찾아야 한다.

package test.db;

import java.io.Closeable;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import junit.framework.TestCase;

public class InstanceDB extends TestCase{

  public void testCase1()  {
    initPool() ;  // 시스템 시작시 단 한번 호출
    
    
    ///////// start
    Connection conn = null;
    PreparedStatement stmt = null ;
    ResultSet rs = null ;
    try {
      conn = getConnection() ;
      stmt = conn.prepareStatement("select * from emp where rownum <= ?");
      stmt.setInt(15);
      rs = stmt.executeQuery() ;
      
      process(rs;
      
    catch (SQLException e) {
      e.printStackTrace();
    finally {
      try {if(rs != nullrs.close();catch(SQLException ex){ex.printStackTrace() ;}
      try {if(stmt != nullstmt.close();catch(SQLException ex){ex.printStackTrace() ;}
      try {if(conn != nullconn.close();catch(SQLException ex){ex.printStackTrace() ;}
    }
    
    ////// end

    destroyPool() // 시스템 종료시 단 한번 호출
  }

  private void process(ResultSet rsthrows SQLException {
    // rs로 실제 작업을 한다.
  }
  
  private Connection getConnection() throws SQLException {
    String url = "jdbc:oracle:thin:@novision:1521:bleujin" ;
    String user = "scott" ;
    String password = "tiger" ;
    return DriverManager.getConnection(url, user, password;
  }

  private void destroyPool() {
    
  }

  private void initPool() {
    try {
      String driverName = "oracle.jdbc.driver.OracleDriver" ;
      Class.forName(driverName);
    catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }
}

보통의 JDBC 예제는 위와 같다. private 함수는 그닥 신경쓸 필요없고 커넥션 풀링 같은 자잘한 문제도 잊어버리기로 하자. 오직 testCase1 함수만 봤을때 언뜻 보기에 별 문제 없어 보인다. 그렇다 별문제 없다. 와~ 이제 집에가서 발딲고 잠이나-ㅅ- 가 아니라.. testCase1 함수는 객체지향의 1원칙 이라고 할 수 있는 DRY를 위반하였다. testCase1 함수 어디에 중복이 있는가? 

예제만을 보면 바로 찾기 어렵지만 사실 testCase함수의 Start와 end 사이가 통째로 중복이다. 왜냐하면 우리는 프로그램 하나를 만들때 testCase와 비슷한 소스를 적어도 수백개-수천개는 만들어야 하기 때문이다. 이를 앞에서 중복얘기할때 말한 구조적인 중복의 문제인데 이는 PMD에 근거한 Code Analyzer같은 툴로 검사해도 나오지 않는 중복이다. (initPool과 destroyPool은 풀링을 제대로 만든다면 프로그램이 뜰때와 내려갈때 한번만 호출이 될것이므로 이부분은 중복이 아니다. )

위 코드는
1. Connection을 얻는다
2. PrepareStatement 객체를 만들어 값을 셋팅한다.
3. 객체를 실행한다.
4. 그 결과값으로 ResultSet을 얻어 무언가 처리를 한다.
5. 사용한 리소스를 닫는다.
6. 예외 처리를 한다.
의 과정을 거치며 위 과정은 2,3번 항목에서 값만 바뀔뿐 수백 혹은 수천번이 반복되는 과정이다.
중복코드를 없애기 위해 getConnection을 함수로 만들어 호출한다지만 호출한다는 과정 자체가 중복이라는 뜻이다.

라이브러리가 단순히 코드의 중복을 없애주는 것과 달리 Framework란 이 과정의 중복을 없애줘야 한다.


package test.db;


import java.sql.SQLException;

import junit.framework.TestCase;

import com.bleujin.framework.db.DBController;
import com.bleujin.framework.db.Rows;
import com.bleujin.framework.db.manager.OracleCacheDBManager;
import com.bleujin.framework.db.procedure.IQueryable;
import com.bleujin.framework.db.servant.StdOutServant;

public class ConceptDB extends TestCase{

  private DBController dc ;
  public void testCase2() {
    initPool() ;  // 시스템 시작시 단 한번 호출

    IQueryable query = dc.createUserProcedure("emp@list(5)").addParam(5;
    Rows rs = dc.getRows(query;
    process(rs;
    
    
    destroyPool() // 시스템 종료시 단 한번 호출
  }
  private void process(Rows rs) {
    // process rs
  }
  private void destroyPool() {
    try {
      dc.getDBManager().destroyPoolConnection() ;
    catch (SQLException e) {
      e.printStackTrace();
    }
  }
  private void initPool() {
    try {
      dc = new DBController("test"new OracleCacheDBManager("jdbc:oracle:thin:@novision:1521:bleujin""scott""tiger" ,5)new StdOutServant(StdOutServant.All)) ;
      dc.getDBManager().initPoolConnection() ;
    catch (SQLException e) {
      e.printStackTrace();
    }
  }
}

Case2의 과정은 아래와 같다.
1. query 객체를 만든다
2. query 객체를 실행한다.
3. 그 결과값인 Rows를 얻어 무언가 처리를 한다.

Case2의 코드는 Case1과 열고 닫는 과정이 없어져서 단순히 줄이 짧다는 것 이상의 차이가 있다. Case1의 과정은 순차적이고 연속적이다. 1에서 6까지의 과정은 순서대로 이루어져야 하며 어느 과정 하나가 생략되어서는 안된다. 이를테면 5번 과정을 빠뜨리면 유한한 자원인 Connection 리소스의 문제로 이후에는 접속조차 안될것이고 6번 과정에서 예외 처리를 적절히 하지 못해도 마찬가지이다. 하지만 Case2는 과정의 단순화뿐 아니라 불연속적이다.

IQueryable 객체를 만들고 실행을 하지 않더라도 혹은 rs의 close()를 호출하지 않아도 전혀 문제가 되지 않는다. 왜냐하면 위에서 나오는 객체들은 자바의 일반 객체이므로 자바의 가비지 처리 알고리즘에 의해 자동적으로 알아서 처리되기 때문이다. 개체로서의 DB가 아나라 개념으로서 DB를 바라보면 구조적 중복을 상당히 없앨뿐 아니라 과정의 연속성의 필요도 없에버릴수 있다. 그럼으로써 프로그래머는 좀더 언어의 처리보다는 비지니스적인 문제에 집중할 수 있고 실수 가능성을 원천적으로 차단해 버린다. 서비스 코드에서는 오직 개념으로서의 DB만 보이므로 connectionless 같은 개체 문제는 아예 신경쓸 필요도 없게 되는 것이다.

귀찮은 반복코드인 예외처리를 없애는게 우선 좋아보이지만 위같이 아예 없어지면 과연 적절한 예외에 대한 상황처리는 어떻게 할거나? 아니 그보다 당장 트랜잭션 처리는 어찌되는거냐.. 같은 세세한 문제는 일단 다음에...


'Framework > Database' 카테고리의 다른 글

Framework (IQueryable)  (0) 2009.03.06
Framework (DBManager)  (0) 2009.03.04
Framework (실험실 코드)  (0) 2009.02.20
Framework (개요)  (0) 2009.02.20
Framework (블랙박스 증후군)  (0) 2009.02.07
Posted by bleujin