Framework/Database2009. 3. 7. 19:27


테스트를 위해 오라클에 패키지를 만들자.
Procedure 구문을 몰라도 그냥 대충 의도만 보면 된다.

--// select 는 cursor type으로 return 하므로 사용자 cursor type을 만든다.

CREATE OR REPLACE PACKAGE SCOTT.Types
    as type cursorType is ref cursor ;
    FUNCTION dummy return number ;
END ;

CREATE OR REPLACE PACKAGE BODY SCOTT.Types
as FUNCTION dummy return Number
is
 BEGIN
   return 1 ;
 End dummy ;
END ;

--// 그냥 dummy 함수 만들어 둔다.


-- // test에 사용할 Package
CREATE OR REPLACE PACKAGE Employee
is
    FUNCTION  listBy(v_deptNo in number) return Types.cursorType ;
    FUNCTION  addDeptWith(v_deptNo in number, v_dname in varchar2, v_loc in varchar2) return number ;
    PROCEDURE addEmpBatch(v_empno in number, v_ename in varchar2, v_deptno in varchar2) ;
END Employee;


CREATE OR REPLACE PACKAGE BODY employee
is
    FUNCTION listBy(v_deptno IN Number)
    return types.cursorType is rtn_cursor types.cursorType ;
    begin
        Open rtn_cursor For
        Select empno, ename, job from emp where deptno = v_deptno ;
       
        return rtn_cursor ;
    end ;

    FUNCTION  addDeptWith(v_deptNo in number, v_dname in varchar2, v_loc in varchar2)
    return number is
    begin
        insert into dept values(v_deptno, v_dname, v_loc) ;
        return SQL%ROWCOUNT ;
    end ;
   
    PROCEDURE addEmpBatch(v_empno in number, v_ename in varchar2, v_deptno in varchar2)
    is
    begin
        insert into emp(empno, ename, deptno) values(v_empno, v_ename, v_deptno);
    end ;

end ;

employee package에는
listBy : 부서번호를 받아서 해당 부서의 사원들의 리스트
addDeptWith : 부서를 추가하는 function
addEmpBatch : 사원을 추가하는 procedure

정도만 있다고 이해하면 된다.


package test.db;

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 TestRows extends TestCase {

  private DBController dc;

  public void testSelect() {

    IQueryable query = dc.createUserProcedure("employee@listby(?)").addParam(20);
    // IQueryable query = dc.createUserCommand("select empno, ename, job from emp where deptno = ?").addParam(20);
    Rows rs = dc.getRows(query);
    System.out.println(rs);
  }

  public void tearDown() throws Exception {
    // 원래는 시스템 종료시 단 한번 호출
    dc.getDBManager().destroyPoolConnection();
  }

  public void setUp() throws Exception {
    // 원래는 시스템 시작시 단 한번 호출
    dc = new DBController("test"new OracleCacheDBManager("jdbc:oracle:thin:@novision:1521:bleujin""scott""tiger"5)new StdOutServant(StdOutServant.All));
    dc.getDBManager().initPoolConnection();
  }
}


간단한 코드를 작성하면 위와 같다.
위의 코드에서는 junit에서 setUp()가 tearDown()은 모든 테스트 메소드 시작과 종료시 호출되므로 테스트 메소드 마다 풀을 만들었다 없앴다 해야 하지만 테스트 코드이므로 신경끄자. 

   
IQueryable query = dc.createUserProcedure("employee@listby(?)").addParam(20);
   Rows rs = dc.getRows(query);
   System.out.println(rs);


OracleCacheDBManager는 UserProcedure의 @앞의 employee는 packageName으로 @뒤의 listBy는 function 혹은 procedure로 해석한다. (앞 글에서 말한대로 이는 DBManager가 결정하는 것일뿐 위의 문장이 항상 PL/SQL을 요구하는것은 아니다.)

난 도저히 프로시저 따위는 못쓰겠다 한다면 
   IQueryable query = dc.createUserCommand("select empno, ename, job from emp where deptno = ?").addParam(20);
와 같이 UserCommand를 사용해도 되긴 한다.



여기서 말하고자 하는 객체는 리턴값인 Rows로서 이 프레임워크에서 중요한 역할을 하고 있다.

위 프로그램을 실행하면
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE RowSet PUBLIC '-//Sun Microsystems, Inc.//DTD RowSet//EN' 'http://java.sun.com/j2ee/dtds/RowSet.dtd'>

<RowSet>
  <data>
    <row>
      <EMPNO>129848</EMPNO>
      <ENAME>m7qpklg</ENAME>
      <JOB>SALESMAN</JOB>
    </row>
    ......
    <row>
      <EMPNO>139387</EMPNO>
      <ENAME>zvk6yp6</ENAME>
      <JOB>SALESMAN</JOB>
    </row>
  </data>
</RowSet>

가 나온다.


Rows는 java.sql.ResultSet을 상속받고 있으므로 기존의 JDBC 처럼 다루면 된다. java.sql.ResultSet과 주요한 차이점은 두가지가 있다.

첫째 close를 하지 않는다.(쿼리 실행시에 getConnection()도 하지 않는다.) 
java.sql.ResultSet과는 달리 Rows는 HTTP와 마찬가지로 JIT식으로 처리를 하고 connectionless 한 환경에서 동작하기 때문이다. - 이 방식의 장단점은 좀 길기 때문로 다른 글에서 언급 - 클라이언트 코드를 작성하는 쪽에서는 수백번-혹은 수천번의 연결하고 실행하고 끊고 예외처리하고 등을 해야 할 필요가 없다. 단지 사원의 리스트를 던져줘라고만 하면 충분하고 또 그래야만 한다. 나머지 과정은 너무 귀찮고 반복적이라 신경쓰고 싶지 않는 것이다.

HTTP는 규약상 connection less 한 규약이지만 실제 사용되는 HTTP 1.1은 내부적으로는 한 페이지에 포함된 글 뿐만 아니라 여러개의 그림도 같은 connection을 통해 가져온다. 좀더 빠르기 때문이다. DB Framework는 풀링 처리를 하기 때문에 그다지 차이가 나진 않지만 그러함에도 2개이상의 Query를 실행시키고 싶다면

  
public void testSelect2(){
    UserProcedures upts = dc.createUserProcedures("two query";
    IQueryable query1 = dc.createUserProcedure("employee@listby(?)").addParam(20);
    IQueryable query2 = dc.createUserCommand("select * from dept where deptno = ?").addParam(10);
    
    upts.add(query1).add(query2;
    Rows rs1 = dc.getRows(upts);
    Rows rs2 = rs1.getNextRows();
  }
와 같이 여러개의 Query를 UserProcedures 객체에 담아서 실행시키면 Rows는 chain 식으로 연결되어 리턴한다.



둘째 SQL실행시에 SQLException을 던지지 않는다.

이전 글인 예외처리의 예외에서 언급한바와 같이 실험실의 바깥에서는 현실적으로 대부분의 SQLException에 대해 특별한 처리를 할게 없다. 그래서 실제로는 runtime exception은 RepositoryException을 던진다. DB가 다운됐거나 SQL오류등으로 혹은 해당 컬럼명이 없어서 SQLException이 발생했을경우 에러 페이지를 보여주고 로그에 기록하는 것 말고 대체 무엇을 할수 있을까? 다른 무언가를 해야 한다고 하더래도 아마도 그 상대적인 확률은 아주 낮을 것이다.

여기서 중요한건 클라이언트 코드 작성자가 선택권을 가지는 것이다. 99%의 A와 1%의 B가 있다면 A를 기본값으로 하고 B도 필요하다면 할수 있는 방법이 상식적이지 않을까? 그리고 만약 반대로 1%의 A와 99%의 B 환경이 있다면 그에 대한 대책도 준비해 줘야 한다. checked exception을 처리할 확률이 작다면 그냥 runtime exception을 catch 하는게 좋지만 오히려 checked exception으로 해야할 경우가 더 많다면 Facade인 DBController을 재정의 하면 된다.

그러나 Facade인 DBController가 쿼리 실행시 SQLException을 감춰버릴 수 있었지만 리턴값의 Rows의 상위 인터페이스인 java.sql.ResultSet은 모든 메소드가 SQLException을 던지도록 되어 있고  이 규약을 깨는것은 혼란을 가져올수 있으므로 좋지 못하다. 다른 어떤 방법이 있을까?

여기서는 다시 두가지 대안을 제시한다
하나는 역시 ResultSet을 상속받지만 모든 메소드에 SQLException을 반환하지 않는  new NRows(Rows rows, RuntimeException ex) 의 컴포지션 객체를 제공하는 방식이고
두번째는 handler를 통해 java의 일반 객체로 바꾸는 방식이다. (이는 다음에 설명)
   


이쯤 되면 슬슬 프레임워크가 기본적으로 갖춰야 할 미덕이 보인다.
첫째 간결한 API가 좋다. 만약 1-2-3 단계를 거쳐서 무언가를 해야한다면 한단계로 줄이는 걸 고민해라.
둘째 빈도의 차이가 발생한다면 확률이 높은 쪽을 기본값으로 한다.
셋째 그래도 클라이언트는 항상 선택할 수 있어야 하고 때에 따라서는 확률이 바뀌는 환경도 고려해야 한다.

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

Framework -커서(ForwardOnly, Sensitive(=Static))  (0) 2009.03.12
Framework (Handler)  (0) 2009.03.08
Framework (IQueryable)  (0) 2009.03.06
Framework (DBManager)  (0) 2009.03.04
Framework (구조적 중복 제거)  (0) 2009.02.21
Posted by bleujin