iBATIS 쿼리 Log4j로 로그 남기기

2009/03/09 13:37
크리에이티브 커먼즈 라이선스
Creative Commons License
Log4j는 해당 카테고리를 통해 살펴보면 되고 .. 간단하게 지난번 사용한 DAO의 사용 방법을 알아보자. dao코드를 그대로 사용하면된다. 어차피 우리가 쿼리를 조작하는 방법은 없으니까 .. 그냥 log4j설정만 씌어주면(?)된다.

1. log4j.jar를 프로젝트에 추가한다.
2. log4j.perperties설정을 등록 한다.

log4j.peoperties샘플

3. 실행 한다.

실행


저작자 표시 비영리 변경 금지

'Dev > iBATIS framework' 카테고리의 다른 글

iBATIS 쿼리 Log4j로 로그 남기기  (0) 2009/03/09
iBATIS DAO 사용하기  (0) 2009/03/06
iBATIS 캐시 모델  (0) 2009/02/24
iBATIS의 DynamicQuery 사용하기 #2  (0) 2009/02/17
iBATIS의 DynamicQuery 사용하기 #1  (0) 2009/02/17
iBATIS 트랜잭션의 종류와 특성  (0) 2009/02/03

Pupustory Dev/iBATIS framework

iBATIS DAO 사용하기

2009/03/06 17:39
크리에이티브 커먼즈 라이선스
Creative Commons License
iBATIS는 본래 sql mapping framework인데 DAO를 사용할 수 있는 방법도 제공 한다. iBATIS를 설명할때 비교했던 방식이 바로DAO방식이다. DAO에 대한 자세한 설명은 다른 글을 통해 습득하는게 더 나을듯 하고, 간단하게 비교하면 다음과 같다.

[DAO] ? [iBATIS] ?
   DAO iBATIS
쿼리 작성
일반적으로 소스에 직접 작성
정형화된 XML에 작성
쿼리 변경
1. 소스에 쿼리 수정
2. 컴파일
3. 업로드 후 WAS재시작
1. XML 쿼리 수정
2. 업로드 후 WAS재시작
(혹은 안하는 방법도 있음)
쿼리 유연성
복합적인 조건이 들어가는 경우 쿼리를 직관적으로 이해하기 힘듬
복잡한 조건에도 직관적으로 이해하기 유리함

즉. 가장큰 차이로는 쿼리의 수정하는 부분에서 복잡한 소스에서 호출된 메소드를 찾아 변경하는 것 보다 유연하다는 것이다. 쿼리부분에서도 복잡한 조건에 유연한 대응이 가능하다.

하지만 이러한 iBATIS에서도 때론 프로젝트에 따라 DAO방식과 같이 사용해야 하는 경우가 있을 수 있다. 이에 iBATIS는 DAO부분을 지원하는 것 이다.

dao.xml !
iBATIS를 DAO로 사용하기 위해선 dao.xml설정파일이 필요 하다. 기본적으로 dao.xml에선 DAO 객체의 정보를 정의한다. 그렇다면 커넥션 풀과 같은 설정은...? 물론 이전에 사용했던 SqlMapConfig.xml에서 정의한 트랜잭션 관리자를 이용할 수도 있다.
dao.xml
<transactionManager>
타입
프로퍼티
설명
EXTERNAL
ExternalDaoTransctionManager
외부에서 개발자가 자체적으로 구현. iBATIS에선 트랜잭션 구현 안함.
HIBERNATE
HibernateDaoTransctionManager
하이버네이트에 트랜잭션 위임
JDBC
* DataSource
* JDBC.Driver
* JDBC.ConnectionURL
* JDBC.Username
* JDBC.Password
* JDBC.DefaultAutoCommit
DataSource를 이용한다. JDBC타입은 SIMPLE, DBCP JNDI가 있다.
SIMPLE  iBATIS의 트랜잭션을 사용.
DBCP  jakarta DBCP DataSource를 사용.
JNDI JNDI의 참조를 사용.


JTA
* DBJndiContext
* UserTransction
JTA API를 사용해 트랜잭션 관리.

OJB

OBJ트랜잭션에 위임
SQLMAP
  SQL Maps 트랜잭션에 위임
TOPLINK
  TopLink 트랜잭션에 위임

위 사항중 중요한것은 JDBC와 SQLMAP가 아닐까 싶다. SQLMAP는 이전에 한참 살펴봤으니 예전 포스트를 참조하면 될 것이고, JDBC도 그리 어려운 문제는 없다. 어차피 JNDI에서 가져오는건 .. DBCP를 쓰는 방식이나 SQLMAP에 설정한 방식이나 프로퍼티가 동일하기 때문이다.

JDBC > SIMPLE

URL username등은 워낙 쉬운부분이라 넘어갔다. SIMPLE로 할 경우 iBATIS framework가 처리해 주는 부분이라 의외로 많은 설정이 필요하지만 실제 업무에 사용하는 부분은 커넥션 생성갯수와 유휴정도가 아닐까 싶다.

JDBC > DBCP

DBCP에 대한 기본적인 설정이 있는 후 사용해야 한다.

JDBC > JNDI


JNDI에 등록된 풀을 이용한다. 만약 컨텍스트에 초기화 파라미터가 있다면 위와같이 context. 을 이용해 프로퍼티를 적용할 수 있다.

이제 핵심부분인 dao.xml을 살펴보자. 설정에 대한 설명은 끝났으니 dao에서 가장 중요한 부분이다.
<dao interface="pupustory.ibatis.dao.ISimpleDAO"
 implementation="pupustory.ibatis.dao.SimpleDAOImpl" />
 interface는 우리가 흔히 사용하는 interface다. implementation은 실제 구현된 클래스를 의미한다. 즉 구현된 클래스에 interface를 레퍼런스로 사용하는 것 이다. 따라서 구현체에 접근해야 하는 부분은 interface에 등록하고, 구현관계(implements INTERFACE)가 되야 한다.

음. 이것은 사실 OOP와도 관계있는 말인데 .. 레퍼런스가 interface여야 하는 이유(혹은 좋은이유)는 .. 워낙 많은 블로그에 포스트 되어있으므로 생략 하겠다.

그럼 실제 구현된 코드를 살펴보자. 포스트가 길어지므로 이것은 접어서 작성하겠다.

코드 보기


해깔리기 쉬운것은 찾아가는 dao는 interface로 찾는 것 이다. 컨텍스트가 많은 경우 (혹은인터페이스는 같지만 무엇인가 틀릴경우) 컨텍스트 id값을 추가해 주는것이 좋다.


저작자 표시 비영리 변경 금지

'Dev > iBATIS framework' 카테고리의 다른 글

iBATIS 쿼리 Log4j로 로그 남기기  (0) 2009/03/09
iBATIS DAO 사용하기  (0) 2009/03/06
iBATIS 캐시 모델  (0) 2009/02/24
iBATIS의 DynamicQuery 사용하기 #2  (0) 2009/02/17
iBATIS의 DynamicQuery 사용하기 #1  (0) 2009/02/17
iBATIS 트랜잭션의 종류와 특성  (0) 2009/02/03

Pupustory Dev/iBATIS framework

iBATIS 캐시 모델

2009/02/24 17:58
크리에이티브 커먼즈 라이선스
Creative Commons License
iBATIS의 캐시모델은 지원하는 타입은 여러가지가 있는데 먼저 캐시를 사용하는 경의 예를 들자면 빈번하게 조회되는 부분은 있지만 삽입, 수정, 삭제가 일어나지 않는 것을 꼭 DBMS에 가서 데이터를 가져와야 하는가 하는 부분이다.

예를 들면 우편번호가 그러할 것이다. 회원 가입을 할때 주소 항목이 있다. 우편번호 조회를 하려고 하는데 우편번호가 삽입,수정,삭제 되는 경우는 극히 드물다. 이러한 경우 1000명의 사용자가 동시에 회원가입을 한다고 했을때 비지니스 로직에선 1000번이나 같은 작업을 수행하게 된다.

이럴때 캐시 모델을 사용하면 데이터가 캐싱되어 저장하고 있다가 DBMS를 가지 않고 저장하고 있는 데이터를 보여주게 된다. 따라서 성능향상에 도움이 될 것이다. 하지만 빈번한 삽입,삭제,수정이 이루어 지는 화면(혹은 쿼리)이라면 오히려 캐시 사용은 독이 될 수 있다.

캐시 모델 속성 (*는 필수)
 속성 설명
 id (*)

 캐시모델의 유일한 이름.
쿼리 맵핑에서 이 속성을 참조 함
 type (*)
 캐시 모델 타입. MEMORY, FIFO, LRU, OSCACHE가 있음
readOnly 캐시를 읽기 전용으로 설정.
읽기 전용은 프로퍼티를 바꿀 수 없음
serialize
 캐시의 내용을 가져올때 레퍼런스를 가져올지, 데이터를 새로 복사해 가져올지를 선택

캐시 모델 타입(type *)
 타입 설명
MEMORY 가비지 컬렉터가 삭제할때 까지 사용 함.
FIFO  First In First Out에 의해 캐시 항목 삭제 함.
LRU  Least Recently Used에 의해 캐시 항목 삭제 함.
OSCACHE
OpenSymphony Cache를 사용 함.

캐시 모델의 readOnly속성과 serialize속성
 readOnly와 serialize는 같이 봐야할 부분이다. 먼저 readOnly는 캐시모델을 읽기 전용으로 수정하므로 캐시 내용을 변경할 수 없다. 복잡한 트랜잭션으로 묶인게 아니라면 캐시 내용을 변경하며 사용하는 것이 더 효과적이다. serialize는 캐시 내용의 '레퍼런스'를 가져오는지 아니면 내용을 복사해 오는지의 얘기다.

 두 옵션의 가장 효과적인 방법
1. readOnly=true serialize=false
2. readOnly=false serialize=true
(이외의 경우는 사용할 경우가 필자 생각에도 거의 없으므로 넘어가도록 하겠다.)

1번의 경우 가장 빠르게 캐시 내용을 가져올 수 있다. 캐시 수정이 불가능한 readOnly를 이용했고, 캐시 내용을 별도로 복사하지 않고 가져오기 때문이다. 하지만 동일안 인스턴스를 이용할 때 문제가 되는 경우라면 주의해야 한다.

2번의 경우 캐시 내용을 readOnly로 수정할 수 없고, 캐시 내용을 복사해 가져오므로 성능엔 괜찮은 편이지만 1번의 경우보단 좋지 못하다.

캐시 내용 삭제 룰
 태그 설명
<flushOnExecute> 지정한 쿼리 맵핑을 사용할 때 내용을 삭제 함.
<flushInterval> 지정한 시간이 지나면 내용을 삭제 함.

<flushOnExecute>는 statment속성 하나만 가지고 있다. 이 속성에 지정된 맵핑 구문이 실행될 때 캐시 내용을 삭제한다. 삭제하는 내용은 일부분이 아닌 모두 삭제 한다.

<flushInterval>은 정해진 시간 후 캐시를 삭제 한다. 속성은 hour, minutes, seconds, milliseconds가 있으며 스케쥴러 같은 작업을 이용한 정해진 시간 때 삭제 등의 기능은 제공하지 않으며, 순수 캐시된 시간을 기준으로 지정한 시간 후 삭제 한다.


cache type=MEMORY
위의 표에서 설명한데로 MEMORY는 가비지 컬랙터(이하 GC)에 의해 결정되는데 추가적으로 레퍼런스 타입이 존재 한다.

<property name="reference-type" value="WEAK" />
WEAK는 MEMORY타입 중 가장 빨리 제거 된다. GC가 메모리를 가져갈 때 어떠한 조건도 없이 가져가게 내버려 둔다. 따라서 DBMS에 다시 가서 조회해 온 후 캐시하는 경우가 빈번하게 일어날 것이다.

<property name="reference-type" value="SOFT" />
WEAK와 마찬가지로 GC가 제거하지만, 메모리의 여유가 있을땐 놔둔다. 만약 메모리의 용량이 오버되는 경우 이것을 삭제 한다.

<property name="reference-type" value="STRONG" />
SOFT와 마찬가지지만 메모리의 한계에 도달해도 삭제하지 않는다.

LRU
LRU 캐시 모델은 가장 오랫동안 사용하지 않은 캐시를 제거하는 방식이다. 프로퍼티는 'size'가 있고, 정해진 크기가 넘어갈 경우 가장 오랫동안 사용하지 않은 캐시를 삭제 한다.

FIFO
FIFO 캐시 모델은 먼저 들어온 정보부터 제거하는 방식이다. 프로퍼티는 'size'가 있고, 정해진 크기가 넘어갈 경우 가장 먼저 들어온 정보부터 제거 한다.

OSCACHE
이것은 Open Sysmphoy의 OSCache 2.0제품을 사용한다. 여기선 넘어가겠다.

캐시의 사용으로 성능 향상을 기대할 수 있다. 위에서 언급한 대로 빈번한 자료 변경이 일어나는 경우 캐시 모델은 오히려 독이될 수 있다. 하지만 자료의 변경이 적고, 호출 빈도가 높다면.. 예를들어 거의 정적인 데이터로 취급되는 DB정보라면 매우 유용할 것 이다.

크게 2가지로 일정 시간마다 캐시 갱신 및 변경이 일어날 경우 캐시 삭제의 모델 역시 적합한 분야에 적용한다면 유용한 아이템이 될 것이다.


저작자 표시 비영리 변경 금지

'Dev > iBATIS framework' 카테고리의 다른 글

iBATIS 쿼리 Log4j로 로그 남기기  (0) 2009/03/09
iBATIS DAO 사용하기  (0) 2009/03/06
iBATIS 캐시 모델  (0) 2009/02/24
iBATIS의 DynamicQuery 사용하기 #2  (0) 2009/02/17
iBATIS의 DynamicQuery 사용하기 #1  (0) 2009/02/17
iBATIS 트랜잭션의 종류와 특성  (0) 2009/02/03

Pupustory Dev/iBATIS framework

iBATIS의 DynamicQuery 사용하기 #2

2009/02/17 17:35
크리에이티브 커먼즈 라이선스
Creative Commons License
이전에 살펴본 DynamicQuery부분에 사용한 옵션 외에 다양한 옵션이 제공 된다. 일반적으로 이런 옵션들을 이용할때 DAO에 쿼리가 박혀있을 경우 매우 복잡해진다. 따라서 스토어드 프로시저를 이용한다거나 하는 방식을 택하게 되는데, iBATIS에서 지원하는 옵션들을 살펴보자

그냥 눈으로만 봐도 충분히 숙지할 수 있는 사항이므로 별도의 설명은 하지 않겠다. 조건에 property는 당연히 들어가는것이 원칙이다. 비교할 대상이나 검사할 대상이 없다면 뭐하러 이런 조건을 사용 하겠는가? 어렵지 않은 내용이지만 한가지 숙지해야 할 사항이 있다.

바로 이항연산자 부분에서 compareValue와 compareProperty인데 다른 부분은 property를 이용해 비교했다. 같은 property가 들어가는걸로 보아 compareProperty는 프로퍼티 중에서 비교하는 것을알수 있다.즉 '같이 넘어온 객체의 프로퍼티와 비교'하여 구문을 추가할지 선택하는것이다.

compareValue는 정적인 값이다. 예를들어 조건이 '성적이 100점 이하'일 경우 100은 정적인 값이다. 만약 이것을 compareProperty로 이용한다면 사용할때마다 '100'의 값을 셋팅해야할 것이다. 이럴때  compareValue를 이용하면 프로그램 코드에 별도로 관리할 필요가 없으므로 유용하게 사용할수 있다. 그럼 예제를 살펴보자.
boardManager.xml
BoardBean.java

 BoardBiz.java
StartApp.java

조회조건에 들어올 값을 확인하고, null이 아닐경우 검색해서 출력한다. equal같은 검색조건을 추가하려 했지만.. 귀찮기도해서 --; 어차피 사용법은 동일하다. 그리고 dynamic부분의 prepend는 삭제했다. 이유는 이미 join을 하기위해 where를 호출했고, 뒤에 올 조건이 있을지 없을지 미지수다. 따라서 삭제 했다. 1=1은 게시 전에 몇가지 만지작 거리다 남은 코드이므로 무시해도 무관하다.

단항,이항 검색조건은 위와 같이 수행하면 되므로 어려울 것이 없다. 그렇다면 이번엔 이터레이터에 대해 알아볼 것이다. 이전에 설명 했지만 만약 사용자가 ' where board_id in (....) '와 같이 몇건이 들어올지 모르는 경우에 유용 하다.

이부분은 $$를 이용한 statment를 이용할 수도 있겠지만 이건 매우 위험하다. sql injection때문에 그렇다. 어플에서 .replace했다면 상관 없겠지만 그래도 프레임워크를 이용하니 .. 기능을 이용해 보도록 하자.

먼저 IN ( ...) 부분엔 정확히 얼마의 문자가 올지 모른다. 한가지 유추할 수 있는 것은 여기엔 배열이 적합한 것 이다. 새로운 예를 만드는 것 보다 위의 코드를 조금 변경해 작성하도록 하자. 먼저 빈즈에 POST_ID값이 저장될 배열 변수를 하나 추가한다.

이제 중요한 sqlmap부분이다.

반복 부분을 iterator를 사용했다. 실행코드를 살펴보자.
 
어려운 구문은 없다. xml에서 open부분에 in ( 를 추가 했고, 배열마다 구분문자는 ','로 지정했다. 완료 후 ) 로 닫으니
SELECT * FROM TB_QNA_BOARD WHERE POST_ID IN ('1','2','4','5','7','9');
와 같은 sql 구문이 생성 된다. resultMap에선 실제 우리가 가져와야 할 빈의 변수와 맵핑하면 된다.

저작자 표시 비영리 변경 금지

Pupustory Dev/iBATIS framework

iBATIS의 DynamicQuery 사용하기 #1

2009/02/17 13:42
크리에이티브 커먼즈 라이선스
Creative Commons License
DAO를 이용할 경우 쿼리를 작성 후 DAO 클래스에 직접 하드코딩 하는 경우가 많다. 뭐 XML을 이용한 다른 프레임워크를 사용할 수도 있겠지만 .. 때론 쿼리 작성을 동적으로 이용해야 하는 경우가 있다. 이번에 살펴볼 사항은 iBATIS에서 이용하는 동적 쿼리 부분이다.

동적쿼리 ?
동적쿼리는 말 그대로 쿼리문이 정적이지 않고 동적이단 얘기다. ....... 풀어말하면 모습이 변할 수 있다는 얘기다. 설명보다 직접 코드를 보며 얘기하는게 더 이해가 빠를 것 같다.

본 포스트에선 아주 간단한 QNA형 게시판을 하나 작성하고, 프로그램에서 '일반 게시물'과 '댓글 게시물(부모 게시물이 있는)'을 가져오는 방법을 사용할 것이다. 일반적으로 이것은 2개의 쿼리로 하는 경우가 대부분이지만 단순한 예제로 설명하기 위한 것 이니 대충 넘어가자. --b

먼저 테이블 생성 쿼리문이다.이 테이블은 여기서만 사용할 예정이다. 직접 안해보고 눈으로만 봐도 충분할 것 같다.

sqlmap부분이다. 조회부분에서 사용된 'select.dynamic.query'가 이번에 살펴볼 dynamic query부분이다. 설명은 차후에 하고 이제 코드를 살펴보자.
DynamicQueryBiz.java
PostReplyBean.java
startApp.java

처음에는 가상의 1000개 데이터를 삽입 한다. 후에 10개의 데이터를 for문으로 가져오는데 bean.setPostId()를 통해 1~1000까지의 아이디중 하나를 무작위로 뽑고 설정한다.

뒤에 오는 부분에 주목하자. 후에 nextBoolean()을 통해 bean.setParentPostId()에 값을 넣을지 말지 정한다. 여기서 값이 들어가면 '부모가 있는 답변글을 가져오는 쿼리'를 선택하고 그렇지 않으면 위에서 삽입한 Post_Id를 기준으로 가져온다.
(예제로 하기엔 좋지 않지만.. 대충 이렇다는 것만 이해하면 되니까 !)

출력 결과


결과는 잘 출력된다. 이제 핵심적인 sqlmap부분을 살펴보자.

  <dynamic prepend="WHERE " open="" close="">
dynamic query 부분이다. prepend는 말 그대로 앞에 붙힐 문장이다. 만약 여기에 where가 없다면 어떻게 될까? 여기엔 없지만 조건에 따라 조회조건이 없는 경우 where 이후 부분이 없으므로 에러가 날 것이다. 따라서 여기에 where를 붙힐 것이다. (후에 다루겠지만 앞에 붙힌 prepend를 삭제하는 속성도 있다.)

open은 prepend 다음에 오는 구문이다. 예를들어 조건이 WHERE IN ( .. .. .. .. ) 라고 할 경우 조건에 맞아 문장이 삽입될때 '('를 추가시켜 줘야 할 것이다. 이때 사용하는것이 open속성이고, 후에 닫는 ')'부분은 close에서 사용한다.

   <isNull property="parentPostId">
    post_id = #postId#
   </isNull>

위 예제는 bean을 던졌는데 bean의 속성 중 parentPostId 부분이 null인지 검사하는 부분이다. java구문으로 하자면 if (bean.parentPostId == null) 쯤 될 것이다. 만약 null 이면 하단의 구문이 추가(동적으로) 된다.

   <isNotNull property="parentPostId">
   parent_post_id = #parentPostId#
   </isNotNull>
  </dynamic>

위 구문과 반대되는 부분이다.

일반적으로 코드에서 동적쿼리를 수행하는 경우 여러 조건을 나열하며 sql을 작성해야 할 것이다. 그 조건을 iBATIS상에서 만족시키기 위해 위와같은 유용한 기능이 제공된다.

이 예제는 여기서 끝이지만 위와같은 조건은 다음 포스트에서 더 자세히 알아볼 것이다. 단항, 이항연산자 등 다양한 옵션이 있다.

저작자 표시 비영리 변경 금지

Pupustory Dev/iBATIS framework

iBATIS 트랜잭션의 종류와 특성

2009/02/03 14:55
크리에이티브 커먼즈 라이선스
Creative Commons License
이전에 살펴본 트랜잭션은 iBATIS에서 지원하는 기본적인 트랜잭션을 이용했다.iBATIS에서는 총 4가지의 트랜잭션을 분류 한다. 그 특성은 다음과 같다.

 자동 트랜잭션
로컬 트랜잭션
글로벌 트랜잭션
사용자 정의 트랜잭션
 한개의 sql구문 수행



다중의 sql구문(update, insert등의 복합적으로 적용되는)을 단일 DB에 수행
다양한 SQL구분을 여러 DB혹은 잠재적으로 DB가 아닌 JMS 큐, JCA 커넥션 등에 수행
 사용자가 별도로 관리




*. 자동 트랜잭션
단순히 한개의 쿼리를 구행할때 수행된다. 별도로 설정하진 않고 사용 했을 경우 해당 된다. 사실 트랜잭션이라 할 수 있지만 엄밀히 따지면 '과연 그런가?'도 싶다.

*. 로컬 트랜잭션
이전 포스트에서 살펴본 내용이 로컬 트랜잭션이다. 자동 트랜잭션도 로컬 트랜잭션에 포함된다. 틀린점이 있다면 로컬 트랜잭션은 여러 구문의 트랜잭션을 수행하고, 자동은 한건만 수행하는 차이점이 있다.

예를들자면 이런 코드가 될 것이다.


*. 글로벌 트랜잭션
(작성 대기)

*. 사용자 정의 트랜잭션
이름 그대로 트랜잭션 관리를 iBATIS의 기능이 아닌 손수 사용하는 의미이다.  iBATIS를 이용하지 않고 트랜잭션 처리를 할땐 보통 오토커밋 옵션을 false하고 처리 후 문제가 없다면 commit, 문제가 있다면 catch에 rollback를 이용하는 식으로 사용했다. 이것으로 이용한 것이 '사용자 정의 트랜잭션'이다.



커넥션을 가져오고, 그것을 넘거 session을 받는다. 사용은 동일하다. 커밋여부 역시 일반적으로 사용해오던 방식으로 사용 한다. 상단에서 dataSource를 통해 Connection을 가져오는 것은.. JAKARTA DBCP를 이용할때 사용한 방식으로 설정하면 된다.

필자는 개인적으로 이렇게 사용하는 경우가 흔할까 .. 하는 생각이 든다. framework를 맹신하는건 문제가 있지만, 그래도 framework를 도입 했다면, 그 기능의 문제나 비지니스가 어쩔수 없는 경우를 제외하곤 framework의 철학에 맞춰 개발하는게 맞지 않나 생각 한다.

(물론 비지니스를 어거지로 framework에 맞추는건 문제가 있다. 더 빠른 길이 있고, framework의 방식에 위배된다면, 더 빠른길을 선택하는게 맞다. 하지만 이런 트랜잭션 처리 부분에선 그런 문제가 거의 발생하지 않을듯 하다.)


저작자 표시 비영리 변경 금지

Pupustory Dev/iBATIS framework

iBATIS의 트랜잭션, 대량 데이터 일괄처리(배치)

2009/01/23 14:59
크리에이티브 커먼즈 라이선스
Creative Commons License
여러가지 비지니스에서 넘어온 정보를 DB에 처리할 업무가 많은 경우 DAO에선 preparedStatment를 받아 반복문을 통해 계속 쿼리를 보내는 패턴을 사용한다. 물론 트랜잭션으로 묶어 처리 한다.

예를들어 한번에 100명의 정보가 들어오는데 100명중 단 1명이라도 처리도중 문제가 발생해 정상처리 되지 않는다면 나머지 99명의 데이터도 처리되어선 안되고 이전상태로 되돌려야 할 경우가 그렇다. 이런경우 흔히 사용하는게 Connection.autoCommit(false);를 이용해 자동으로 커밋되는것을 해제. 처리 후 정상처리 되었을 경우  Connection.commit();하고 그렇지 않고 에러가 나거나 할 경우 Connection.rollback();하여 처리하는 경우다.

iBATIS에서도 이러한 트렌잭션을 지원하는데 방법을 구현하기 위해 기존에 사용한 Manager를 조금 수정하고 작성했다.

DBManager.java

TransactionBiz.java

StartApp.java

트렌잭션 시작과 종료(커밋하지 않으므로 롤백. 적용안함.) 적용(commit)를 위해 외부에 호출하는 메소드를 추가했다. 평소 DAO에서 많이 사용해온 방식대로 트랜잭션을 시작하고 처리가 정상적으로 끝나면 트랜잭션 커밋을 수행 한다. 단, 예외적 상황이 발생할 경우 트랜잭션을 커밋하지 않고 종료 한다.

이번엔 좀 더 일괄처리에 유용한 방법을 말하고자 한다. 바로 일괄처리(배치) 부분인데 .. 흔히 알고있는 배치라 함은 메인과 별도로 백단에서 작업을 처리하고, 처리완료 후 결과를 알려주는 방식이다. 보통 처리량이 많을 경우. 혹은 사용자가 오래걸리는 작업을 수행하고 계속 완료되길 기다릴 수 없는 경우에 이러한 방법을 많이 사용 한다.

이와 조금 다르게 iBATIS에선 별도로 백단에서 수행되는 것은 아니다. 하지만 데이터 량이 많은 정보를 하나의 트랜잭션을 물고 일일이 누적시키는게 아니라 실제 쿼리 수행은 하지 않고 작업을 준비하다 executeBatch();를 수행하면 이때 작업을 일괄적으로 수행 한다.

따라서 배치를 처리하는 중에는 실제 sql을 수행하지 않으므로 작업 내용의 SQLException등이 발생하지 않는다.(실제 db에 수행하지 않는다.) 처리 후 executeBatch()를 만나 배치를 수행할때 실제 쿼리가 수행되는 것이다. 코드는 다음과 같다.

TransactionBiz.java

그외의 코드는 위와 같아 별도로 작성하지 않았다. 단순히 트랜잭선 내부에서 블럭을 이루듯 배치를 시작하고, 배치를 실행하는 두개의 문장만 추가 되었다. 배치를 통해 성능향상이 어느정도 되었는지 확인하기위해 결과값을 보면 다음과 같다.

 배치작업 적용 전
9702 ms
 배치작업 적용 후
2546 ms

시스템에 따라 약간의 차이는 있을 수 있겠지만 1만건을 기준으로 이정도의 성능 차이라면 대단히 큰 차이라 할 수 있다. 데이터 량이 많은 경우 배치작업은 필수라 할 수 있다. iBATIS에선 그것마저 두줄의 코드로 구현이 가능하다.
저작자 표시 비영리 변경 금지

Pupustory Dev/iBATIS framework

  1. Blog Icon
    해적이

    iBATIS 관련된 내용이 많은 도움이 되네요^^ 고맙습니다~ ^^
    질문이 하나 있는데요.. 올려져 있는 DBManager.java를 그대로 적용해 보았습니다.
    그런데 적용해서 컴파일을 하면 문제없이 되는데요.. Runtime을 하게 되면 java.lang.NullPointerException이 떨어지더군요.. startTransaction()에서요...
    왜 이렇게 NullPointerException이 떨어지는지 아무리 생각해도 잘 모르겠어서 이렇게 질문을 남깁니다~
    좋은 자료 내용 감사합니다~

  2. 음~ 글쎄요~
    코드는 문제 없어을텐데;
    소스를 제가 볼수 있을지요~

iBATIS의 설정과 기본 CRUD 살펴보기

2009/01/22 14:34
크리에이티브 커먼즈 라이선스
Creative Commons License

이전 포스트에서 우리는 iBATIS를 이용하기 위해 설정파일(SqlMapConfig.xml)sqlmap파일(UserManager.xml) 을 간단히 작성해 봤다. 이제 이부분에 대해 좀 더 세밀한 부분을 얘기하고자 한다.

SqlMapConfig.xml ?
이것은 iBATIS를 이용하는데 가장 중요한 부분이다. 간단하게 이용하려고 빠진부분이 많이 있다. 그중 <setting /> 부분이 있는데 이것은 나중에 다루도록 할 것이고, 예제에서 진행한 부분에 대해서만 얘기하고자 한다.

SqlMapConfig.xml

db.properties

SqlMapConfig.xml에 작성된 properties resource는 db정보를 관리하는 프로퍼티 설정 파일을 읽는 부분이다. 예상했다시피 ${key} 부분을 통해 프로퍼티 파일의 값을 가져온다. 물론 직접 작성할 수도 있다. 직접 작성한다면하단의 소스가 될 것이다.

<sqlMap resource="UserManager.xml"/> 부분은 우리가 작성한 sql map의 파일을 서술한다. 마찬가지로 직접 작성이 가능하지만 차후 프로그램이 커졌을때 유지보수를 위해 별도로 관리하는 것이 편리 하다.

sqlMap ? 

이제 sqlmap부분을 살펴보자. 부분 부분의 세밀한 설명을 위해 파일의 주요 내용은 잘라서 설명하기로 한다.


SqlMapConfig.xml
UserManager.xml

c에서 많이 보던 namespace이다.  sqlmap는 내부에서 하나의 큰 덩어리로 관리 되므로 각각의 영역별로 namespace를 이용하는것이 도움이 된다. 

select


UserManager.xml

친절하게도 iBATIS는 우리가 기존에 DAO pattern에서 이용해 왔던 bean으로 반환해 주기도 한다. 이것은 단건일 경우의 얘기고 다건은 List에 담아서 보내준다.(이전에 봤던것 처럼 List.get(int)를 통해 얻을 수 있다.) CRUD시 발생하는 반환값, 혹은 파라미터 값을 bean의 표준에 맞게 작성하고 이것을 이용할때 <select id="sample" resultClass="java.lang.String">와 같이 full path를 써야하는 불편함을 해소해주는 기능을 한다.

UserManager.xml


이것의 결과는 과연 몇건이 될 것인가? 답은 '알수 없다'. 없거나 한건, 혹은 다건이 출력될 수도 있으므로 bean에 담는건 무리가 있다. 따라서 이것은 iBATIS가 List형식으로 반환해 줘야 하는데 이때 담긴 bean의 정보를 등록한다. resultMap는 '결과를 받을때 다건이니 map형식으로 주세요. 단 그안의 내용은 이것과 같습니다.'라는 의미다.

property부분은 bean의 변수, column은 실제 db 컬럼명이다.

UserManager.xml

select를 수행하는 부분이다. id를 통해 프로그램에서 접근하며 반환이 int값이므로 int로 기입했다.

* 단! iBATIS는 프리미티브 타입을 지원하지 않는다.(int, double, boolean ..) 따라서 여기서 기입한 int는 java.lang.Integer를 의미한다. 사실 이부분은 resultClass="java.lang.Integer"로 서술해도 동일한 효과이다. 내부적으로 int가 "java.lang.Integer"로 typealias되어 있기 때문에 우린 int로 사용할 수 있다.

물론 결과는 Object 타입의 Integer이므로 이전 포스트에서 사용한것과  마찬가지로 캐스팅 후 사용해야 한다.
(단,bean은 프리미티브 타입으로 작성된다. resultMap에 기입한 내용에 따라 ..)

insert
UserManager.xml

insert를 수행한다. parameterClass="UserBean" 로 서술한대로 우리는 정보가 있는 UserBean을 넣을 것이다. value 부분에 #userid# .. 이러한 부분들은 실제 값이 맵핑되는 부분이다. 마찬가지로 bean의 변수 그대로 기입해주면 된다.

*단! 여기서 #userid#가 아닌 $userid$도 사용할 수 있다. 이것은 preparedStatmentStatmtent의 차이인데 $$는 Statment를 사용하므로 SQL Injection 공격에 취약하다는 것을 염두해 둬야 한다.

update

UserManager.xml


update를 수행한다. insert에서 살펴본 것과 비슷하게 사용 한다.

지금까지 살펴본 사항은 select의 다건 출력을 제외하곤 모두 resultClass를 이용해 왔다. iBATIS에선 java.util.Map 타입을 이용해 사용할 수도 있다. 위의 insert 같은 내용을 Map 타입을 통해 이용하는 방법은 다음과 같다.

java.util.Map를 이용한 insert

UserManager.xml

StartApp.java

실행되는 풀 소스는 작성하지 않았지만, 일단 저런식으로 작성이 가능하다는 예로 작성했다. 반환값도 마찬가지로 값을 가져올 수 있다. 단, 반환에 대한 key는 db의 컬럼명을 통해 가져오면 된다. 사실 Map을 이용할땐 이 변수의 형식을 알 수가 없다. 즉 db에 DATE형식인데 이러한 것은 #value:DATE#와 같이 :TYPE를 기입해 줘야 한다. 여기서 TYPE는 java.sql.Types에 있는 방식을 사용해야 한다.(만약 jdbc타입으로 작성하고자 한다면 #value:jdbcType=TYPE#로 사용하면 된다.)

iBATIS in action 에선 이러한 bean과 map의 장단점은 다음과 같다.

 접근방식 장점  단점 
 자바빈즈 성능
컴파일 시 강력한 타입 검사
컴파일시 이름 검사
IDE에서의 리팩토링 지원
형변환이 줄어듬 
코드량의 증가(get/set)



 
 Map 코드량의 감소



느림
컴파일 시 검사하지 않음
약한 타입
실행시 오류 발생이 잦음
리팩토링 지원 없음 
iBATIS in action (122p)

이렇듯 Map보다 bean을 이용하는 것이 훨씬 이득이 많다. 무엇보다 필자 생각엔 Map로 해서 key를 맵핑할때 이름검사가 제대로 이루어 지지 않아 많은 오류를 격지 않을까 생각 한다. 사실 코드의 양은 반대로 생각해 봐야 하는게 어차피 bean을 만드는 것은 별도로 class를 생성하는 것인데 '코드량을 고민해야하나..' 도 싶다.

insert에서 살펴본대로 우린 SQL Injection을 피하기 위해 ##(PreparedStatment)를 이용하려고 한다. 헌데 '만약 게시물 제목이'바보2MB'가 들어간 모든 게시물'한다고 한다면 어떻게 해야할까? 쿼리를 다음과 같이 작성 했다고 가정 하자.

이럴경우엔 검색 파라미터로 넘어오는 변수에 직접 '%'와일드 카드를 넣어주거나 아니면 앞 뒤에 '%' || title || '%' 식으로 추가해 줘야 한다.


이번엔 sqlMap에서의 코드 재사용(?)을 살펴보자. sqlMap에는 <sql id=""></sql>를 지원하는데 이것은 위에 살펴본 select, insert, update와 같이 외부에서 직접 호출해 사용할 수 없고 컴포넌트 화 해서 이용할 수 있다.  예를들면 이런 코드가 작성 될 수 있다.

별로 좋은 예제는 아니다.(실무에서 이렇게 쓸까 ..) 그냥<sq/>과 그것을 이용하기 위한 <include />를 설명하기 위해서 사용해 봤다. 외부에선 절대 호출할 수 없고, 내부에서 컴포넌트 화 해서 이용하는데 의미가 있다.

저작자 표시 비영리 변경 금지

Pupustory Dev/iBATIS framework

iBATIS 처음 시작하기

2009/01/15 16:48
크리에이티브 커먼즈 라이선스
Creative Commons License
자바 어플들은 DAO패턴을 많이 이용한다. 웹이건 어플이건 MVC패턴을 사용하기도 하거니와 뷰와 비지니스 등의 역할분담은 더이상 강조하지 않아도 되는 부분이 되버렸다.

쿼리를 DAO에 작성하고, 비지니스 레이어에선 맞는 DAO의 메소드를 호출해 결과값을 가져가고 그것을 뷰를 통해 사용자에게 보여지게 된다. 여기서 한가지 살펴볼 것이 있다. 만약 현재 사용자에게 서비스(웹을 기준으로..)가 되고 있고, 쿼리를 수정해야 한다면 어떻게 해야하나 ? 일반적인 DAO패턴을 사용했다면 해야할 것은 다음과 같다.

1. 소스코드의 쿼리 수정
2. 변경된 소스코드 빌드
3. 운영환경에 적용파일 배치
4. WAS 재시작

단 하나의 쿼리를 수정하는데만 해도 이런 복잡한 작업이 진행 된다. 여기서 문제는 '크건 작건 쿼리만 수정되는 부분으로 다시 빌드해야 한다'는 점이다. 그래서 나온것인 iBATIS같은 ORM이다. ORM은 .. 구글링해서 이해하는게 좋을듯 하다. 여기선 iBATIS를 살펴보는 페이지니까 ..(사실 iBATIS가 ORM이라 하기엔 무리가 있다고 개인적으로 생각한다. ORM이라 하면.. 음.. 하이버네이트도 가치 검색해서 두개의 차이점을 확인해 보면 된다.)

개발자는 iBATIS에서 제공하는 dtd를 참조해 xml을 작성하고, 여기에 결과값, 조건에 넘길 파라미터 값 등을 기술한다. 그리고 작성하는 코드에 iBATIS에서 제공하는 api를 통해 기술한 쿼리를 요청하고, 결과값도 돌려받는다. 이렇게 되면 db 쿼리에 관련된 부분은 프로그램에 종속되지 않고 코드도 훨씬 간결해질 것이다. 

먼저 iBATIS를 다운받아 보자.


친절하게도 iBATIS는 java뿐 아니라 .net나 ruby도 지원해준다. 필자는 java개발자므로 java를 기준으로 설명할 것이다. get Software를 통해 다운받자.
(1.4와 1.5버젼이 따로 되어있다. 아마도 1.5에 추가된 for each나 generic의 부분이 아닐지 ..)

먼저 우리가 처음 시도할 테이블 스키마를 생성해 보자(이것은 오라클 기준임..)

TB_PUPUSTORY_USER 테이블 생성

회원 정보를 저장하는 간단한 1개의 테이블이다. 프로젝트에 추가하고 다음을 기준으로 설정파일(xml, properties)을 기술한다.

iBATIS db properties 파일 (db.properties)

iBATIS 설정파일 (SqlMapConfig.xml)

iBATIS sql 설정파일 (UserManager.xml)

이제 코드를 작성하기전에 가장 중요하면서 해맬수 있는 부분을 설명하고자 한다. 보통 웹 어플은 WEB-INF에 넣어두면 상관없다. 하지만 일반 자바 프로젝트라면 과연 이 설정파일들은 어디에 있어야 하는가? 

이클립스에서 자바 프로젝트로 생성하면 소스는 프로젝트 디렉토리의 src, 빌드된 파일은 bin에 저장된다. 여기서 위의 설정파일은 'bin'에 있어야 한다. 하지만 친절한 이클립스는 src에 파일을 넣어두면 빌드할때 src의 파일을 bin에 친절히 복사해 주신다. 따라서 소스관리측면에서도 src에 오는것이 옳다. 이제 코드를 보자


사용할 bean (UserBean.java)


사용할 iBATIS를 이용한 클레스(DBManager.java)


실행 프로그램 (StartApp.java)


실행 프로그램 (MyService.java)


주석을 보면 어렵지 않게 이해할 수 있다. DBManager를 이용해 sql mapper를 싱글턴으로 생성한다. 그것을 MyService에서 mapper를 받아 활용하게 된다. 기본적으로 bean던져 insert를 했고, 1개의 row를 반환하는(로우 수 반환하는 부분)방법도 살펴봤고, 다건(모든 사용자 정보)를 반환하는 방법, 정보를 수정하는 방법도 살펴 봤다.

이제 다음장에서 좀 더 흥미롭고 세밀한 부분에 대해 살펴볼 것이다.
저작자 표시 비영리 변경 금지

Pupustory Dev/iBATIS framework