54.iBATIS, MyBatis/iBatis2015. 7. 23. 12:19
반응형

ex)

SELECT * FROM job where (name1 = 'key1' or name2 = 'key2') and name3 = 'key3'


-------------------------------------------------------

<select id="job" parameterClass="Map">

  SELECT *

  FROM jobs

  <dynamic prepend="WHERE">

   <isNotNull prepend="AND" removeFirstPrepend="true" open="(" close=")" >

      <isNotEmpty prepend="OR" property="key1">

         name1 = #key1#

      </isNotEmpty>

      <isNotEmpty prepend="OR" property="key2">

         name2 = #key2#

      </isNotEmpty>

     </isNotNull>

   <isNotEmpty prepend="AND" property="key3">

   name3 = #key3#

   </isNotEmpty>

  </dynamic>

 </select> 

Posted by 1010
54.iBATIS, MyBatis/iBatis2015. 2. 10. 18:06
반응형

이항연산 요소

property – 비교되는 프라퍼티(필수)

compareProperty – 비교되는 다른 프라퍼티(필수 또는 compareValue)

compareValue – 비교되는 값(필수 또는 compareProperty)

removeFirstPrepend – 첫번째로 내포된 내용을 생성하는 요소의 prepend를 제거(true|false, 선택)

open – 결과적인 전체내용물을 열기위한 문자열(선택)

close – 결과적인 전체내용물을 닫기위한 문자열(선택)

<isEqual> 프라퍼티와 값 또는 다른 프라퍼티가 같은지 체크.

<isNotEqual> 프라퍼티와 값 또는 다른 프라퍼티가 같지 않은지 체크.

<isGreaterThan> 프라퍼티가 값 또는 다른 프라퍼티보다 큰지 체크.

<isGreaterEqual> 프라퍼티가 값 또는 다른 프라퍼티보다 크거나 같은지 체크.

<isLessThan> 프라퍼티가 값 또는 다른 프라퍼티보다 작은지 체크.

<isLessEqual> 프라퍼티가 값 또는 다른 프라퍼티보다 작거나 같은지 체크.

예제

<isLessEqual prepend=”AND” property=”age” compareValue=”18”>

ADOLESCENT = ‘TRUE’

</isLessEqual>

단항연산 요소

단항연산 요소는 특수한 조건을 위해 프라퍼티의 상태를 체크한다.

단항연산 속성:

prepend – statement에 붙을 오버라이딩 가능한 SQL부분(옵션)

property – 체크되기 위한 프라퍼티(필수)

removeFirstPrepend – 태그를 생성하는 첫번째 내포 내용의 prepend제거(옵션)

open – 결과적인 전체내용물을 열기위한 문자열(옵션)

close – 결과적인 전체내용물을 닫기위한 문자열(옵션)

<isPropertyAvailable> 프라퍼티가 유효한지 체크(이를 테면 파라미터빈의 프라퍼티이다.)

<isNotPropertyAvailable>

프라퍼티가 유효하지 않은지 체크(이를 테면 파라미터의 프라퍼티가 아니다.)

<isNull> 프라퍼티가 null인지 체크

<isNotNull> 프라퍼티가 null이 아닌지 체크

<isEmpty> Collection, 문자열 또는 String.valueOf() 프라퍼티가 null이거나

empty(“” or size() < 1)인지 체크

<isNotEmpty> Collection, 문자열 또는 String.valueOf()

<isParameterPresent> 파라미터 객체가 존재(not null)하는지 보기위해 체크.

예제

<isNotEmpty prepend=”AND” property=”firstName” >

FIRST_NAME=#firstName#

</isNotEmpty>

Posted by 1010
54.iBATIS, MyBatis/iBatis2012. 4. 27. 17:15
반응형

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

  1. <!--property가 존재 하는지 검사(map의경우 property를 key로 검색실패) -->
  2. <isPropertyAvailable property=""></isPropertyAvailable>
  3. <!--property가 존재 하지 않는지 검사(map의경우 property를 key로 검색실패) -->
  4. <isNotPropertyAvailable property=""></isNotPropertyAvailable>
  5. <!--property가 null인지 검사 -->
  6. <isNotNull property=""></isNotNull>
  7. <!--property가 null이 아닌지 검사 -->
  8. <isNull property=""></isNull>
  9. <!-- property가 비어있는지 검사("".equals) 검사-->
  10. <isEmpty prepend=""></isEmpty>
  11. <!-- property가 비어있지 않은지 검사(!"".equals) 검사-->
  12. <isNotEmpty prepend=""></isNotEmpty>
  13. <!-- property가 비교값과 같은지 검사-->
  14. <isEqual property="" compareValue="" compareProperty=""></isEqual>
  15. <!-- property가 비교값과 같지 않은지 검사-->
  16. <isNotEqual property="" compareValue="" compareProperty=""></isNotEqual>
  17. <!-- property가 비교값보다 큰지 검사 -->
  18. <isGreaterThan property="" [compareValue="" || compareProperty=""]></isGreaterThan>
  19. <!-- property가 비교값보다 큰거나 같은지 검사 -->
  20. <isGreaterEqual property="" [compareValue="" || compareProperty=""]></isGreaterEqual>
  21. <!-- propety가 작은지 검사 -->
  22. <isLessThan property="" [compareValue="" || compareProperty=""]></isLessThan>
  23. <!-- propety가 작거나 같은지 검사 -->
  24. <isLessEqual property="" [compareValue="" || compareProperty=""]></isLessEqual>
  25. <!-- property를 반복하며 추가. 반복시 값의 사이에 conjunction정의 문자 삽입 -->
  26. <iterate property="" open="(" close=")" conjunction=","></iterate>

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

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

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


boardManager.xml

  1. <typeAlias alias="boardbean" type="pupustory.ibatis.beans.BoardBean"/>
  2. <resultMap class="boardbean" id="boardbean">
  3. <result property="postId" column="post_Id"/>
  4. <result property="postTitle" column="post_Title"/>
  5. <result property="postWriter" column="post_Writer"/>
  6. <result property="postBody" column="post_Body"/>
  7. </resultMap>
  8. <parameterMap class="boardbean" id="boardbean">
  9. <parameter property="postId" />
  10. <parameter property="postTitle"/>
  11. <parameter property="postWriter"/>
  12. <parameter property="postBody"/>
  13. </parameterMap>
  14. <select id="select.board" resultMap="boardbean"
  15. parameterClass="boardbean" >
  16. SELECT
  17. A.POST_ID AS POST_ID
  18. ,A.POST_TITLE AS POST_TITLE
  19. ,A.POST_WRITER AS POST_WRITER
  20. ,B.POST_BODY AS POST_BODY
  21. FROM TB_MAIN_BOARD A, TB_SUB_BOARD B
  22. WHERE A.POST_ID = B.POST_ID
  23. <dynamic open="" close="">
  24. <isNull prepend=" AND " property="postId">
  25. 11=1
  26. </isNull>
  27. <isNotNull prepend=" AND " property="postId">
  28. a.post_id = #postId#
  29. </isNotNull>
  30. <isNotNull prepend=" AND " property="postTitle">
  31. post_title like '%' || #postTitle# || '%'
  32. </isNotNull>
  33. <isNotNull prepend=" AND " property="postWriter">
  34. post_Writer like '%' || #postWriter# || '%'
  35. </isNotNull>
  36. <isNotNull prepend=" AND " property="postBody">
  37. post_body like '%' || #postBody# || '%'
  38. </isNotNull>
  39. </dynamic>
  40. </select>
  41. <insert id="insert.main.board" parameterClass="boardbean">
  42. insert into tb_main_board
  43. values(#postId#,#postTitle#,#postWriter#)
  44. </insert>
  45. <insert id="insert.sub.board" parameterClass="boardbean">
  46. insert into tb_sub_board(post_id, post_body)
  47. values(#postId#,#postBody#)
  48. </insert>
  49. </sqlMap>


BoardBean.java

  1. package pupustory.ibatis.beans;
  2. public class BoardBean {
  3. String postId;
  4. String postTitle;
  5. String postWriter;
  6. String postBody;
  7. public String getPostBody() {
  8. return postBody;
  9. }
  10. public void setPostBody(String postBody) {
  11. this.postBody = postBody;
  12. }
  13. public String getPostId() {
  14. return postId;
  15. }
  16. public void setPostId(String postId) {
  17. this.postId = postId;
  18. }
  19. public String getPostTitle() {
  20. return postTitle;
  21. }
  22. public void setPostTitle(String postTItle) {
  23. this.postTitle = postTItle;
  24. }
  25. public String getPostWriter() {
  26. return postWriter;
  27. }
  28. public void setPostWriter(String postWriter) {
  29. this.postWriter = postWriter;
  30. }
  31. public String toString() {
  32. return "postId["+postId+"]"
  33. +"postTitle["+postTitle+"]"
  34. +"postWriter["+postWriter+"]"
  35. +"postBody["+postBody+"]";
  36. }
  37. }


BoardBiz.java

  1. package pupustory.ibatis.board;
  2. import pupustory.ibatis.manager.DBManager;
  3. import pupustory.ibatis.beans.BoardBean;
  4. import java.sql.SQLException;
  5. public class BoardBiz {
  6. DBManager manager = DBManager.getInstance();
  7. // 가상 데이터 삽입
  8. public void insertVirData() throws SQLException{
  9. BoardBean bean = null;
  10. final int MAX_BOARD_DATA = 10;
  11. manager.startTransaction();
  12. manager.startBatch();
  13. for (int i=0;i<MAX_BOARD_DATA;i++) {
  14. bean = new BoardBean();
  15. bean.setPostId(i+"");
  16. bean.setPostTitle("제목! "+i);
  17. bean.setPostWriter("pupustory"+i);
  18. bean.setPostBody("본문 히히히히 헤헤"+i);
  19. manager.getMapper().insert("insert.main.board", bean);
  20. manager.getMapper().insert("insert.sub.board", bean);
  21. }
  22. manager.executeBatch();
  23. manager.commitTransaction();
  24. }
  25. // 게시물 조회
  26. public java.util.List getPost(BoardBean bean)
  27. throws SQLException {
  28. return
  29. (java.util.List)manager.getMapper().queryForList("select.board",bean);
  30. }
  31. }

StartApp.java

  1. package pupustory.ibatis.board;
  2. import java.sql.SQLException;
  3. import pupustory.ibatis.beans.BoardBean;
  4. public class StartApp {
  5. public static void main(String ar[]) throws SQLException{
  6. BoardBiz biz = new BoardBiz();
  7. //biz.insertVirData();
  8. BoardBean bean = null;
  9. java.util.List list = biz.getPost(bean);
  10. for (int i=0;i<list.size();i++) {
  11. System.out.println(list.get(i).toString());
  12. }
  13. bean = new BoardBean();
  14. // id 3번 게시물 조회
  15. bean.setPostId("3");
  16. list = biz.getPost(bean);
  17. for (int i=0;i<list.size();i++) {
  18. System.out.println(list.get(i).toString());
  19. }
  20. // 본문에 '4'문자 포함한 게시물 조회
  21. bean = new BoardBean();
  22. //bean.setPostId("2");
  23. bean.setPostBody("4");
  24. list = biz.getPost(bean);
  25. for (int i=0;i<list.size();i++) {
  26. System.out.println(list.get(i).toString());
  27. }
  28. }
  29. }


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

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

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

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

  1. private String rePostId;
  2. public String getRePostId() {
  3. return rePostId;
  4. }
  5. public void setRePostId(String rePostId) {
  6. this.rePostId = rePostId;
  7. }
  8. public String toString() {//toString()는 값 확인을 위해 변경 함
  9. return "postId [" +rePostId+"]"
  10. +" postTitle [" +postTitle+"]"
  11. +" postWriter [" +postWriter+"]"
  12. +" postBody [" +postBody+"]"
  13. +" parentPostId [" +parentPostId+"]";
  14. }


이제 중요한 sqlmap부분이다.

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE sqlMap
  3. PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
  4. "http://ibatis.apache.org/dtd/sql-map-2.dtd">
  5. <sqlMap namespace="UserManager">
  6. <typeAlias alias="postreply" type="pupustory.ibatis.beans.PostReplyBean"/>
  7. <resultMap class="postreply" id="postreply.result" >
  8. <result property="rePostId" column="POST_ID" />
  9. <result property="postTitle" column="POST_TITLE" />
  10. <result property="postWriter" column="POST_WRITER" />
  11. <result property="postBody" column="POST_BODY" />
  12. <result property="parentPostId" column="PARENT_POST_ID" />
  13. </resultMap>
  14. <parameterMap class="postreply" id="postreply.param">
  15. <parameter property="postId" />
  16. <parameter property="postTitle"/>
  17. <parameter property="postWriter"/>
  18. <parameter property="postBody" />
  19. <parameter property="parentPostId"/>
  20. </parameterMap>
  21. <select id="select.dynamic.query" resultMap="postreply.result"
  22. parameterClass="postreply" >
  23. SELECT * FROM TB_QNA_BOARD
  24. <dynamic prepend="WHERE">
  25. <iterate property="postId"
  26. conjunction="," open="post_id IN (" close=")" >
  27. #postId[]#
  28. </iterate>
  29. </dynamic>
  30. </select>
  31. </sqlMap>


반복 부분을 iterator를 사용했다. 실행코드를 살펴보자.

  1. package pupustory.ibatis.dynamicquery;
  2. import pupustory.ibatis.beans.PostReplyBean;
  3. public class StartApp {
  4. public static void main(String ar[]) throws java.sql.SQLException {
  5. PostReplyBean bean = new PostReplyBean();
  6. DynamicQueryBiz biz = new DynamicQueryBiz();
  7. String[] postIds = {"1","2","4","5","7","9"};
  8. bean.setPostId(postIds);
  9. java.util.List list = biz.getPostBoard(bean);
  10. if (list.get(0) instanceof PostReplyBean) {
  11. System.out.println("true");
  12. }
  13. for (int i=0;i<list.size();i++) {
  14. bean = (PostReplyBean)list.get(i);
  15. System.out.println(bean.toString());
  16. }
  17. }
  18. }

어려운 구문은 없다. xml에서 open부분에 in ( 를 추가 했고, 배열마다 구분문자는 ','로 지정했다. 완료 후 ) 로 닫으니

SELECT * FROM TB_QNA_BOARD WHERE POST_ID IN ('1','2','4','5','7','9');

와 같은 sql 구문이 생성 된다. resultMap에선 실제 우리가 가져와야 할 빈의 변수와 맵핑하면 된다.

[출처] http://pupustory.tistory.com/184

Posted by 1010
54.iBATIS, MyBatis/iBatis2012. 4. 27. 00:05
반응형

iBatis를 사용하다 보니 또하나의 문제에 봉착했다.. LIKE 검색의 %기호를 인식하지 못하는 것이었다.


구글신에게 검색해 보고 다음과 같은 해결책을 얻을 수 있었다.

MySQL :
SELECT * FROM tbl_name WHERE column_name LIKE "%$username$%"

ORACLE :
SELECT * FROM tbl_name WHERE column_name LIKE '%' || #username# || '%'

SYBASE/SQL SERVER
SELECT * from tbl_name WHERE column_name LIKE '%' + #username# + '%'



여기서 변수명을 #로 둘러싸는 것과 $로 둘러싸는것의 차이점을 알 필요가 있다.
#의 경우에는 Prepare Statement로 등록이 된다. 디버그를 찍어봐도 ?로 치환된 이후 값이 대입된다.
하지만 $의 경우 바로 값이 치환된다. 특정 변수가 바로 DB에 입력되므로 보안에 좀더 신경을 써야 할것으로 생각된다.



3.9. Dynamic SQL

ADO에서 작업을 수행할때 발생되는 문제는 동적 SQL 이었다. 이것은 보통 SQL 문장과 함께 작업할때 파라미터의 값을 변경하는 작업으로 어려울때 이용을 하게 된다. 일반적인 방법은 if-else를 이용하거나, 끔찍한 스트링 조합으로 이를 해결하고 있다. 바라던 결과는 종종 각 예에 따른 쿼리를 작성하는 것이다. iBATIS DataMapper API는 어떠한 매핑되는 문장의 엘리먼트에도 적용할 수 있도록 좀더 좋은 코드를 만들 수 있도록 지원해준다. 간단한 예를 보면 다음과 같다.

Example 3.56. A simple dynamic select sttatement, with two possible outcomes

< select id="dynamicGetAccountList" cacheModel="account-cache" parameterClass="Account"
resultMap="account-result" >
select * from ACCOUNT
<isGreaterThan prepend="and" property="Id" compareValue="0">
where ACC_ID = #Id#
</isGreaterThan>
order by ACC_LAST_NAME
< /select>

상단에 제시된 예제는 2개의 가능한 문장을 생성해낼 수 있으며, 이 기준은 Id 프로퍼티에 들어갈 파라미터 객체에 달려 있다. 만약에 Id 파라미터가 0보다 큰 경우 문장은 다음 쿼리를 만들어 낸다.

select * from ACCOUNT where ACC_ID = ?

만약 파라미터가 0보다 작거나 인경우에는 스테이트 먼트는 다음과 같은 쿼리를 만든다.

select * from ACCOUNT


복잡한 상황에 부딛히지 않을경우 언뜻 보기에는 유용하지 않을 수있다. 예를 들어 다음과 같은 복잡한 상황이 될경우는 달라진다.

Example 3.57. A complex dynamic select statement, with 16 possible outcomes

< select id="dynamicGetAccountList" parameterClass="Account" resultMap="account-result" >
select * from ACCOUNT
<dynamic prepend="WHERE">
<isNotNull prepend="AND" property="FirstName">
( ACC_FIRST_NAME = #FirstName#
<isNotNull prepend="OR" property="LastName">
ACC_LAST_NAME = #LastName#
</isNotNull>
)
</isNotNull>
<isNotNull prepend="AND" property="EmailAddress">
ACC_EMAIL like #EmailAddress#
</isNotNull>
<isGreaterThan prepend="AND" property="Id" compareValue="0">
ACC_ID = #Id#
</isGreaterThan>
</dynamic>
order by ACC_LAST_NAME
< /select>

이러한 상황에서는 1개의 서로다른 쿼리가 만들어 진다. if-else 구문과 스트링 조합으로 만든다면 아마도 매우 혼란하고, 수백 라인의 코드가 필요할 수있을 것이다.

dynamic 문장은 가능하면 간단한 컨디션 태그를 SQL에 넣어야 한다. 예를 들면 다음과 같다.

Example 3.58. Creating a dynamic statement with conditional tags

< statement id="someName" parameterClass="Account" resultMap="account-result" >
select * from ACCOUNT
<dynamic prepend="where">
<isGreaterThan prepend="and" property="id" compareValue="0">
ACC_ID = #id#
</isGreaterThan>
<isNotNull prepend="and" property="lastName">
ACC_LAST_NAME = #lastName#
</isNotNull>
</dynamic>
order by ACC_LAST_NAME
< /statement>

상기 구문에서 <dynamic>엘리먼트로 쌓여있는 SQL의 영역이 동적 영역이다. dynamic 엘리먼트는 옵션이고, prepend("WHERE")를 이용하여 조건 문장을 포함하지 않고서도 조건절을 적용할 수 있도록 해준다. 스테이트먼트 섹션은 아래에서 말하는것과 같이 몇개의 컨디션 엘리먼트를 포함하고 있다. 모든 컨디션 엘리먼트쿼리에 파라미터 객체를 전달한 상태를 기본으로 처리가 된다. prepend 속성은 코드 부분으로 이것은 필요한 경우 상위 엘리먼트의 prepend를 상속 받는다. 상기 예에세는 "where" prepend를 첫번째 조건이 true인경우 적용이 된다. 이것은 SQL 문장이 정당하게 만들어 졌는지 보장하는데 필요한 요소이다. 예를 들어 첫번째 상황이 true인상황이고, AND가 필요없다고 한다면 사실 스테이트먼트는 깨질 것이다. 다음 섹션에서 엘리먼트의 다양한 종류에 대해서 설명하고, Binary Conditionals와 Unary Conditionals 그리고 반복에 대해서 다룰 것이다.

3.9.1. Binary Conditional Elements

이항 조건 엘리먼트는 속성 값과 정적값을 비고하거나 다른 프로퍼티 값과 비교를 수행한다. 만약 결과가 true인경우 바디 컨텐츠는 SQL 쿼리문에 포함된다.

3.9.1.1. Binary Conditional Attributes:

prepend – 구문이 SQL 의 부분으로 붙어서 사용될 것인지 결정한다. (옵션)
property – 비교할 대상 프로퍼티 (필요)
compareProperty – 비교할 다른 프로퍼티(필수 혹은 비교할 값)
compareValue – 비교할 값 (필수 혹은 compareProperty)

Table 3.7. Binary conditional attributes

Element Description
<isEqual> 두 프로퍼티의 비교에서 프로퍼티의 값이 다른 프로퍼티와 동일한지 검사
<isEqual prepend="AND" property="status" compareValue="Y"> MARRIED = ‘TRUE'
< /isEqual>
<isNotEqual> 하나의 프로퍼티가 다른 프로퍼티와 다른지 검사<isNotEqual prepend="AND" property="status" compareValue="N"> MARRIED = ‘FALSE'
< /isNotEqual>
<isGreaterThan> 하나의 프로퍼티가 다른 프로퍼티보다 큰지 검사
<isGreaterThan prepend="AND" property="age" compareValue="18"> ADOLESCENT = ‘FALSE'
< /isGreaterThan>
<isGreaterEqual> 하나의 프로퍼티가 다른 프로퍼티값보다 크거나 같은지 검사한다.<isGreaterEqual prepend="AND" property="shoeSize" compareValue="12"> BIGFOOT = ‘TRUE'
< /isGreaterEqual>
<isLessEqual> 프로퍼티가 다른 프로퍼티보다 작거나 같은지 검사한다.<isLessEqual prepend="AND" property="age" compareValue="18"> ADOLESCENT = ‘TRUE'
< /isLessEqual>


 

3.9.2. Unary Conditional Elements

단항 조건 엘리먼트는 특정 상황에 대해서 프로퍼티의 상태를 검사한다.

3.9.2.1. Unary Conditional Attributes:

prepend – 적용하고자 하는 문장 (옵션)
property – 체크된 프로퍼티 (필수)

Table 3.8. Unary conditional attributes

Element Description
<isPropertyAvailable> 프로퍼티가 사용가능한 상태인지 체크한다.<isPropertyAvailable property="id" >
ACCOUNT_ID=#id#
< /isPropertyAvailable>
<isNotPropertyAvailable> 프로퍼티가 사용가능하지 않는지 검사한다.<isNotPropertyAvailable property="age" >
STATUS='New'
< /isNotEmpty>
<isNull> 프로퍼티가 널인지 검사한다.
<isNull prepend="AND" property="order.id" >
ACCOUNT.ACCOUNT_ID = ORDER.ACCOUNT_ID(+)
< /isNotEmpty>
<isNotNull> 프로퍼티가 널이 아닌지 검사한다.
<isNotNull prepend="AND" property="order.id" >
ORDER.ORDER_ID = #order.id#
< /isNotEmpty>
<isEmpty> 컬렉션 벨류, 스트링이 널이거나 혹은 비어 잇는지 ("" 혹은 size() < 1)인지 검사한다.<isEmpty property="firstName" >
LIMIT 0, 20
< /isNotEmpty>
<isNotEmpty> 컬렉션 밸류, 스트링이 널이 아닌지, 혹은 비어있지 않은지 검사한다.
<isNotEmpty prepend="AND" property="firstName" >
FIRST_NAME LIKE '%$FirstName$%'
< /isNotEmpty>


 

3.9.3. Parameter Present Elements

이 엘리먼트는 파라미터 객체의 존재에 대해서 체크를 수행한다.

3.9.3.1. Parameter Present Attributes:

prepend – 적용할 문장 구문 (옵션)

Table 3.9. Testing to see if a parameter is present

Element Description
<isParameterPresent> 파라미터 객체가 현재 값이 존재하는지 (널이 아닌지)검사한다.<isParameterPresent prepend="AND">
EMPLOYEE_TYPE = #empType#
< /isParameterPresent>
<isNotParameterPresent> 파라미터 객체가 현재 값이 아닌지 (널인지) 검사한다.<isNotParameterPresent prepend="AND">
EMPLOYEE_TYPE = ‘DEFAULT'
< /isNotParameterPresent>


 

3.9.4. Iterate Element

이 태그는 컬렉션을 반복하고, 리스트에서 각 아이템의 내용을 반복한다.

3.9.4.1. Iterate Attributes:

prepend – 사용할 구문(옵션)
property – 반복할 리스트 객체 (필수)
open – 반복에서 전체 블록을 열어줄때 시작할 단어 보통 [, ( 이 이용된다. (옵션)
close – 블록 반복의 끝에 닫을때 사용할 단어 보통 ], ) 이 이용된다.(옵션)
conjunction – 반복할 단어 사이에 연결자 AND, OR (옵션)


 

Table 3.10. Creating a list of conditional clauses

Element Description
<iterate> 리스트 반복 예제
<iterate prepend="AND" property="UserNameList"
open="(" close=")" conjunction="OR">
username=#UserNameList[]#
< /iterate>Note: [] 을 리스트 의 시작과 끝을 나타낼때 매우 중요한 값이다. 이 브라켓은 리스트에서 단순히 스트링 값을 담고 있다고 판단하고, 값을 출력하게 된다.


3.9.5. Simple Dynamic SQL Elements

상단에 설명한 전체 동적 매핑된 구문의 강력함에도 불구하고, 가끔 단순하고, 작은 내용을 처리해야할 때가 있다. 여기 예에서는 SQL 스테이트문에는 단순한 동적 SQL 엘리먼트를 포함할 수 있으며 이것은 order by 문장에서 동적 구문을 구현하도록 하고 있다. 이것은 인파인 파라미터와 매우 닮은 형태를 가지고 있다. 그러나 약간의 차이점이 있자. 다음 예를 보자.

Example 3.59. A dynamic element that changes the collating order

< statement id="getProduct" resultMap="get-product-result">
select * from PRODUCT order by $preferredOrder$
< /statement>

상단의 예에서 preferredOrder 동적 엘리먼트는 파라미터 객체로 넘어온 preferredOrder의 값으로 대체된다. 차이점은 SQL 문장을 근본적으로 바꾼다는 것이고, 단순하게 파라미터 값을 세팅하는 것보다 더 심각할 수 있다. 동적 엘리먼트를 잘못 만들게 되면 보안, 성능, 안정성의 문제를 야기 시킨다. 그러므로 나머지 부분의 체크를 수행하기 위해서 적당한 다이나믹 엘리먼트를 이용해야할 것이다. 또한 디자인에서 명심해야할 것은 잠재적으로 데이터베이스 스펙이 비즈니스 객체 모델을 침범할 수 있다는것을 알아야 한다.
For example, you may not want a column name intended for an order by clause to end up as a property in your business object, or as a field value on your server page.

단순한 동적 엘리먼트는 <statements>를 추가하고, 변화가 필요한 부분을 직접 수정하면 된다.

Example 3.60. A dynamic element that changes the comparison operator

< statement id="getProduct" resultMap="get-product-result">
SELECT * FROM PRODUCT
<dynamic prepend="WHERE">
<isNotEmpty property="Description">
PRD_DESCRIPTION $operator$ #Description#
</isNotEmpty>
</dynamic>
< /statement>

상단 예제는 $operator$토큰을 전달 엘리먼트로 교체하는 예제이다. 만약 검색이 LIKE 검색을 수행하는 경우 %dog%로 값이 들어왔다면 SQL 문장은 다음과 같은 구문을 만들어 낸다.

SELECT * FROM PRODUCT WHERE PRD_DESCRIPTION LIKE ‘%dog%'
Posted by 1010
54.iBATIS, MyBatis/iBatis2012. 3. 5. 19:51
반응형

DECLARE
    BEGIN
     UPDATE table_a SET 
            mdf_dt = SYSDATE
      WHERE  gds_ctf_no = #gds_ctf_no#
    ;
     UPDATE table_b SET 
            mdf_dt = SYSDATE
      WHERE  gds_ctf_no = #gds_ctf_no#
    ;
    END; 
Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 4. 24. 13:16
반응형

출처 : http://mudchobo.tomeii.com/tt/category/아이바티스(iBATIS)

iBATIS를 이용해서 간단한 예제를 만들어봅시다.

웹이랑 섞으면 복잡하니까 콘솔에서 아주 간단하게 해보겠습니다-_-;

iBATIS를 들어보기만 하고 저처럼 어려울 것 같아서 거부하신분들에게 바칩니다-_-; 저는 처음에 상당히 어려울 줄 알았는데 공식홈페이지가니까 이동국님이 번역해 놓으신 문서가 있더라구요. 약간 이상하게(?) 번역해 놓긴 했지만 영어울렁증이 있는 저에게는 정말 도움이 되는 자료 였습니다 ^^

iBATIS공식홈페이지입니다.( 개발자 가이드와 튜토리얼 메뉴얼도 있습니다.)
for Java를 클릭하면 자바용이 나옵니다.
http://ibatis.apache.org/

접근하기 쉽게 하기 위해서 원더걸스멤버를 DB에 저장해놓고 해봅시다-_-;


CREATE TABLE `WONDERGIRLS` (
  `NUM` int(11) NOT NULL,
  `NAME` varchar(10) NOT NULL,
  `AGE` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=euckr;

INSERT INTO `WONDERGIRLS` (`NUM`, `NAME`, `AGE`) VALUES 
(1, '선예', 18),
(2, '선미', 15),
(3, '소희', 15),
(4, '예은', 18),
(5, '유빈', 99);


사실 원더걸스가 이렇게 어린지 몰랐습니다-_-; 자 우선 넘어가고-_-;

이클립스를 실행하고 JAVA PROJECT로 프로젝트를 하나 만듭시다.
그리고 패키지를 jyp라고 해서 만듭시다-_-;
iBATIS lib파일인 ibatis-2.3.0.677.jar과 OracleJDBC를 LIB으로 추가합시다.

iBATIS를 사용하기 위한 설정파일을 구성해봅시다.
jyp에다가 new해서 xml파일을 하나 만듭시다. 이름은 SqlMapConfig.xml 이라고 합시다.

SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">

<sqlMapConfig>

 <properties resource="./jyp/SqlMapConfig.properties" />
 
 <settings
     cacheModelsEnabled="true"
     enhancementEnabled="true"
     lazyLoadingEnabled="true"
     maxRequests="32"
     maxSessions="10"
     maxTransactions="5"
     useStatementNamespaces="false" 
 />
    
 <transactionManager type="JDBC" >
    <dataSource type="SIMPLE">
      <property name="JDBC.Driver" value="${driver}" />
      <property name="JDBC.ConnectionURL" value="${url}" />
      <property name="JDBC.Username" value="${username}" />
      <property name="JDBC.Password" value="${password}" />
    </dataSource>
 </transactionManager>
 
 <sqlMap resource="./jyp/WonderGirls.xml" />
  
</sqlMapConfig>

내용을 보면 환경을 설정하고 JDBC와 연결하기 위한 작업을 하는 듯 합니다. JDBC연결작업은 SIMPLE이라고 되어있는데 DBCP로도 됩니다. 메뉴얼에 보시면 개발자가이드 부분에 보면 하는 법이 있습니다.
그리고 마지막에 sqlMap의 resource를 WonderGirls.xml을 추가해놨는데요 차후 설명하겠습니다.

그다음에 저기 JDBC설정에 ${driver}등 변수형식으로 되어있는데 맨위에 properties파일을 하나 설정한게 있는데 저기에 정의 하면 됩니다.

SqlMapConfig.properties

driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:XE
username=MUDCHOBO
password=1234

오라클은 저렇게 하면 되구요. mysql은 mysql에 맞게 바꾸면 돼요.
만약 한 프로그램에서 여러개의 db를 접근한다면 저 파일들을 또 만들면 되겠죠?

그 다음 클래스를 하나 만듭시다. MyAppSqlmapConfig라는 클래스를 만듭시다.

package jyp;

import java.io.Reader;

import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;

public class MyAppSqlMapConfig {

 private static final SqlMapClient sqlMap;
 
 static {
  try {
   String resource = "./jyp/SqlMapConfig.xml";
   Reader reader = Resources.getResourceAsReader(resource);
   sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
  } catch (Exception e) {
   e.printStackTrace();
   throw new RuntimeException("Error initializing class. Cause:" + e);
  }
 }

 public static SqlMapClient getSqlMapInstance() {
  return sqlMap;
 }
}

방금 설정한 설정파일을 이용해서 sqlMapClient를 만드는 듯 합니다. 저 sqlMap객체를 통해서 db에 접근할 수 있는 듯 합니다. 저도 잘은 모르겠네요 ^^

원더걸스 getter, setter를 만들어 봅시다. WonderGirls라는 이름으로 클래스를 만듭니다.
WonderGirls.java

package jyp;

public class WonderGirls {

 private int num;
 private String name;
 private int age;
 
 public int getNum() {
  return num;
 }
 public void setNum(int num) {
  this.num = num;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public int getAge() {
  return age;
 }
 public void setAge(int age) {
  this.age = age;
 }
}

이제 가장 중요한 부분인 Wondergirls에 관련된 sqlMap을 작성해봅시다.
이름은 아까 정의한 WonderGirls.xml파일로 만듭시다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" 
"http://www.ibatis.com/dtd/sql-map-2.dtd">

<sqlMap namespace="WonderGirls">
  
 <resultMap id="WondergirlsResult" class="jyp.WonderGirls">
  <result column="NUM" property="num" />
  <result column="NAME" property="name" />
  <result column="AGE" property="age" />
 </resultMap>
 
 <select id="getWondergirls" resultMap="WondergirlsResult">
  SELECT NUM, NAME, AGE
  FROM WONDERGIRLS
  ORDER BY NUM
 </select>

 <select id="selectWondergirl" parameterClass="Integer" 
             resultClass="jyp.WonderGirls">
  SELECT NUM, NAME, AGE
  FROM WONDERGIRLS
  WHERE NUM = #num#
 </select>
 
 <insert id="insertWondergirl" parameterClass="jyp.WonderGirls">
  INSERT INTO
  WONDERGIRLS (NUM, NAME, AGE)
  VALUES (#num#, #name#, #age#)
 </insert>
 
 <update id="updateWondergirl" parameterClass="java.util.Map">
  UPDATE WONDERGIRLS SET
  NAME = #name#,
  AGE = #age#
  WHERE NUM = #num# 
 </update>
 
 <delete id="deleteWondergirl" parameterClass="Integer">
  DELETE WONDERGIRLS
  WHERE NUM = #num#
 </delete>
 
</sqlMap>

sql문을 xml로 뺐습니다. 나중에 고칠 때 편할 듯 싶네요.
sql이 총 5가지가 있네요.
전체리스트가져오기, 멤버번호받아서 한명멤버정보 가져오기, 멤버추가하기, 멤버수정하기, 멤버삭제하기

select해서 List를 가져올 때에는 원래 자동맵핑이 되서 알아서 가져오지만 그래도 명시적으로 맵핑을 해줘야돼요. 그래서 resultMap태그를 이용해서 명시해준뒤에 select를 선언해서 그 resultMap의 id를 resultMap속성값에 넣어주면 됩니다.
insert할 때에도 WonderGirls클래스형으로 받아서 넣어주면 되구요.
update할 때 좀 틀린게 parameterClass를 Map으로 선언합니다. 이름, 나이, 번호를 각각 객체로 받아서 Map에 저장한 것을 받아서 사용하는 것이죠.
delete할 때에는 Integer클래스형으로 숫자하나 받아서 해당 멤버를 지우게 됩니다.

이번엔 DAO를 만들어봅시다. WondergirlsDao라는 이름의 클래스를 만들어봅시다.

package jyp;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import com.ibatis.sqlmap.client.SqlMapClient;

public class WondergirlsDao {

 @SuppressWarnings("unchecked")
 public void viewMember(SqlMapClient sqlMap) throws SQLException {
  List<WonderGirls> wonderGirlsList = (List<WonderGirls>) sqlMap
    .queryForList("getWondergirls");
  for (int i = 0; i < wonderGirlsList.size(); i++) {
   WonderGirls wonderGirl = (WonderGirls) wonderGirlsList.get(i);
   System.out.print("번호 : " + wonderGirl.getNum());
   System.out.print("   이름 : " + wonderGirl.getName());
   System.out.println("   나이 : " + wonderGirl.getAge());
  }
 }

 public void insertMember(SqlMapClient sqlMap, WonderGirls wonderGirls) 
    throws SQLException {  
  sqlMap.insert("insertWondergirl", wonderGirls);
  System.out.println("추가에 성공했습니다.");
 }

 public void modifyMember(SqlMapClient sqlMap, Map<String, Object> map) 
    throws SQLException {
  sqlMap.update("updateWondergirl", map);
  System.out.println("수정에 성공했습니다.");
 }

 public void deleteMember(SqlMapClient sqlMap, int num) throws IOException,
   SQLException {
  sqlMap.delete("deleteWondergirl", num);
  System.out.println("삭제에 성공했습니다.");
 }
 
 public boolean validateMember(SqlMapClient sqlMap, int num) throws SQLException {
  WonderGirls wonderGirls = 
   (WonderGirls) sqlMap.queryForObject("selectWondergirl", num);
  
  if (wonderGirls == null) {
   return false;
  }
  return true;
 }
}

DAO를 보면 이상하게 간단합니다. 위에 xml에 다 쿼리를 선언해줬기때문에 그냥 파라메터만 넘겨주면 됩니다.
List보기는 sqlMap.queryForList("getWondergirls"); getWondergirls는 위에 선언한 쿼리의 id입니다.
insert할 때에는 객체를 받아서 넘겨주고, 수정할 때에는 Map을 넘겨주고, 삭제 할때에는 숫자를 넘겨주면 됩니다.
아 간단해요. 한줄이면 끝나요-_-;
예전에 Connection을 close()안해줬더니 서버가 다운되려고 한 경험이 있는데 이런일은 절대 없을 것 같군요.

이제 main함수를 작성해봅시다. 이름은 WondergirlsApplication으로 해봅시다.
public static void main(String[] args)에 체크해주시는 것 잊지 마시구요-_-;
WondergirlsApplication.java

package jyp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import com.ibatis.sqlmap.client.SqlMapClient;

public class WondergirlsApplication {

 public static void main(String[] args) throws SQLException, IOException {

  BufferedReader bufferedReader = new BufferedReader(
    new InputStreamReader(System.in));
  SqlMapClient sqlMap = MyAppSqlMapConfig.getSqlMapInstance();
  WondergirlsDao wondergirlsDao = new WondergirlsDao();
  WonderGirls wonderGirls = new WonderGirls();
  
  Integer age, num;
  String name;
  
  while (true) {
   try {
    System.out.println("\n1.멤버출력\n2.멤버추가\n3.멤버수정\n4.멤버삭제\n5.종료");
    System.out.print("--> ");
    String temp = bufferedReader.readLine();
    int MenuNum = Integer.parseInt(temp);
    switch (MenuNum) {
    case 1:
     wondergirlsDao.viewMember(sqlMap);
     break;
     
    case 2:
     wonderGirls = new WonderGirls();
     System.out.print("번호을 입력하세요 : ");
     wonderGirls.setNum(Integer.parseInt(bufferedReader.readLine()));
     System.out.print("이름을 입력하세요 : ");
     wonderGirls.setName(bufferedReader.readLine());
     System.out.print("나이을 입력하세요 : ");
     wonderGirls.setAge(Integer.parseInt(bufferedReader.readLine()));
     wondergirlsDao.insertMember(sqlMap, wonderGirls);
     break;
     
    case 3:
     Map<String, Object> map = new HashMap<String, Object>(3);

     System.out.print("수정할 번호을 입력하세요 : ");
     num = new Integer(Integer.parseInt(bufferedReader.readLine()));
     if (!wondergirlsDao.validateMember(sqlMap, num)) {
      System.out.println("없는 멤버번호입니다.");
      break;
     }
     System.out.print("이름을 입력하세요 : ");
     name = bufferedReader.readLine();
     System.out.print("나이을 입력하세요 : ");
     age = new Integer(Integer.parseInt(bufferedReader.readLine()));

     map.put("num", num);
     map.put("name", name);
     map.put("age", age);
     wondergirlsDao.modifyMember(sqlMap, map);
     break;
     
    case 4:
     System.out.print("삭제할 멤버번호를 입력하세요 : ");
     num = Integer.parseInt(bufferedReader.readLine());
     if (!wondergirlsDao.validateMember(sqlMap, num)) {
      System.out.println("없는 멤버번호입니다.");
      break;
     }
     wondergirlsDao.deleteMember(sqlMap, num);
     break;
     
    case 5:
     System.exit(0);
     
    default:
     System.out.println("1~5중에서 선택하세요!");
     break;
    }
   } catch (NumberFormatException e) {
    System.out.println("잘못 입력했습니다. 다시 입력하세요");
   }
  }
 }
}

자세히 보시면 뭐 별거없는데 거창하게 만들어놨군요(제가 좀 허접해서-_-) 아 참고로 예외처리 잘 안해놨으니 알아서 버그많아요 알아서 보세요-_-;

1번은 멤버 리스트를 가져오는군요.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 5

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 

2번은 멤버를 추가하는데 추가해봅시다-_-;

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 2
번호을 입력하세요 : 6
이름을 입력하세요 : 성종천
나이을 입력하세요 : 25
추가에 성공했습니다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 5
번호 : 6   이름 : 성종천   나이 : 25

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 

아 멤버가 추가되었어요-_-; (이거 욕먹겠군요-_-;)
멤버를 수정해봅시다. 유빈양의 나이가 잘못 되었으니 수정해봅시다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 3
수정할 번호을 입력하세요 : 5
이름을 입력하세요 : 유빈
나이을 입력하세요 : 20
수정에 성공했습니다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 20
번호 : 6   이름 : 성종천   나이 : 25

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 

아.... 유빈양이 제일 마지막에 들어왔는데 나이가 제일 많군요-_-;
그리고 이제 말도 안되는 성종천이라는 멤버를 지워봅시다-_-;

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 4
삭제할 멤버번호를 입력하세요 : 6
삭제에 성공했습니다.

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 1
번호 : 1   이름 : 선예   나이 : 18
번호 : 2   이름 : 선미   나이 : 15
번호 : 3   이름 : 소희   나이 : 15
번호 : 4   이름 : 예은   나이 : 18
번호 : 5   이름 : 유빈   나이 : 20

1.멤버출력
2.멤버추가
3.멤버수정
4.멤버삭제
5.종료
--> 

아.....잘 되는군요-_-; 난 왜 이짓을 하고 있는거지-_-;
Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 4. 24. 11:00
반응형

<sql /> 이라는 Element 를 사용한 것인데 꽤 괜찮은 기능이다..
예제는 다음과 같음.
조금만 살펴 보면 알수 있다.

<sql id="select-order">
 select * from order
</sql>

<sql id="select-count">
 select count(*) as value from order
</sql>

<sql id="where-shipped-after-value">
 <![CDATA[
  where shipDate > #value:DATE#
 ]]>
</sql>

<select
 id="getOrderShippedAfter"
 resultClass="map">
 <include refid="select-order" />
 <include refid="whre-shipped-after-value" />
</select>

<select
 id="getOrderCountShipped-after-value"
 resultClass="int">
 <include refid="select-count" />
 <include refid="whre-shipped-after-value" />
</select>

Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 4. 24. 10:45
반응형
출처 : http://openframework.or.kr/JSPWiki/Wiki.jsp?page=QueryForMapExample

SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>

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

<sqlMapConfig>

  <!-- Configure a built-in transaction manager.  If you're using an
       app server, you probably want to use its transaction manager
       and a managed datasource -->
  <transactionManager type="JDBC" commitRequired="false">
    <dataSource type="SIMPLE">
      <property name="JDBC.Driver" value="com.mysql.jdbc.Driver"/>
      <property name="JDBC.ConnectionURL" value="jdbc:mysql://localhost/ibatis_sample"/>
      <property name="JDBC.Username" value="root"/>
      <property name="JDBC.Password" value="root"/>
    </dataSource>
  </transactionManager>  
  <!-- List the SQL Map XML files. They can be loaded from the
       classpath, as they are here (com.domain.data...) -->
  <sqlMap resource="com/mydomain/data/Account.xml"/>
</sqlMapConfig>

SimpleExample

package com.mydomain.data;

import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
import com.ibatis.common.resources.Resources;
import com.mydomain.domain.Account;

import java.io.Reader;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.sql.SQLException;

public class SimpleExample {

  private static SqlMapClient sqlMapper;

  static {
    try {
      Reader reader = Resources.getResourceAsReader("com/mydomain/data/SqlMapConfig.xml");
      sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);
      reader.close();
    } catch (IOException e) {
      // Fail fast.
      throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
    }
  }
 
............

  public static Map selectAccountMap() throws SQLException {
    return sqlMapper.queryForMap("selectAllAccounts", null, "id");
  }
 
  public static Map selectAccountMapValue() throws SQLException {
    return sqlMapper.queryForMap("selectAllAccounts", null, "id", "emailAddress");
  }
}

Account.java

package com.mydomain.domain;

public class Account {

  private int id;
  private String firstName;
  private String lastName;
  private String emailAddress;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public String getEmailAddress() {
    return emailAddress;
  }

  public void setEmailAddress(String emailAddress) {
    this.emailAddress = emailAddress;
  }

}

Account.xml

<?xml version="1.0" encoding="UTF-8" ?>

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

<sqlMap namespace="Account">

  <!-- Use type aliases to avoid typing the full classname every time. -->
  <typeAlias alias="Account" type="com.mydomain.domain.Account"/>

  <!-- Result maps describe the mapping between the columns returned
       from a query, and the class properties.  A result map isn't
       necessary if the columns (or aliases) match to the properties
       exactly. -->
  <resultMap id="AccountResult" class="Account">
    <result property="id" column="ACC_ID"/>
    <result property="firstName" column="ACC_FIRST_NAME"/>
    <result property="lastName" column="ACC_LAST_NAME"/>
    <result property="emailAddress" column="ACC_EMAIL"/>
  </resultMap>

  <!-- Select with no parameters using the result map for Account class. -->
  <select id="selectAllAccounts" resultMap="AccountResult">
    select * from ACCOUNT
  </select>
</sqlMap>

설명

queryForMap() 메소드를 메소드 인자의 형태에 따라 두가지가 제공된다.
- queryForMap(String id, Object parameterObject, String keyProp)
- queryForMap(String id, Object parameterObject, String keyProp, String valueProp)

queryForMap(String id, Object parameterObject, String keyProp)

예제 소스의 SimpleExample에 보면 다음처럼 사용한 경우가 있다.

public static Map selectAccountMap() throws SQLException {
  return sqlMapper.queryForMap("selectAllAccounts", null, "id");
}

여기서 인자를 순서대로 보면 첫번째 인자는 Account.xml에서 "selectAllAccounts" 라는 id를 가리키는 것으로 다음 쿼리문을 실행하도록 지정하는 셈이다.

select * from ACCOUNT

두번째 인자는 parameter객체를 나타내는 것으로 현재 예제에서는 일단 전체 레코를 가져오는 것이라 조회조건을 나타내기 위한 파라미터 객체가 없다. 그래서 null로 지정했다. 조건문을 만들고자 할때는 이 객체를 적절히 셋팅해서 전달하면 된다.

세번째 인자는 queryForMap() 메소드 호출로 인해 반환되는 Map에서 key역할을 담당하는 것으로 여기서 반환되는 객체는 Account라는 객체를 가지는 Map객체이다. 현재 데이터가 다음과 같다면 두개의 Account객체가 넘어오는데 여기서 id가 1인 Account와 2인 Account를 구별하기 위한 Map의 키 역할을 담당할 칼럼을 지정하는 의미라고 보면 이해가 빠를듯 하다. 하지만 여기서 주의할 점은 세번째 인자의 값을 칼럼명이 아닌 해당 칼럼에 대응되는 Account객체의 변수명을 지정해줘야 한다는 것이다.

mysql> select * from account;
+--------+----------------+---------------+------------------+
| ACC_ID | ACC_FIRST_NAME | ACC_LAST_NAME | ACC_EMAIL        |
+--------+----------------+---------------+------------------+
| 1      | DongGuk        | Lee           | fromm0@gmail.com |
| 2      | DongGuk        | Lee           | fromm0@gmail.com |
+--------+----------------+---------------+------------------+
2 rows in set (0.00 sec)

이론적으로는 이렇게 실제 테스트 코드를 돌려보자.

Map map = SimpleExample.selectAccountMap();
System.out.println("# Map 정보 \n"+map);
     
account = (Account) map.get(1);
System.out.println("# id : " + account.getId());
System.out.println("# emailAddress : " + account.getEmailAddress());
System.out.println("# firstName : " + account.getFirstName());
System.out.println("# lastName : " + account.getLastName());

결과는 다음과 같다

# Map 정보
{2=com.mydomain.domain.Account@3eca90, 1=com.mydomain.domain.Account@64dc11}
# id : 1
# emailAddress : fromm0@gmail.com
# firstName : DongGuk
# lastName : Lee

queryForMap(String id, Object parameterObject, String keyProp, String valueProp)

예제 소스의 SimpleExample에 보면 다음처럼 사용한 경우가 있다.

public static Map selectAccountMapValue() throws SQLException {
  return sqlMapper.queryForMap("selectAllAccounts", null, "id", "emailAddress");
}

인자는 네번째를 제외하고는 앞의 예제와 동일하다. 네번째 인자는 Account객체에서 어떤값만 반환받을지는 결정하는 값이라고 보면 된다. 여기서는 selectAccountMapValue() 메소드에서 볼수 있듯이 emailAddress만을 반환받고자 했다.

이론적으로는 이렇게 실제 테스트 코드를 돌려보자.

map = SimpleExample.selectAccountMapValue();
System.out.println("# Map 정보 \n"+map);
String email = (String) map.get(1);      
System.out.println("# 반환값 : " + email);

결과는 다음과 같다.

# Map 정보
{2=fromm0@gmail.com, 1=fromm0@gmail.com}
# 반환값 : fromm0@gmail.com
Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 4. 24. 10:44
반응형

출처 : http://jnylove.tistory.com/249


iBatis
를 해보고자 예제 실습을 따라했다.

# 필요한 파일

1. Member.xml
2. SqlMapConfigExample.properties
3. SqlMapConfigExample.xml
4. Member.java
5. MemberApp.java

# 필요한 것들

1. MySql, Oracle, MSSQL 등등의 DB
2. 1번 DB에 접속할 수 있는 접속 정보(URL, ID, PASSWD, DB Name 등등)



# 실습 순서

1. DB에 접속하여 Member table의 schema를 확인한다
   SQL> desc MEMBER
   (#id#, #passwd#, #name#, #jumin1#, #jumin2#, #email#, #url#, #addr#, #tel_no#, #regdate#)
2. 1번에서 확인한 사항을 바탕으로 DTO(Member.java)를 생성한다.
    private String id;
    ... 중략...
    private String regdate;

    public String getId() {
        return id;
    }
    ...중략...
    public void setId(String id) {
        this.id = id;
    }
    ....중략...
3. SqlMapConfigExample.xml 파일을 만든다.
  <properties resource="SqlMapConfigExample.properties"/>
  <transactionManager type="JDBC" commitRequired="false">
    <dataSource type="SIMPLE">
       <property name="JDBC.Driver" value="${driver}"/>
       <property name="JDBC.ConnectionURL" value="${url}"/>
       <property name="JDBC.Username" value="${username}"/>
       <property name="JDBC.Password" value="${password}"/>
    </dataSource>
  </transactionManager>
  <sqlMap resource="Member.xml"/>
4. SqlMapConfigExample.properties 파일을 생성한다.
   driver=com.mysql.jdbc.Driver
   url=jdbc:mysql://localhost:3306/jsptest
   username=lucky
   password=lucky
5. Member.xml 파일을 생성하여 select, insert, update, delete 를 실행하기 위한 query를 정의한다.
 <select id="getMember" resultClass="Member">
  SELECT ID, PASSWD, NAME, JUMIN1,  ...중략... WHERE ID = #value#
 </select>
 <insert id="insertMember" parameterClass="Member">
  INSERT INTO MEMBER(ID, ...중략...) VALUES(#id#, ...중략...)
 </insert>
 <update id="updateMember" parameterClass="Member">
  UPDATE MEMBER ...중략...
 </update>
 <delete id="deleteMember" parameterClass="Member">
  DELETE ...중략...
 </delete>
6. compile
    $ javac -classpath .;..\..\..\lib\ibatis-2.3.0.677.jar *.java
7. run
    $ java -classpath .;..\..\..\lib\ibatis-2.3.0.677.jar;..\..\..\lib\mysql-connector-java-5.0.4-bin.jar MemberApp


# 주의사항
MemberApp.java 소스 중에

String resource = "SqlMapConfigExample.xml";
Reader reader = Resources.getResourceAsReader(resource);

이런 내용이 있는데, Resources.getResourceAsReader() 이 method가
resource를 classloader 기준으로 찾기 때문에
위의 xml 파일들이 class 파일이 위치한 곳에 같이 있어야 한다.

처음에 eclipse 에서 해보려고, package 만들어서 class 만들고 xml 파일은 다른 폴더에 저장하고 별~~ 삽질을 다 해봤으나 계속 "Could not found resource" 라는 exception만 뱉어냈었다.
eclipse에서의 문제 해결은 못하고, 우선 실행을 해봐야겠기에 전부 한 폴더에 몰아놓고 테스트 했다.











Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 4. 21. 14:28
반응형
SVN 정보
  • http://svn.apache.org/repos/asf/ibatis/trunk/java/docs/

번역문서

강좌및 관련작성문서

관련툴 

  • SQL2iBatis - SQL문이 저장되어 있는 sql파일을 인자로 줘서 iBatis용 자바소스와 SQL Maps 맵핑 xml파일 생성하기
  • DDL2iBatis-DDL2iBatis 프로그램 압축파일


Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 4. 21. 11:44
반응형
1. 2개의 프레임웍의 분류
a. Hibernate: Object Relational Mapper
b. iBatis: SQL mapper

2. Object Relational Mapper란?
a. Database 엔티티(일종의 테이블 row)와 자바 객체를 동기화 하는 역할을 담당
b. Hibernate는 이러한 역할을 하는 프레임웍
c. 모든 sql문은 프레임웍에서 생성되고 실행됨
d. sql작업이 필요할 경우 HSQL을 통하여 이루어짐(EJB-QL과 유사)
e. HSQL은 실제적인 sql의 앞단에서 처리되는 객체지향 쿼리 랭귀지
f. 종류: hibernate, TopLink, Cocobase, JDO 구현체

3. SQL Mapper
a. 자바객체를 실제 sql 문장에 맵핑.(자바 코드에서 sql 관련부분 제거)
b. Sql 문장은 자동 생성되는 것은 아니고 개발자가 기술해 줌
c. 맵핑 자체는 데이터베이스이 엔티티와 관계(relationship)에 독립적임.(mapping 자체가 sql문에 국한)
d. 실제적으로 모든 임베디드 sql 시스템은 모두 sql mapper로 간주가능
e. 예: iBATIS SQL Maps, Oracle SQLJ, Forte 4GL Embedded SQL, Pro*C Embedded SQL
f. iBatis sql map의 경우 xml에 임베디드된 sql (자바코드의 sql을 xml 파일로 분리)

4. Hibernate와 iBatis의 비교우위
a. Hibernate와 iBatis는 다른 특성을 갖는 프레임웍임
b. 일차원적인 비교는 불가능
c. 상황에 따라 적용 프레임웍의 효율성이 달라짐
   c-1 Hibernate가 적절한 경우
    * 새로운 프로젝트가 시작된 상태
    * 객체 모델과 데이터베이스 디자인이 미완성인 상태
   c-2 iBatis가 적절한 경우
    * 3rd party databases에 접근하는 경우
    * 레거시 데이터베이스와 연동이 필요한 경우
    * 적업하고 디비 디자인이 부적절한 상태(지져분한 설계)시
    * O/R Mapper가 이러한 상황을 제어할 능력이 없을수도 있음.
    * SQL Mapper를 사용할 경우 객체 모델과 데이터 모델사이의 멥핑에는 아무런 제약 사항이 없음.
    * sql문을 인력을 사용하여 수작업으로 tuning이나 최적화를 해야 할경우

5. Performance 측면의 비교

a. 과거 embeded sql mapper
    * 컴파일 랭귀지를 사용하여 제작됨
    * 매우 빠른속도를 제공하고 시스템 환경에 최적화 되어 있음
b. O/R mapper
    * sql mapper에 비하여 다양한 일을 수행
    * 대부분 reflection 방식 (hibernate),
    * binary code inhencement 방식(JDO).
    * hibernate의 향후 버전에서는 binary code inhencement방식을 채용
    * reflection 방식을 사용한다는 측면은 iBatis와 공통점

6. 프레임웍 성능비교는 무의미
a. 프레임웍 성능이란 프레임웍을 어떻게 사용하는 방식에 따라서 결정
b. 일반적으로 O/R mapper가 sql mapper에 비해서 훨씬더 효율적인 맵핑을 하고 수행전략을 수립.
c. O/R mapper는 객체 모델과 데이터베이스 모델에 대한 광범위한 정보를 포함있음
d. 간단한 CRUD 어플리케이션에 테이블-클래스 맵핑을 사용한다면  단순성과 성능이란 측면에서 O/R mapper많은 장점을 갖고 있음
f. 복잡한 데이터 전송방식의 환경에서는 sql mapper가 효율적임
g. Sql mapper가 더 효율적인 sql의 장점들을 표출할 수 있음

7. 결론
a. 하나이상을 선택하여 테스트 해보라.
b. 프로젝트에 대한 컨셉에 따라 세밀하게 테스트 해보라.
c. 모든 프로젝트의 특정은 모두 다르며 상황에 따라 Hibernate, iBATIS SQL Maps, TopLink, raw JDBC를 유연하게 사용해야 함

8. 필자의 의견 (Clinton Begin-iBatis 개발자)
이러한 이유에서 다양한 툴(프레임웍)을 빠르고 효과적으로 선택하고 테스트 하는 방법을 배우는 것이 더 중요하고 유용하다. 프레임웍중 하나만을 사용할 줄 아는 것은 중요한 것이 아니다. 다양한 상황에서 연습을 해보고, 더 좋은 결정을 내려보시길 바랍니다. 성배를 찾는 것은 중요한 것이 아닙니다. 
Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 2. 18. 11:59
반응형
허접 구닥다리 wono의 Linux에서 JSP 시작하기 3.

(IBatis 사용하기)


원본 글 사이트는 아래와 같습니다.


http://blog.naver.com/wono77/140030004047

 

* 글쓴 History:

최초 글쓴시각- 2006년 10월 20일.

수정- 2006년 10월 27일.

이번엔 DB를 접근할 차례입니다.


(제 강좌는 Linux 콘솔에서 직접 java 명령어로 컴파일합니다.

DB는 mysql 사용.

일단은 윈도우와 요즘 인기있는 자바툴인 이클립스를 쓰지 않습니다.

초보가 구조적으로 디렉토리 구조등을 이해하기 위해서 리눅스 콘솔이 더 좋다고 생각하기 때문입니다.

3번 강좌를 보시기전에 1,2번 강좌를 선행하시기 바랍니다.^^)


먼저 IBatis를 프레임웍 기반이 아닌,

처음부터 초보가... mysql db를 생쿼리로 날리는 것 밖에 모르는 ..

log4j, struts, spring을 모르는 정말 초보가 IBatis로 SqlMap을 어떻게 접근해나가야할까..

저는 그런 고민을 하였으며,

iBatis를 시작하는 많은 사람들이 고민하였을 문제일것입니다.


그러나, 인터넷에는 그런 한글 문서를 찾기가 힘들더군요.

구전으로 전해지는것일까요? (아마, 그런것 같습니다.)

아니면, 좋은 출판된 책이라도 있는 것일까요? (이건 아직 아닌것 같습니다^^)


IBatis를 어떻게 어디서 다운받아서 어디에 설치하고, 설정은 어떻게 하라는

초보적인 문서가 없었습니다.


그래서 직접 설치하면서 하나하나 정리해 보았습니다.


여기서 일단 집고 넘어가보지요.


IBatis 가 무엇입니까?


DB를 xml 형식으로 좀더 효율적으로 사용하자는 것입니다.

java 파일에서 db를 사용할때, xml에 따로 빼서 사용하자는 건데...


hibernate란 것도 있습니다.

이 IBatis란 것을 spring+Ibatis, struts+Ibatis로 쓰기도 하고, log4j+Ibatis로도 쓰기도 합니다.


http://ibatis.apache.org/javadownloads.html

일단 위 사이트에서 iBatis를 다운로드 받으세요.


바이너리 판이라서 설치하실 필요가 없습니다.

윈도우에 일단 받아서 압축을 풀어보시면,

저 파일중 필요한 폴더는 lib 단 1개입니다.


lib안에 3개의 jar파일이 있는데요, 실제 필요한 것은 2개입니다.


ibatis2-common-2.1.6.589.jar

ibatis2-sqlmap-2.1.6.589.jar

사용하는 jar 파일들은 일단 톰캣에서 공통적으로 사용하는 common에 밀어 넣도록 하겠습니다.


/apache-tomcat/common/lib

위 폴더 아래에 2파일을 밀어 넣는다.


그리고 사용하기 위한 설정에 대한 설명이 "이동국"님이 작성하신 메뉴얼이 역시 위 링크에 있습니다.

허락은 감히 받지 않았지만, 그 pdf 메뉴얼을 여기 올립니다.

문서가 2개인데 하나는 용량이 5메가라 안올라가네요..


lib에 파일을 밀어 넣었는데, 윈도우, 리눅스 모두 이 lib 디렉토리의 classpath를 잡아주어야합니다.


저는 리눅스 bash 쉘을 쓰므로, .bash_profile 을 아래와 같이 설정합니다.

저는 자바를 /home/wono/jsp/jdk 에 설치했으며,

톰캣은 /home/wono/jsp/local/apache-tomcat 에 설치했습니다.


# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

# User specific environment and startup programs

SVN_EDITOR=/usr/bin/vim
export SVN_EDITOR

IRTEAM=/home/wono

PATH=$PATH:$HOME/bin:/usr/local/bin:

export PATH
unset USERNAME

#자바 설정

JAVA_HOME=/home/wono/jsp/jdk
CATALINA_HOME=/home/wono/jsp/local/apache-tomcat
PATH=$JAVA_HOME/bin:$CATALINA_HOME/bin:$PATH
export JAVA_HOME CATALINA_HOME
#export CLASSPATH="$CATALINA_HOME/common/lib/servlet-api.jar:$JAVA_HOME/mysql-connector-java-5.0.2-beta-bin.jar:."
export CLASSPATH="$CATALINA_HOME/common/lib/servlet-api.jar:$JAVA_HOME/mysql-connector-java-5.0.2-beta-bin.jar:$CATALINA_HOME/common/lib/jsp-api.jar"
export CLASSPATH="$CLASSPATH:$CATALINA_HOME/common/lib:$CATALINA_HOME/common/lib/*.jar:$CATALINA_HOME/common/lib/ibatis2-common-2.1.6.589.jar:$CATALINA_HOME/common/lib/ibatis2-sqlmap-2.1.6.589.jar:."
#export CLASSPATH="$CLASSPATH:$CATALINA_HOME/common/lib/jsp-api.jar"
#--------------------------------------------------------------
# myapp
# myapp의 lib추가
#export CLASSPATH="$CATALINA_HOME/webapps/myapp/WEB-INF/lib:$CATALINA_HOME/webapps/myapp/WEB-INF/lib/*.jar"
export CLASSPATH="$CLASSPATH:$CATALINA_HOME/webapps/myapp/WEB-INF/classes:$CATALINA_HOME/webapps/myapp/WEB-INF/classes/*.xml:$CATALINA_HOME/webapps/myapp/WEB-INF/classes/*.properties"

export PATH


source .bashrc를 해주거나, 재접을 하시고, 1번 강좌에서 설명한

myapp라는 폴더에서 작업을 하겠습니다.


제가 iBatis로 해볼 작업은 다음과 같습니다.


Mysql의 DB명 testDB

Table 이름과 속성은 아래와 같습니다.

tb_Person{

int pid,

varchar pname

}
 

안에 값을 간단히 넣어 두세요.^^

insert into tb_Person values(2222,'wono');

insert into tb_Person values(1111,'kanoe');


이제 가장 간단한 select 쿼리를 날리는 예제를 iBatis로 작성하겠습니다.


select pid,pname from tb_Person where pid=2222;


이걸 만들겁니다.

사실 php로 코딩하자면 꿀입니다. 아주 간단합니다.

또는 기존의 java로 코딩해도 별거 아닙니다.


그냥 아래처럼 코딩하면 됩니다. 이것이 model1 방식입니다.


db 이름을 testDB이며 로그인 아이디는 loginId, 패스워드는 1111, 로그인포트 3306인 경우.


import java.sql.*;

      class Driver {
        public static void main(String argv[]) {
            try {
                Class.forName("org.gjt.mm.mysql.Driver");
                System.out.println("jdbc 드라이버 로딩 성공");
            } catch (ClassNotFoundException e) {
                System.out.println(e.getMessage());
            }
            try {
                String url = "jdbc:mysql://localhost:3306/testDB";
                Connection con = DriverManager.getConnection(url,"loginId","1111");
                System.out.println("mysql 접속 성공");
                Statement stmt = con.createStatement();
                ResultSet rs = stmt.executeQuery("select pid from tb_Person where pid=2222");
                System.out.println("Got result:");
                while(rs.next()) {
                    String no= rs.getString(1);
                    System.out.println(" no = " + no);
                }
                stmt.close();
                con.close();
            } catch(java.lang.Exception ex) {
                ex.printStackTrace();
            }
        }
      }


이러면 끝입니다. 간단합니다.(우선 pid빼기일때..)

머리아프게 iBatis 쓸필요 없습니다.

하지만, 이렇게 살수는 없지 않겠습니까?^^;

파일수도 늘어나고, xml도 써야하고;; 머리아프겠지만, 조금더 함께 고생해봅시다~

쿼리문을 다 xml하나에 모아보자는데....


그래서 아래와 같은 총 6개의 파일들이 필요합니다.

이제 하나하나 살펴 보겠습니다.


3개의 java 파일과,

apache-tomcat/webapps/myapp/WEB-INF/classes 안에 넣을 다음 3개의 설정파일이 필요합니다..


3개의 자바 파일은 myapp 안에 그냥 두면 됩니다.


1. MyAppSqlMapConfig.java (ibatis와의 connectionPool 생성 및 db문xml 파싱)

2. Person.java  (id 값을 가져오기 위한 person 클래스입니다.)

3. DBTest.java  ( 이안에 main이 있습니다.)


3개의 설정 파일은 apache-tomcat/webapps/myapp/WEB-INF/classes 에 둡니다.


1. Person.xml  (table을 가지고 놀 db 쿼리문을 여기에 xml로 작성하셔야합니다.)

2. SqlMapConfig.properties  (db 연결용 connect 파일, id, pass가 들어있음)

3. SqlMapConfig.xml (ibatis의 핵심 설정 파일. Person.xml등 정의)

설정 파일의 소스를 먼저 본후, 소스를 하나하나 보겠습니다.


1. Person.xml


<?xml version="1.0"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="Person">
<select id="getPerson" resultClass="Person">
    SELECT pid as id,pname as name

    FROM tb_Person
    WHERE
    pid = #value#
</select>
</sqlMap>


쿼리문을 하나 만들때 마다 <select id..>를 선언합니다.

getPerson이라고 이름지었는데, 실제 DBTest.java 소스에서 이 getPerson 이름을 사용해서,

이곳을 호출합니다.

pid as id에서 이 id를 알리어스라고 하는데, 이렇게 as id로 알리아스를 해주어야,

자바 프로그램에서 사용이 가능합니다.

이 id라는 것은 Person.java 라는 프로그램에서 실제로 선언한 프로그램 변수명이어야 합니다.


2. SqlMapConfig.properties


driver=org.gjt.mm.mysql.Driver
url=jdbc:mysql://localhost:3306/testDB
username=loginId
password=1111


이건 그냥 connect 설정입니다.


3. SqlMapConfig.xml


<?xml version="1.0" ?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<!--<properties resource="examples/domain/SqlMapConfig.properties" />-->
<properties resource="WEB-INF/classes/SqlMapConfig.properties" />
<!--<properties resource="SqlMapConfig.properties" />-->
<!--These settings control SqlMap configuration details, primarily to do with transaction management. They are all optional (see the Developer Guide for more). -->
<settings
    cacheModelsEnabled="true"
    enhancementEnabled="true"
    lazyLoadingEnabled="true"
    maxRequests="32"
    maxSessions="10"
    maxTransactions="5"
    useStatementNamespaces="false"
/>
<!--Type aliases allow you to use a shorter name for long fully qualified class names. -->
<!--<typeAlias alias="order" type="testdomain.Order" />-->
<!--Configure a datasource to use with this SQL Map using SimpleDataSource.
Notice the use of the properties from the above resource -->
<transactionManager type="JDBC" >
  <dataSource type="SIMPLE">
   <property name="JDBC.Driver" value="${driver}" />
   <property name="JDBC.ConnectionURL" value="${url}" />
   <property name="JDBC.Username" value="${username}" />
   <property name="JDBC.Password" value="${password}" />
  </dataSource>
</transactionManager>
<!--Identify all SQL Map XML files to be loaded by this SQL map. Notice the paths
are relative to the classpath. For now, we only have one… -->
<sqlMap resource="WEB-INF/classes/Person.xml" />
<!--<sqlMap resource="Person.xml" />-->
</sqlMapConfig>

별다른 설명은 필요 없을듯합니다.

제 경우 classpath에 위치 지정을 해줬는데됴, WEB-INF/classes 와 같이 위치 지정을 직접 안하면, 안되더군요.



이제 자바 소스 들어갑니다.~


1. MyAppSqlMapConfig.java (ibatis와의 connectionPool 생성 및 db문xml 파싱)


import java.io.*;
import com.ibatis.common.resources.*;
import com.ibatis.sqlmap.client.*;

public class MyAppSqlMapConfig {
        private static final SqlMapClient sqlMap;
        static {
            try {
                    String resource = "SqlMapConfig.xml";
                    Reader reader = Resources.getResourceAsReader(resource);
                    sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
            } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException("Error initializing class. Cause:" + e);
            }
        }

        public static SqlMapClient getSqlMapInstance() {
            return sqlMap;
        }

}


2. Person.java  (id 값을 가져오기 위한 person 클래스입니다.)


import java.util.*;

public class Person {

    private int id;

    private String name;

    // setter
    public void setId (int id) { this.id = id; }

    // getter
    public int getId () { return id; }

    public void setName (String name) { this.name = name; }

    public String getName () { return name; }

}


3. DBTest.java  ( 이안에 main이 있습니다.)


import java.sql.SQLException;
import java.util.*;

import com.ibatis.sqlmap.client.*;

public class DBTest {

    /**
     * @param args
     * @throws SQLException
     */
    public static void main(String argv[]) throws SQLException {
        // TODO Auto-generated method stub
        // select
        /*
        */
        selectTest();
    }

    public static void selectTest () throws SQLException
    {
        //sqlMap 초기화(MyAppSqlConfig.java)
        SqlMapClient sqlMap = MyAppSqlMapConfig.getSqlMapInstance();


        Integer personId = 2222;
        //Person.xml 안의 ID가 getPerson인 DB 선언에서 값이 psersonId(2222)인것을 select>해옴
        Person person = (Person) sqlMap.queryForObject("getPerson",personId);

        System.out.println("- Id : " + person.getId());

        System.out.println("- name : " + person.getName());

        System.out.println("\nSelect Done");
    }

}


짝짝짝...

그대로 한번 꼭 해보시길 바랍니다.


결과는


- Id: 2222

- name: wono


라고 나오겠지요? ^^


저도 처음에 남이 하는걸 볼때는 쉬워 보였는데, 직접 해볼려니 어렵더군요.


아, 컴파일은 각각 해주시면 됩니다.(아직 모든 파일들을 한방에 쏵~ 컴파일 해주는 ant 설정은 해주지 않았습니다. 뒷 강좌에서 리눅스 ant에 대해 다루겠습니다.^^)


1. javac MyAppSqlMapConfig.java

2. javac Person.java 

3. javac DBTest.java 


그리고, java DBTest 라고 해주시면...

값을 확인해 보실수 있습니다.^^



이 강좌를 iBatis를 처음 접하는 저와 같은 모든 이땅의 java, jsp 초보프로그래머에게 바칩니다.

조금이라도 도움이 되셨다면, 덧글이라도 살짝 남겨주세요. 감사합니다.


iBatis 사이트(http://ibatis.apache.org/javadownloads.html)의 이동국님께서 올려주신 메뉴얼을 많이 참고하였음을 밝히며, 조금밖에 안바꼈지만, 본 강좌의 저작권은 wono77에게 있습니다.

퍼가실때는 스크랩을 해주세요. 감사합니다. ^^ (__)


이제 이 iBatis의 기능으로 insert, delete, update 를 추가해서 게시판을 만들거나, struts, spring, log4j 같은 프레임웍을 조금씩 얹어가볼 생각입니다.

강좌는 계속 수정,보완되며... 진행 될 예정입니다.^^

Posted by 1010
54.iBATIS, MyBatis/iBatis2009. 1. 14. 14:57
반응형
1. 우선 iBATIS 를 다운받습니다.

2. 다운을 풀고, /lib 폴더에 ibatis-2.3.4.726 를 톰캣의 lib폴더나 프로젝트의 web-inf/lib 폴더에 넣습니다.
- /현제프로젝트/WebContent/WEB-INF/lib/
- /톰캣/lib/   (톰캣6버전)

두폴더중 편한곳에다가 jar파일을 저장합니다.


3. example 폴더를 타고 들어가보면 sqlMapConfi.xml 파일이 있습니다. 이파일을 복사해서 붙여넣기 하셔도되고
src폴더에서 새로 xml 파일을 만드셔도 됩니다.

sqlMapClient.xml

<?xml version="1.0" encoding="UTF-8" ?>

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

<sqlMapConfig>

  <!-- Configure a built-in transaction manager.  If you're using an
       app server, you probably want to use its transaction manager
       and a managed datasource -->
  <transactionManager type="JDBC" commitRequired="false">
    <dataSource type="SIMPLE">
      <property name="JDBC.Driver" value="org.hsqldb.jdbcDriver"/>
      <property name="JDBC.ConnectionURL" value="jdbc:oracle:thin:@192.168.10.103:1521:db"/>
      <property name="JDBC.Username" value="ahm"/>
      <property name="JDBC.Password" value="ahm"/>
    </dataSource>
  </transactionManager>

  <!-- List the SQL Map XML files. They can be loaded from the
       classpath, as they are here (com.domain.data...) -->
  <sqlMap resource="db/Account.xml"/>
  <!-- List more here...
  <sqlMap resource="com/mydomain/data/Order.xml"/>
  <sqlMap resource="com/mydomain/data/Documents.xml"/>
  -->

</sqlMapConfig>

빨간부분으로 표시된부분이 제가 수정한 부분입니다. db정보는 propertie로 빼서 관리하는방법도 있으나,
우선은 이렇게 하도록 하겠습니다.

4. 자바빈즈를 생성합니다.

db라는 패키지를 만들고 그 밑에 Acount.java 파일을 생성합니다. 결과를 담아올 빈즈입니다.
Account.java

package
 db;

public class Account {
 private int id;

  public int getId() {
   return id;
 }

  public void setId(int id) {
   this.id = id;
 }
}

5. XML을 생성합니다.

우리가 사용할 쿼리를 작성하는 XML입니다.
Account.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="Account">
  <!-- Select with no parameters using the result map for Account class. -->
  <select id="getAcount" resultClass="db.Acount">  다음값을 이용하여 쿼리와 맵핑합니다.
    select id from ACCOUNT where name=#value#
  </select>
</sqlMap>

6. 이제 쿼리를 실행할 JAVA를 작성한다.

SimpleExample.java

package db;

import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
import com.ibatis.common.resources.Resources;

import java.io.Reader;
import java.io.IOException;
import java.sql.SQLException;

public class SimpleExample {

  private static SqlMapClient sqlMapper;

  static {
   try {
     Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
     sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);
     reader.close();
   } catch (IOException e) {
     // Fail fast.
     throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
   }
 }

  public static Account getAcount() throws SQLException {
   return (Account)sqlMapper.queryForObject("getAcount","ahm");
   // 이부분에서 쿼리를 실행한다. queryForObject는 한개의 데이터를 가져올떄 사용하는 메소드이다.
 }
 
 public static void main(String[] args){
   
   try {
   Account temp = getAcount();    
   System.out.println(temp.getId());
 } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
 }
 }
}

물론 이 java파일을 실행하기 전에는 DB에 TABLE과 데이터가 준비되어있어야겠다.

현제 이클립스내에 폴더구조이다.


이미지와 같이 , iBATIS 를 설정하는 설정파일인 sqlMapConfig.xml 파일과
               실제 SQL이 담겨져 있는 Account.xml 파일
               결과를 담아올 객체인   Account.java 파일
               이러한 과정을 호출하는 SimpleExample.java 파일
               그리고 WEB-INF/lib 에 ibatis-**.jar 파일 등을 해당폴더에 넣고 위에 순서대로 따라한다면
               DB에서 요청사항을 무난히 가져올수 있을것이다.

다음엔 좀더 세부적인 설정사항이라던지 자세한 정보를 보여드리겠습니다.
Posted by 바보기자



가장 간단히 설명하면, JAVA에서 DB관련 작업을 편하게 해주는 프레임웍정도라고 할까?

iBATIS in action에서 iBATIS는 "SQL 실행 결과를 자바빈즈 혹은 Map 객체에 매핑해주는 퍼시스턴스 솔루션으로 SQL을 소스 코드가 아닌 XML로 따로 분리해 관리하여 지겨운 SQL 문자열 더하기에서 해방시켜 줍니다. 또한 XML에서 동적 SQL 요소를 사용하여 쿼리 문장을 프로그래밍 코딩 없이 자유롭게 변환할 수 있게 해줍니다. 이러한 접근방식으로 인해 iBATIS를 사용하면 JDBC를 사용할 때보다 약 60% 정도의 코드만으로 프로그램 작성이 가능하다" 라고 한다.

말로만 하면 뭔소리인지 모르겠으니 간단한 예제 정도를 들어보자.

- 일반적인 JDBC 예제
import javax.naming.*;
import javax.sql.*;
import java.sql.*;

public class Employee {
 public Account getAccount(int id) throws SQLException, NamingException{
   Account account = null;
   
   String sql = "select * from employee where id= ?";
   
   Connection conn      = null;
   PreparedStatement ps = null;
   ResultSet rs       = null;
   
   try {      
     Context ctx = new InitialContext();
     DataSource ds =
             (DataSource)ctx.lookup(
                "java:comp/env/jdbc/TestDB");
     conn = ds.getConnection();
     ps = conn.prepareStatement(sql);
     ps.setInt(1, id);
     rs = ps.executeQuery();
     
     while( rs.next()){
       account = new Account();
       account.setId(rs.getInt("ID"));        
     }
   } finally {
     try {
       if ( rs != null ) rs.close();
     } finally {
       try {
         if (ps != null) ps.close();
       } finally {
         if (conn != null) ps.close();
       }
     }
   }
   return account;  
 }
}   

뭐다들 아시겠지만 간단히 쿼리를 날려서 Acount 객체에 담아가지고 오는 소스이다. 대충봐도 무척이나 길다,
이걸 iBATIS를 이용해서 처리하는 예를 보자,

- iBATIS 를 이용한 예
acount.xml
<select id="getAcount" resultClass="Acount" parameterClass="java.lang.Integer">

   select * from employee where id= #id#
</select>

java
Acount act = (Acount) sqlMap.queryForObject("getAcount",new Integer(5));
 
보면 알겠지만 상단에 쿼리를 닮고있는 xml과 아래 간단히 크 쿼리를 실행시키는 java 한줄정도?이다.
사실 iBATIS를 설정하는 config파일과 sqlMap객체를 불러오는 부분이 있긴하지만, 무척이나 좋아보이도록,
이것만 쓰겠다. -_-;;

iBATIS 의 목표와 특징은 몇마디로 짧게정의하다면,

쉽고, 간단하고, 의존성이 적은 프레임웍이라는 것이다.
sql문과 java코드와의 분리만으로도 java개발자는 쿼리문을 신경쓰지 않아도 된다. sql문이 변경되더라도,
파라미터 값만 변경되지 않는다면, java소스에서는 수정할 부분이 없다.

~ 이론적인 면은 대충 접어두고 실전으로 넘어가자(사실 나도잘몰라서;;ㅈㅅ)

다음 포스트는 실제로 이클립스에서 오라클 디비와 연동하겠습니다.

beans.tistory.com

2008/11/13 - [W_@Second_Position/ω FrameWork_iBATIS] - iBATIS환경을 이해할 수 있도록 흐름도를 한번 그려 보자!

Posted by 바보기자

출처 : http://webprogrammer.tistory.com/595

iBATIS환경을 이해할 수 있도록 흐름도를 한번 그려 보자!

이미지를 클릭하면 원본을 보실 수 있습니다.


위의 그림에서 알 수 있듯이 sqlMapConfig.xml문서가 바로 환경 설정 파일임을 알 수 있을 것이다.

이것은 load시 한번만 인식 되는데 그 부분이 바로 앞의 모든 예제들에서 클라이언트 부분에 다음과 같은 코드가 된다.


      Reader reader = Resources.getResourceAsReader("ex2/sqlMapConfig.xml");


이때 transationManager요소와 sqlMap요소들을 인식하여  RDBMS와 SQL문을 관리하는 xml문서들이 인식된다.그리고


     SqlMapClient sqlMapper = SqlmapClientBuilder.buildSqlmapClient(reader);


문장으로 인해 인식된 SQL문들을 관리하는 xml요소들이 모두 sqlMapper로 하나의 객체에 id가 키값이 되어 Map구조로

담겨져 관리된다. qureryForList()와 같은 메서드를 통하여 지정된 id를 사용하면 테이블에 있는 자원들을 Emp.xml에 정의된

resultMap이나 지정된 객체로 쉽게 받는다.


특징
iBATIS Hibernate JPA
간단함
가장 좋음
좋음 좋음
완벽한 객체 관계 매핑 평균 가장 좋음 가장 좋음
데이터 모델 변경시 융통성 좋음 평균 평균
복잡성
가장 좋음 평균 평균
SQL에 대한 의존성 좋음 평균 평균
성능 가장 좋음 가장 좋음 N/A *
다른 관계 데이터베이스에 대한 이식성 평균 가장 좋음 N/A *
자바가 아닌 다른 플랫폼에 대한 이식성 가장 좋음 좋음 지원하지 않음
커뮤니티 지원과 문서화 평균 좋음 좋음
<표:http://blog.openframework.or.kr/50에서 펌>
Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 19. 14:01
54.iBATIS, MyBatis/iBatis2008. 11. 13. 11:28
반응형

<펌글:http://blog.paran.com/devtopia/13828096>

실무에서 SQL문을 작성하다 보면 동적인 쿼리문 작성을 작성해야 할 때가 많이 있다.

이때 지겹게 if~else if 문을 통해 아주 지저분한 소스 코드를 생성할 때가 왕왕 있게 마련이다.

이때 ibatis에서는 아주 깔금하게 구현할 수 있는 방법을 제공해 준다.


<statement id="dynamicGetAccountList" resultMap="account-result">

  select * from account

  <dynamic prepend="WHERE">

    <isNotNull prepend="AND" property="firstName">

      (acc_first_name = #firstName#

    <isNotNull prepend="OR" property="lastName">

       acc_last_name = #lastName#

    </isNotNull>

    )

    </isNotNull>

    <isNotNull prepend="AND" property="emailAddress">

      acc_email like #emailAddress#

    </isNotNull>

    <isGreaterThan prepend="AND" property="id" campareValue="0">

      acc_id = #id#

    </isGreaterThan>

  </dynamic>

  order by acc_last_name

</statement>


상황에 의존적인 위 동적 statement로 부터 각각 다른 16가지의 SQL문이 생성될 수 있다. if-else구조와 문자열 연결을 코딩하는 경우 수백라인이 필요할 수도 있다.

동적 statement를 사용하는 것은 몇몇 조건적인 태그를 추가하는 것처럼 간단하게 작성할 수 있다.


이러한 조건들에 대해 간단히 정리하면 아래와 같다.


바이너리 조건 요소-바이너리 조건 요소는 정적값 또는 다른 프로퍼티값을 위한 프로퍼티값과 비교한다. 만약 결과가 true라면 몸체부분의 SQL쿼리가 포함된다.


바이너리 조건 속성

prepend

Statement에 붙을 오버라이딩 가능한 SQL부분(옵션)

property

비교되는 property(필수)

compareProperty

비교되는 다른 property (필수 또는 compareValue)

compareValue

비교되는 값(필수 또는 compareProperty)


<isEqual>

프로퍼티가 값 또는 다른 프로퍼티가 같은지 체크

<isNotEqual>

프로퍼티가 값 또는 다른 프로퍼티가 같지 않은지 체크

<isGreaterThan>

프로퍼티가 값 또는 다른 프로퍼티 보다 큰지 체크

<isGreaterEqual>

프로퍼티가 값 또는 다른 프로퍼티 보다 크거나 같은지 체크

<isLessThan>

프로퍼티가 값 또는 다른 프로퍼티 보다 작은지 체크

<isLessEqual>

프로퍼티가 값 또는 다른 프로퍼티 보다 작거나 같은지 체크


사용법 예제)

<isLessEqual prepend="AND" property="age" compareValue="18">

  ADOLESCENT = 'TRUE'

</isLessEqual>


단일 조건 요소-단일 조건 요소는 특수한 조건을 위해 프로퍼티의 상태를 체크한다.

prepend

statement에 붙을 오버라이딩 가능한 SQL부분(옵션)

property

체크하기 위한 프로퍼티(필수)

 

<isPropertyAvailable>

프로퍼티가 유효한지 체크

(이를 테면 파라미터의 프로퍼티이다.)

<isNotPropertyAvailable>

프로퍼티가 유효하지 않은지 체크

(이를 테면 파라미터의 프로퍼티가 아니다.)

<isNull>

프로퍼티가 null인지 체크

<isNotNull>

프로퍼티가 null이 아닌지 체크

<isEmpty>

Collection, 문자열 또는 String.valueOf() 프로퍼티가 null이거나 empty(“” or size() < 1)인지 체크

<isNotEmpty>

Collection, 문자열 또는 String.valueOf() 프로퍼티가 null 이아니거나 empty(“” or size() < 1)가 아닌지 체크


사용법 예제)

<isNotEmpty prepend="AND" property="firstName">

  FIRST_NAME = #firstName#

</isNotEmpty>


다른 요소들

Parameter Present : 파라미터 객체가 존재하는지 체크

Parameter Present Attributes : prepend - the statement에 붙을 오버라이딩 가능한 SQL부분

<isParameterPresent>

파라미터 객체가 존재(not null)하는지 체크

<isNotParameterPresent>

파라미터 객체가 존재하지(null) 않는지 체크

 

사용법 예제)

<isNotParameterPresent prepend="AND">

EMPLOYEE_TYPE = 'DEFAULT'

</isNotParameterPresent>


Iterate : 이 태그는 Collection을 반복하거나 리스트내 각각을 위해 몸체 부분을 반복한다.

Iterate Attributes :

  prepend - the statement에 붙을 오버라이딩 가능한 SQL부분 (옵션)

  property - 반복되기 위한 java.util.List타입의 프로퍼티 (필수)

  open - 반복의 전체를 열기 위한 문자열, 괄호를 위해 유용하다. (옵션)

  close - 반복의 전체를 닫기 위한 문자열, 괄호를 위해 유용하다. (옵션)

  conjunction - 각각의 반복 사이에 적용되기 위한 문자열, AND 그리고 OR을 위해 유용하다. (옵션)

<iterate>

java.util.List 타입의 프로퍼티 반복


사용법 예제)

<iterate prepend="AND" property="userNameList" open="(" close=")" conjunction="OR">

username = #userNameList[]#

</iterate>


주의:iterator요소를 사용할 때 리스트 프로퍼티의 끝에 중괄호[]를 포함하는 것은 중요하다. 중괄호는 문자열처럼 리스트를 간단하게 출력함으로부터 파서를 유지하기 위해 리스트처럼 객체를 구별한다.

Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:40
반응형

Release 2.3.4 (JDK 1.5 Required)

Developer Build

Example Application

Documentation

Latest Documentation: Note that the documentation for iBATIS is updated very frequently, so it is possible (likely) that there is newer documentation available in the Subversion repository than what is listed here. If in doubt, check Subversion. The documentation in Subversion is in Open Office format. This link will show the very latest version of all iBATIS for Java documentation:

Undocumented Stuff:Although we try to keep the documentation as up-to-date as possible, sometimes the software simply evolves faster than the docs. In these cases, we make a habit of listing the undocumented features on the wiki, here: Undocumented features.

Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:29
반응형
Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:27
반응형

iBATIS환경을 이해할 수 있도록 흐름도를 한번 그려 보자!

이미지를 클릭하면 원본을 보실 수 있습니다.


위의 그림에서 알 수 있듯이 sqlMapConfig.xml문서가 바로 환경 설정 파일임을 알 수 있을 것이다.

이것은 load시 한번만 인식 되는데 그 부분이 바로 앞의 모든 예제들에서 클라이언트 부분에 다음과 같은 코드가 된다.


      Reader reader = Resources.getResourceAsReader("ex2/sqlMapConfig.xml");


이때 transationManager요소와 sqlMap요소들을 인식하여  RDBMS와 SQL문을 관리하는 xml문서들이 인식된다.그리고


     SqlMapClient sqlMapper = SqlmapClientBuilder.buildSqlmapClient(reader);


문장으로 인해 인식된 SQL문들을 관리하는 xml요소들이 모두 sqlMapper로 하나의 객체에 id가 키값이 되어 Map구조로

담겨져 관리된다. qureryForList()와 같은 메서드를 통하여 지정된 id를 사용하면 테이블에 있는 자원들을 Emp.xml에 정의된

resultMap이나 지정된 객체로 쉽게 받는다.


특징
iBATIS Hibernate JPA
간단함
가장 좋음
좋음 좋음
완벽한 객체 관계 매핑 평균 가장 좋음 가장 좋음
데이터 모델 변경시 융통성 좋음 평균 평균
복잡성
가장 좋음 평균 평균
SQL에 대한 의존성 좋음 평균 평균
성능 가장 좋음 가장 좋음 N/A *
다른 관계 데이터베이스에 대한 이식성 평균 가장 좋음 N/A *
자바가 아닌 다른 플랫폼에 대한 이식성 가장 좋음 좋음 지원하지 않음
커뮤니티 지원과 문서화 평균 좋음 좋음
<표:http://blog.openframework.or.kr/50에서 펌>

Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:24
반응형

지디넷코리아]iBATIS SQLMaps(이하 아이바티스)는 이미 국내외 많은 개발자들이 사용하고 있는 퍼시스턴스 계층의 프레임워크이다. 실제로 국내에서 가장 크다는 포털 사이트인 네이버와 다음도 아이바티스를 사용하고 있으며, 다른 업체들도 아이바티스를 그대로 사용하거나 약간 변형시킨 형태로 사용하고 있는 것으로 알고 있다. 특집 3부에서는 아이바티스의 특징과 활용법에 대해 알아본다.

과거 ORM의 대표인 하이버네이트와 데이터 매퍼인 아이바티스 간에 어느 프레임워크가 더 좋은가에 대한 논쟁도 있었을 만큼 많은 자바 개발자들은 데이터베이스에 관련된 ORM 프레임워크에 대한 관심이 크다.

이런 논쟁 가운데 아이바티스는 ORM이 아니기 때문에 논쟁의 대상이 될 수 없다는 의견도 있었다. 사용 목적이라는 관점에서 본다면 하이버네이트와 아이바티스는 논쟁의 대상이 될 수 있으나 ORM이냐 아니냐를 가름하는 관점에서 본다면, 역시 아이바티스는 ORM이 아니라고 할 수 있다.

  ORM과 데이터 매핑

아이바티스의 핵심 개발자인 래리 메도스(Larry Meadors)는 다음처럼 ORM과 데이터 매퍼를 구분한다.

ORM = 데이터베이스 객체를 자바 객체로 매핑
Data mapping = SQL “구문”을 자바 객체로 매핑

여기서 말하는 개념은 두 프레임워크의 성격을 제대로 이해할 수 있게 해준다. 이러한 개념적인 차이점은 결과적으로 ORM의 캐싱 전략은 데이터베이스 객체에 매핑 된 자바 객체가 캐시에 저장되는 게 기본이고 데이터 매퍼는 객체가 아닌 SQL 구문 자체가 캐시에 저장이 되는 것과 같은 차이점을 낳게 된다. 그렇다고 아이바티스를 ORM의 범주에 포함시키는 데 큰 무리는 없다.

ORM이 생겨난 원인을 무엇일까? 가장 기본적인 것은 현재의 데이터베이스 프로그래밍에서 일반 JDBC 형태의 개발 방법이 개발자에게 필요 이상의 많은 타이핑과 자원관리를 요구한다는 것이다.

ORM을 사용해본 개발자는 이 부분에 대해 ORM이 어느 정도 효과를 보인다고 생각할 것이다. 물론 새로운 프레임워크에 대한 학습과 해당 프레임워크가 요구하는 설정작업, 그리고 개발방식은 분명히 개발자에게 또 다른 부담으로 작용하는 것 또한 사실이다. 하지만 개인적으로 개발자는 항상 새로운 어떤 것을 배우고 습득해야 한다.

그런 면에서 이런 프레임워크로 인한 부담을 느끼지 말고 자기개발의 수단으로 생각했으면 좋겠다. 필자가 처음으로 아이바티스를 접했던 3년 전과는 달리 현재는 국내에 서적도 나와 있고 많은 관련문서가 있어서 개발자들이 아이바티스를 학습하는데 전혀 무리가 없을 듯하다.

그래서 이번에는 특정 부분에 집중하지 않고 아이바티스를 활용할 수 있는 부분들을 전체적으로 살펴보고 아이바티스 홈페이지를 통해 논의 되고 있는 아이바티스 3.0이 나아가고자 하는 방향을 여기서 간단히 살펴보고자 한다.

  기능에 따른 사용형태

아이바티스를 사용하는 개발에서는 크게 설정 정보를 가지는 SQLMaps 파일과 SQL구문을 가지는 SQLMap 파일, 그리고 아이바티스 API를 사용하는 자바 코드로 구성이 된다고 볼 수 있다. 먼저 설정정보를 가지는 SQLMaps 파일을 살펴보자. SQLMaps 파일 설정은 이미 많이 알려져 있기 때문에 첨부된 예제소스를 참조하도록 한다.

샘플용 소스이기 때문에 typeAlias와 SQLMap 파일의 개수가 적다. 하지만 실제 프로젝트에 아이바티스를 적용할 때는 사전에 typeAlias에 대한 명명 규칙을 정해서 중복 되지 않도록 하거나 각각의 SQLMap 파일에 정의해서 StatementNamespaces로 각각을 구분해주어야 설정 파일의 증가로 복잡해질 때 혼동의 여지가 줄어들 것이다.

전역 세팅에 해당되는 설정과 typeAlias, 그리고 트랜잭션 관리자 및 SQLMap 파일에 대한 위치 정보를 가지고 있다. 전역 세팅에 해당되는 설정은 이외에도 더 있으며 상세한 설명은 이 글의 범위를 벗어나기에 생략한다. 각각의 설정 값에 대한 상세한 정보는 kldp.net에서 호스팅 중인 아이바티스 개발자 가이드 한글문서나 공식 영문문서를 보면 알 수 있다.

typeAlias는 말 그대로 타입에 대한 별칭이다. 자바의 패키지 구조로 인해 이름이 긴 클래스를 사용할 경우 차후에 불필요한 중복 타이핑을 해야 하는 등 불편이 따른다. 때문에 여기서는 별칭 형태로 정의할 것이다.

트랜잭션 관리자 부분은 간단히 볼 때 데이터베이스 세팅이다. 간혹 커뮤니티에 아이바티스에서 데이터소스를 두 개 이상 설정할 수 없냐는 질문이 올라오는데 1.x 버전에서는 가능했으나 2.x 부터는 혼란의 소지가 있어 불가능하다. 꼭 필요한 경우라면 이 설정 파일을 데이터 소스 개수만큼 생성하여 동적으로 읽어 들이는 수밖에 없을 듯하다.

마지막으로 SQL 구문을 가지게 되는 SQLMap 파일이다. 여기서는 클래스 패스 기준으로 설정되었다. 아이바티스 사용자들의 공통적인 고민은 WAS의 재시작 없이 SQL구문이 갱신되는 기능일 것이다. 이 경우 아이바티스 단독으로만 사용할 때는 SQLMap 파일을 파일 경로로 지정해주는 방법을 사용할 수 있겠지만, 이 방법 또한 정상적으로 갱신이 안 되는 경우가 있다고 하니 필자로서는 스프링 프레임워크를 사용하는 방법을 권하고 싶다.

스프링 프레임워크를 사용하면 WAS를 재시작하지 않고도 SQL구문이 갱신되는 것을 AppFuse를 통해 간단히 체크해볼 수 있다.

<그림 1>은 이제부터 살펴볼 기본적인 CRUD 작업에 사용된 테이블의 구조다. 계정(account) 정보와 그 계정의 가족 정보(account_family)를 가지는 지극히 간단한 구조로 되어 있다. 하지만 여기서 살펴볼 아이바티스의 기능을 구현하기에는 더없이 적절한 구조라고 생각된다.

<그림 1> ER 다이어그램


일반적인 CRUD 작업
데이터 매퍼인 아이바티스는 SQL ‘구문’을 자바 객체로 매핑하기 때문에 기본적으로 정적인 CRUD 작업에 가장 최적화되어 있다고 할 수 있다. 물론 데이터베이스 함수 및 프로시저, 동적 SQL 처리에도 사용할 수는 있지만 약간의 제약이 있게 마련이다. 여기서는 SQL 구문의 재사용을 위해 요소를 사용했고 이를 요소를 사용해서 다른 구문에서 가져다가 사용하고 있다.

쿼리문을 일종의 특정 id값을 가지는 xml에 저장하고 그 id를 기준으로 호출해서 사용하는 게 아이바티스의 기본 사용법이다. 이 파일을 이해하는데 별무리가 없으리라 생각된다. 그러면 이 SQL 구문들을 어떻게 호출해서 사용하면 좋을까? 자바 코드를 보자.

 <리스트 1> CRUD 작업을 위한 SQLMap 파일


<sql id="selectAccount_frag">
select
acc_id,
acc_first_name,
acc_last_name,
acc_email
from account
</sql>

<select id="selectAccountById" parameterClass="int" resultClass=vAccount">
<include refid="selectAccount_frag"/>
where acc_id = #id#
</select>

<insert id="insertAccount" parameterClass="Account">
insert into account (
acc_id,
acc_first_name,
acc_last_name,
acc_email
) values (
#id#, #firstName#, #lastName#, #emailAddress#
)
</insert>



자바 코드에서는 각각의 SQL 구문을 호출하는 형태를 <리스트 2>와 같이 취하고 있다. 사전에 아이바티스 설정 파일을 통해 관련 정보를 얻는 부분이 static 구문 안에 처리가 되어 있다. 이 소스는 아이바티스를 처리하는 결과를 보기 위해 log4j 설정과 아이바티스 설정 정보를 읽어오는 두 가지로 구성이 되어 있다.

log4j.xml 파일과 아이바티스 설정 파일의 위치를 클래스 패스 기준으로 가져오도록 했다. SqlMapClient가 제공하는 각각의 메소드에 SQLMap 파일에 정의된 SQL 구문의 id와 인자로 넣어줄 객체를 정의하는 형태를 취한다. 짐작하겠지만 SQL 구문의 id와 일치하는 SQL 구문에 해당 객체의 값이 인자로 전달되어서 내부적으로 java.sql의 PreparedStatement 객체 생성 후 일반 JDBC처럼 처리된다.

 <리스트 2> CRUD 작업을 위한 자바 코드


private static SqlMapClient sqlMapper;

static {
try {
// iBATIS SQLMaps setting
Reader reader = Resources.getResourceAsReader("kr/or/openframework/dao/ibatis/SqlMapConfig.xml");
sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);
reader.close()
} catch (IOException e) {
throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
}
}

public static Account selectAccountById(int id) throws SQLException {
return (Account) sqlMapper.queryForObject("selectAccountById", id);
}
public static String insertAccount(Account account) throws SQLException {
sqlMapper.insert("insertAccount", account);
return "SUCCESS";
}



프로시저
예제를 위해 필자는 다음처럼 간단한 프로시저를 생성했다. 여기서는 IN 타입의 파라미터만을 사용했지만 IN과 OUT, INOUT 타입 모두 지원한다. 여기서 사용된 프로시저는 인자로 들어온 값을 id로 해서 샘플용 데이터를 생성한다.

 <리스트 3> 프로시저 생성 구문


drop procedure if exists procedurein;

delimiter // ;

create procedure procedurein (in p_param int)
begin
insert into account (
acc_id,
acc_first_name,
acc_last_name,
acc_email
) values (
p_param, "procedure_test", "procedure_test", "test@test.com"
);
end;
//

delimiter ; //



프로시저 호출은 일반적으로 콘솔 창에서 실행하는 것처럼 하고 { }로 감싸주면 된다. 처리 자체는 오히려 CRUD보다 간단하다고 할 수 있으나 직접 해보면 쉽지 않다는 것을 알 수 있다.

 <리스트 4> 프로시저 호출을 위한 SQLMap 파일


<procedure id="inProcedure" parameterClass= "java.lang.Integer">
{ call procedurein(#val#) }
</procedure>



 <리스트 5> 프로시저 호출을 위한 자바 코드


public static String callInTypeProcedure(int id) throws SQLException {
sqlMapper.update("inProcedure", new Integer(id));
return "SUCCESS";
}



프로시저의 처리는 아이바티스 홈페이지나 메일링 리스트를 통해 끊임없이 제기되는 문제 중에 하나이다. 개발자와 데이터베이스마다 구현 방식이 조금씩 다른 탓도 있겠지만 여러 가지 눈에 띄는 어려움이 존재하기 때문에 아이바티스 문서를 보면 꼭 표준 형태의 프로시저를 사용하도록 권장하고 있다.

필자의 경우에도 오라클(Oracle)에서는 정상적으로 프로시저 형태의 샘플 코드를 테스트 했으나 MySQL로 작성하면서 이런저런 문제에 봉착했었다. 실제 프로젝트에서 아이바티스를 사용하여 프로시저를 처리할 때는 프로시저 사용 패턴을 정의하고 사전에 정상적으로 처리가 되는지 테스트 해보길 권하고 싶다.

<표 1> 프로시저 처리시 사용할 메소드에 대한 권고사항


앞의 예제는 IN 파리미터에 값을 전달하고 내부적으로 처리한 후에 어떠한 데이터도 반환하지 않기 때문에 update() 메소드가 사용되었다(프로시저 처리 시 결과 세트를 반환하고 데이터를 업데이트 할 경우, 요소의 commitRe quired 속성 값을 true로 세팅해줘야 한다).

N+1 문제

 <리스트 6> N+1문제를 가지는 형태의 SQLMap 파일


<resultMap id="get_account_family_nplus1" class="Account">
<result property="id" column="id" />
<result property="firstName" column="firstName" />
<result property="lastName" column="lastName" />
<result property="emailAddress" column= "emailAddress" />
</resultMap>

<resultMap id="get_family_nplus1" class="Family">
<result property="acc_id" column="id" />
<result property="fullName" column="fullName" />
<result property="account" column="id" select= "getFamiliesUsingNplus1" />
</resultMap>

<sql id="selectAccount_frag">
select
acc_id as id,
acc_first_name as firstName,
acc_last_name as lastName,
acc_email as emailAddress
from account
</sql>

<select id="selectAccountWithFamilyUsingNplus1" parameterClass="int" resultMap="get_family_nplus1">
select
acc_id as id,
family_fullname as fullName
from Account_family
where acc_id = #id#
</select>

<select id="getFamiliesUsingNplus1" parameterClass="int" resultMap="get_account_family_nplus1">
<include refid="selectAccount_frag"/>
where acc_id = #id#
</select>



log4j를 이용해 실제로 수행되는 SQL문을 찍어보면 다음과 같이 표시된다.

Executing Statement: select acc_id as id, family_fullname as fullName from Account_family where acc_id = ?
Executing Statement: select acc_id, acc_first_name, acc_last_name, acc_email from account where acc_id = ?
Executing Statement: select acc_id, acc_first_name, acc_last_name, acc_email from account where acc_id = ?

2(N)개의 데이터를 가져오기 위해 실제로는 SQL 문을 한 번 더(+1) 수행하여 총 3번(Account_family에 한번, Account에 두 번)을 실행하게 되는 셈이다. 이것을 N+1 문제라고 하는데 이를 개선하기 위해 groupBy를 사용할 수 있다.

 <리스트 7> N+1문제를 가지는 형태의 자바 코드


@SuppressWarnings("unchecked")
public static List<Account> selectAccountWithFamilyUsingNPlus1(int id) throws SQLException {
List<Account> familys = (ArrayList)sqlMapper.queryForList("selectAccountWithFamilyUsingNplus1", new Integer(id));
return familys;
}



 <리스트 8> N+1문제를 가지지 않는 형태의 SQLMap 파일


<resultMap id="get_account_family_avoidnplus1" class= "Account" groupBy="id">
<result property="id" column="id" />
<result property="firstName" column="firstName" />
<result property="lastName" column="lastName" />
<result property="emailAddress" column= "emailAddress" />
<result property="families" resultMap= "Account.get_family_avoidnplus1"/>
</resultMap>

<resultMap id="get_family_avoidnplus1" class="Family">
<result property="acc_id" column="id" />
<result property="fullName" column="fullName" />
</resultMap>

<select id="selectAccountWithFamilyAvoidNplus1" parameterClass="int" resultMap= "get_account_family_avoidnplus1">
select
a.acc_id as id,
a.acc_first_name as firstName,
a.acc_last_name as lastName,
a.acc_email as emailAddress,
f.family_fullname fullName
from account a left join account_family f
on a.acc_id=f.acc_id
where a.acc_id=#id#
</select>



 <리스트 9> N+1문제를 가지지 않는 형태의 자바 코드


@SuppressWarnings("unchecked")
public static List<Account> selectAccountWithFamilyAvoidNPlus1(int id) throws SQLException {
List<Account> familys = (ArrayList<Account>)sqlMapper.queryForList("selectAccountWithFamilyAvoidNplus1", new Integer(id));
return familys;
}




조인 구문과 groupBy를 사용하면 다음처럼 일반 join 문을 사용하여 처리를 하기 때문에 N+1과 같은 문제가 발생하지 않는다. 현재는 이 방법이 가장 추천된다고 할 수 있다. 하지만 방법적으로 하위 결과 맵을 사용하는 것이 추천되는 방법이라고 보기는 어려울 듯하다. 아이바티스 3.0에서는 좀 더 개선된 방법이 제공될 것으로 짐작된다.

Executing Statement: select a.acc_id, a.acc_first_name, a.acc_last_name, a.acc_email, f.family_fullname from account a left join account_family f on a.acc_id=f.acc_id where a.acc_id=?

동적 SQL

 <리스트 10> 동적 SQL을 사용하는 SQLMap파일 내용


<select id="selectAccountDynamic" parameterClass="Account" resultClass="Account">
<include refid="selectAccount_frag" />
<dynamic prepend="WHERE">
<isGreaterEqual prepend="AND" property= "id" compareValue="2">
acc_id = 1
</isGreaterEqual>
</dynamic>
</select>



 <리스트 11> 동적 SQL을 호출하는 자바코드


@SuppressWarnings("unchecked")
public static List<Account> selectAccountDynamic(Account account) throws SQLException {
List<Account> accounts = (List<Account>)sqlMapper.queryForList("selectAccountDynamic", account);
return accounts;
}



필자의 경우 인자로 넣어준 id의 값이 3이었기 때문에 다음처럼 WHERE acc_id =1이 추가되어 처리되었다. 동적 SQL의 경우 자바 코드가 아닌 XML의 태그로 동적 SQL을 생성하기 때문에 손에 익지 않아 불편할 수도 있다. 이미 아이바티스 개발팀은 이 동적 SQL 부분에 대한 개선작업을 진행하고 있다. 그 결과물로 3.0에서 도입될 방식에 대해 잠시 뒤에 살펴보도록 하겠다.

 <리스트 12> 동적 SQL을 호출하는 결과


Executing Statement: select acc_id as id, acc_first_name as firstName, acc_last_name as lastName, acc_email as emailAddress from account WHERE acc_id = 1



  아이바티스 3.0 소식

아이바티스 3.0 개발은 다음과 같은 방향으로 진행이 된다.

- 테스트 주도 개발
- 성능보다는 코드의 간결성
- 복잡한 디자인 보다는 간결한 디자인
- 하나의 JAR파일
- 다른 라이브러리의 의존성을 없앰
- 더 나은 플러그인 지원
- 추가적인 플러그인을 위한 프로젝트(http://sourceforge.net/ projects/ibatiscontrib/)

역시나 기본적으로 계속 간결한 프레임워크를 유지하면서 많은 사용자들이 원하는 기능은 플러그인 형태로 제공하는 것이 기본적인 개발 방향임을 짐작할 수 있다. 역시 아이바티스는 간단함 내지 간결함으로 표현할 수 있는 프레임워크이다.
그럼 저 기본 방향을 염두에 두고 현재 다소 형상화되어 보이는 3.0 기능에 대해 간단히 살펴보자.

인터페이스 바인딩
일단 다음과 같은 기존 코딩 방식에 대해 살펴보자. 기존 방식의 문제점은 매핑 구문명이 문자열 기반이라 매핑 구문명의 철자가 잘못되어 있을 경우 에러가 발생하게 되고 컴파일시가 아닌 런타임 시 에러가 발견할 수 있게 된다. 더군다나 문자열 기반이라 SQLMap 파일의 증가는 곧 사용할 구문에 대한 체크에 일부 시간을 소요할 수밖에 없도록 만드는 계기가 된다.

그리고 반환 타입은 형 변환을 통해 명시화되지만 실제로 반환 타입의 모호로 인해 형 변환 시 ClassCastException은 아이바티스 사용자라면 한번쯤 겪게 되는 에러가 된다. 즉 애매한 형 변환과 문자열 기반의 매핑 구문은 결과적으로 대부분의 에러가 런타임 시 발생하여 개발을 힘들게 만드는 요인이 되고 있다.

이런 점을 해결하기 위해 나온 것인 이 인터페이스 바인딩이다.

기존에 XML에 타입을 선언하고 문자열 기반으로 구문을 호출하는 것 대신에 좀 더 서술적이고 타입에 안전한 인터페이스의 사용으로 앞서 언급된 문제점을 많이 보완해 나가고 있다. 다음의 코드는 기존 방식의 호출을 새로운 인터페이스 바인딩 방법으로 호출한 것이다.

애매한 형 변환 작업이 없고 문자열 기반의 매핑 구문을 사용하지 않아 기본적으로 컴파일 시 많은 에러를 찾아낼 수 있다. 요즘처럼 IDE를 기본적으로 사용하는 환경이라면 얼마나 많은 도움이 될지는 의심할 여지가 없다.

결과적으로 인터페이스 바인딩을 통해 매핑 구문명과 파라미터 타입, 결과 타입을 자동적으로 알 수 있게 되는 셈이다.

다중 레벨 설정
아이바티스의 오랜 설정 방식인 XML은 3.0에서도 역시 좋은 방법이 되겠지만 3.0에서 디폴트 설정 방법이 되지는 않는다. 3.0에서는 다음과 같은 다중 레벨 설정이 가능하게 된다.

- 관례(Convention)
- 어노테이션(말 그대로 하면 주석) : 앞의 관례보다 우선시 된다.
- XML : 앞의 관계, 어노테이션보다 우선시 된다.
- 자바 API : 앞의 관례, 어노테이션, XML보다 우선시 된다.
관례는 메소드 시그니처로 정해진 규칙이라고 보면 된다.

Employee getEmployee (int id); 라는 메소드 시그니처는 다음과 같은 SQL과 동일 시 된다고 볼 수 있다.

SELECT id, firstName, lastName FROM Employee WHERE id = ?

여기서 메소드 반환 타입의 이름이 테이블 명이 되고 메소드의 인자로 주어진 값이 자동으로 WHERE 조건문을 구성한다고 볼 수 있다. 간단한 조건문이나 들어가는 형식의 전체 데이터를 뽑는 기능이라면 이 메소드를 선언하여 추가 작업 없이 해당 기능이 구현할 수 있다.

어노테이션으로 설정하기
XML 설정을 넘어서 어노테이션으로 메타데이터 정보를 설정하는 기능이 많이 도입되었다. 이에 아이바티스도 이러한 기능을 추가적으로 지원한다. 현재 아이바티스의 XML 파일은 다음과 같은 정보를 가진다.

- 설정
- 메타 정보
- 코드

설정은 환경적인 정보이다.

메타정보는 결과 맵과 파라미터 맵, 그리고 캐시 모델들을 나타내고 코드는 SQL과 동적 SQL 요소를 포함한다. 설정 정보는 properties 파일이나 XML에 담겨야하고, 코드는 자바 코드나 XML에 담겨야 한다. 단 메타정보만이 어노테이션에서 처리가 가능하도록 될 것이다.

어노테이션의 예제는 다음과 같다. 앞의 관례에 의한 방식에서 칼럼 별 타입을 정의하고 조건문을 구체화하기 위해 어노테이션이 사용된 것을 볼 수 있다.

 <리스트 13> CRUD 작업을 위한 어노테이션 사용 예제


@Select({"SELECT #id(EMP_ID:NUMERIC), #firstName(FIRST_NAME:VARCHAR), #lastName(LAST_NAME:VARCHAR) ",
"FROM EMPLOYEE",
"WHERE EMP_ID = @id"})
Employee selectEmployee(int id);

@Insert({"INSERT INTO EMPLOYEE (EMP_ID, FIRST_NAME, LAST_NAME)",
"VALUES (@id, @firstName, @lastName)"})
void insertEmployee(Employee emp);

@Delete("DELETE EMPLOYEE WHERE EMP_ID = @id")
void deleteEmployee(int id);



<리스트 14>와 같은 형식의 어노테이션도 사용 가능하게 된다. 여기서는 ResultClass와 PropertyResult를 추가적으로 어노테이션을 통해 정의한 것이다.

 <리스트 14> ResultClass, PropertyResults를 사용하는 어노테이션 예제


@ResultClass (Department.class)
@PropertyResults({
@Result(property="id", column="DEPT_ID"),
@Result(property="name", column="NAME"),
@Result(property="employees",
nestedQuery=@QueryMethod(type=CompanyMapper.class, methodName="getEmployeesForDeparment", parameters="id"))
})
@Select("SELECT #id, #name FROM DEPARTMENT WHERE COMP_ID = @id ")
List<Department> getDepartmentsForCompany(int id);



<리스트 15>와 같이 캐싱을 설정할 수도 있다.

 <리스트 15> 캐싱 설정의 어노테이션 사용 예제


@CacheContext("Employee")
public class EmployeeMapper {
void insertEmployee(Employee emp);
Employee getEmployee(int id);
List<Employee> findEmployeesLike(Employee emp);
}



기존 설정이라면 다음과 같은 의미를 가진다.

 <리스트 16> 캐싱 설정의 기존방식 예제


<Mapper cacheContext="Employee">
<Insert id="insertEmployee" ...> ... </Insert>
<Select id="getEmployee" ...> ... </Select>
<Select id="findEmployeesLike" ...> ... </Select>
</Mapper>



@flushCache
List<Employee> updateAndGetSpecialEmployees();
는 다음과 같은 의미를 가진다.

<Select id=“findEmployeesLike” flushCache==“true”...> ... </Select>

예제를 통해 보면 현재의 설정을 일일이 XML 파일에 적어야 할 사항을 어노테이션을 통해 설정을 함으로써 일단 XML 파일 내용의 갱신 문제에 좀 더 자유로워 질 수 있을 듯하다. 한편, 개발자가 초기 XML 설정 외에 대부분의 작업을 자바 소스에서 제어함으로써 작업도 한결 수월하게 할 수 있을 것이다.

XML 설정 향상
다음은 XML 설정 향상과 관련된 주요 내용들이다.

1. 필드와 생성자 파라미터 그리고 자바빈즈 프로퍼티에 결과 및 파라미터를 맵핑된다.
2. N+1 문제를 해결하는 조인 매핑과 groupBy는 사용이 더 쉬워진다. <리스트 17>의 요소를 사용해서 하위 결과 맵을 생성하는 작업이 필요 없도록 한다.
3. 결과 맵과 파라미터 맵은 기본적으로 ‘자동 매핑’ 되고 이름이 일치하지 않는 프로퍼티만을 명시한다.
4. 기능적으로 좀 더 뛰어나면서 간단한 타입 핸들러 구현체와 데이터 타입 변환 필터를 제공할 것이다.
5. 가장 큰 변화는 XML 파일이 Mapper.class의 복사본과 함께 처리한다는 점이다. 이를테면 EmployeeMapper.xml은 클래스패스에 존재하는 EmployeeMapper.class를 위해 로드 된다. 해당 매퍼 클래스의 객체를 생성하면 자동으로 같은 이름의 매퍼 XML 파일이 로드 되는 형식이라고 짐작된다. 객체 생성에 따라 매퍼 XML 파일이 로드 되는 형식이라면 SQL 구문의 갱신이 3.0에서는 어느 정도 해결될 듯하다.

 <리스트 17> Mapper용 XML설정


<Mapper>
<ResultMap id="selectACompanyWithJoin" resultClass= "Company">
<Constructor column="C.COMP_ID"/>
<Constructor column="C.NAME"/>

<Property name="departments.id" column="D.DEPT_ID"/>
<Property name="departments.name" column="D.NAME"/>

<Collection type="Department.class" property= "departments" groupBy="id"/>
<Collection type="Employee.class" property= "departments.employees" groupBy="departments.id"/>
</ResultMap>

<Select id="selectACompanyWithJoin" parameters= "id:int,order:string">
SELECT
#{id},
#{name},
#{departments.id},
#{departments.name},
FROM COMPANY C
INNER JOIN DEPARTMENT D ON C.COMP_ID = D.COMP_ID
INNER JOIN EMPLOYEE E ON D.DEPT_ID = E.DEPT_ID
WHERE
C.COMP_ID = @{id}
ORDER BY ${order}
</Select>
</Mapper>



동적 SQL
현재의 요소를 사용하는 방법 외에도 3.0에서는 다음과 같은 방법이 가능하게 된다. 아무래도 자바 개발자에게는 XML 에서 문자열을 조작하는 것보다는 자바 로직으로 처리하는 편이 훨씬 수월할 것이다.

 <리스트 18> SQLSource를 확장한 동적 SQL시용 예제


public class GetAccountListSQL extends SQLSource {
public String getSQL(Object param) {
Account acct = (Account) param;
append("select * from ACCOUNT");
prepend(“WHERE”); // 다음의 append 앞에 기본적으로 추가한다.
if (exists(acct.getEmailAddress())) {
append("AND", "ACC_EMAIL like #EmailAddress#"); // 필요하다면 첫 번째 인자를 붙이겠지만 그렇지 않다면 첫 번째 인자는 무시된다.
}
if (greaterThan(0,acct.getID())) {
append("AND", "ACC_ID = #ID#");
}
prepend(); // 앞의 prepend 뒤에 아무 코드가 없다면 앞의 prepend를 지운다.
append ("order by ACCT_LAST_NAME");
}
}



<리스트 18>은 다음과 같이 사용할 수 있다.

@SQLSource(GetAccountListSQL.class)
List<Accout> getAccountList(Account acct);

또는 다음처럼도 가능하다.

<Select id=“getAccountList” source=“org.apache.GetAccount ListSQL” ...> ...
</Select>

그 외에 테이블 관계를 나타내기 위한 간단한 기능이 추가되고 SQL 생성을 지원하는 몇 가지 옵션도 추가될 것으로 보인다.

지금까지 개인적으로 원했던 기능 중에 3.0에서 추가되도록 활발히 진행 중인 것 위주로 살펴보았다. 하지만 곰곰이 생각해보면 아이바티스가 추구했던 간결함은 오히려 반감되고 있는 게 아닌가 하는 우려도 든다. 아이바티스의 장점은 간결함이다. JDBC에 익숙한 개발자라면 몇 시간의 교육만으로도 실제 프로젝트에 적용할 수 있을 정도다.

단점이라면 매핑 작업이 번거롭다는 점을 들 수 있을 것이다. 파라미터 맵과 결과 맵에 매핑해주는 작업이 JDBC를 그대로 사용하는 것보다 그렇게 쉬운 작업이 아니다. 그럼에도 불구하고 아이바티스를 많이 사용하게 되는 데에는 충분히 사용할만한 장점이 있기 때문일 것이다. 앞으로도 발전하는 아이바티스의 모습을 기대해 본다. @

참고자료
1. 아이바티스 홈페이지
2. AppFuse 홈페이지
3. 아이바티스 공식 문서 한글화 프로젝트
4. ORM의 또 다른 핵 iBATIS SQLMaps
5. 아이바티스에서 프로시저사용하기
6. http://www.mail-archive.com/user-java@ibatis.apache.org/msg02045.html
7. 아이바티스 3.0 화이트보드


* 이 기사는 ZDNet Korea의 제휴매체인 마이크로소프트웨어에 게재된 내용입니다.
Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:23
반응형

실무에서 SQL문을 작성하다 보면 동적인 쿼리문 작성을 작성해야 할 때가 많이 있다.
이때 지겹게 if~else if 문을 통해 아주 지저분한 소스 코드를 생성할 때가 왕왕 있게 마련이다.
이때 ibatis에서는 아주 깔금하게 구현할 수 있는 방법을 제공해 준다.

<statement id="dynamicGetAccountList" resultMap="account-result">
  select * from account
  <dynamic prepend="WHERE">
    <isNotNull prepend="AND" property="firstName">
      (acc_first_name = #firstName#
    <isNotNull prepend="OR" property="lastName">
       acc_last_name = #lastName#
    </isNotNull>
    )
    </isNotNull>
    <isNotNull prepend="AND" property="emailAddress">
      acc_email like #emailAddress#
    </isNotNull>
    <isGreaterThan prepend="AND" property="id" campareValue="0">
      acc_id = #id#
    </isGreaterThan>
  </dynamic>
  order by acc_last_name
</statement>

상황에 의존적인 위 동적 statement로 부터 각각 다른 16가지의 SQL문이 생성될 수 있다. if-else구조와 문자열 연결을 코딩하는 경우 수백라인이 필요할 수도 있다.
동적 statement를 사용하는 것은 몇몇 조건적인 태그를 추가하는 것처럼 간단하게 작성할 수 있다.

이러한 조건들에 대해 간단히 정리하면 아래와 같다.

바이너리 조건 요소-바이너리 조건 요소는 정적값 또는 다른 프로퍼티값을 위한 프로퍼티값과 비교한다. 만약 결과가 true라면 몸체부분의 SQL쿼리가 포함된다.

바이너리 조건 속성

prepend

Statement에 붙을 오버라이딩 가능한 SQL부분(옵션)

property

비교되는 property(필수)

compareProperty

비교되는 다른 property (필수 또는 compareValue)

compareValue

비교되는 값(필수 또는 compareProperty)


<isEqual>

프로퍼티가 값 또는 다른 프로퍼티가 같은지 체크

<isNotEqual>

프로퍼티가 값 또는 다른 프로퍼티가 같지 않은지 체크

<isGreaterThan>

프로퍼티가 값 또는 다른 프로퍼티 보다 큰지 체크

<isGreaterEqual>

프로퍼티가 값 또는 다른 프로퍼티 보다 크거나 같은지 체크

<isLessThan>

프로퍼티가 값 또는 다른 프로퍼티 보다 작은지 체크

<isLessEqual>

프로퍼티가 값 또는 다른 프로퍼티 보다 작거나 같은지 체크


사용법 예제)

<isLessEqual prepend="AND" property="age" compareValue="18">

  ADOLESCENT = 'TRUE'

</isLessEqual>

단일 조건 요소-단일 조건 요소는 특수한 조건을 위해 프로퍼티의 상태를 체크한다.

prepend

statement에 붙을 오버라이딩 가능한 SQL부분(옵션)

property

체크하기 위한 프로퍼티(필수)

<isPropertyAvailable>

프로퍼티가 유효한지 체크

(이를 테면 파라미터의 프로퍼티이다.)

<isNotPropertyAvailable>

프로퍼티가 유효하지 않은지 체크

(이를 테면 파라미터의 프로퍼티가 아니다.)

<isNull>

프로퍼티가 null인지 체크

<isNotNull>

프로퍼티가 null이 아닌지 체크

<isEmpty>

Collection, 문자열 또는 String.valueOf() 프로퍼티가 null이거나 empty(“” or size() < 1)인지 체크

<isNotEmpty>

Collection, 문자열 또는 String.valueOf() 프로퍼티가 null 이아니거나 empty(“” or size() < 1)가 아닌지 체크


사용법 예제)

<isNotEmpty prepend="AND" property="firstName">

  FIRST_NAME = #firstName#

</isNotEmpty>

다른 요소들
Parameter Present : 파라미터 객체가 존재하는지 체크
Parameter Present Attributes : prepend - the statement에 붙을 오버라이딩 가능한 SQL부분

<isParameterPresent>

파라미터 객체가 존재(not null)하는지 체크

<isNotParameterPresent>

파라미터 객체가 존재하지(null) 않는지 체크

 

사용법 예제)

<isNotParameterPresent prepend="AND">

EMPLOYEE_TYPE = 'DEFAULT'

</isNotParameterPresent>

Iterate : 이 태그는 Collection을 반복하거나 리스트내 각각을 위해 몸체 부분을 반복한다.
Iterate Attributes :
  prepend - the statement에 붙을 오버라이딩 가능한 SQL부분 (옵션)
  property - 반복되기 위한 java.util.List타입의 프로퍼티 (필수)
  open - 반복의 전체를 열기 위한 문자열, 괄호를 위해 유용하다. (옵션)
  close - 반복의 전체를 닫기 위한 문자열, 괄호를 위해 유용하다. (옵션)
  conjunction - 각각의 반복 사이에 적용되기 위한 문자열, AND 그리고 OR을 위해 유용하다. (옵션)

<iterate>

java.util.List 타입의 프로퍼티 반복


사용법 예제)

<iterate prepend="AND" property="userNameList" open="(" close=")" conjunction="OR">

username = #userNameList[]#

</iterate>


주의:iterator요소를 사용할 때 리스트 프로퍼티의 끝에 중괄호[]를 포함하는 것은 중요하다. 중괄호는 문자열처럼 리스트를 간단하게 출력함으로부터 파서를 유지하기 위해 리스트처럼 객체를 구별한다.

Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:21
반응형

1. iBatis란?

iBatis는 데이터베이스에 있는 자원들을 보다 편리하게 가져오기 위한 기술이다. 하지만 DB에 있는 테이블과 자바 객체간의 직접적인 동기화를 이루는 ORM(Object Relational Mapper)는 아니며, 자바빈즈를 PreparedStatement의 바인드 변수인 파라미터(?)와 ResultSet으로 맵핑시켜주는 기능으로 지금에 와서는 SQL Maps 또한 ORM이라고도 한다.

즉, iBatis는 sql문이 자동 생성되는 것이 아니며, 특정 XML문서에 임베디드된 sql문을 개발자가 직접 정의 하여 자바 코드와 sql문을 XML문서로 분리를 시켜 놓는다. 그리고 자바 객체를 sql문의 XML문서에서 맴핑을 걸어 놓아 보다 쉽게 Value Object를 얻기위해 사용되는 SQL Mapper이다.


2. 환경설정

 2-1) 우선 다음 사이트에 접속하여 오른쪽 항목에서 [iBaits]를 선택하여 iBatis의 라이블러리를 내려 받으러 접근한다.

이미지를 클릭하면 원본을 보실 수 있습니다.


2-2) 다음에 확인되는 화면의 왼쪽 메뉴 중..[Get software...]에 있는 [for Java]를 선택한다.

이미지를 클릭하면 원본을 보실 수 있습니다.


2-3) 다음 그림과 같이 [Download iBATIS Java 2.X.X]를 선택한다.

이미지를 클릭하면 원본을 보실 수 있습니다.


2-4) 다운로드 대화창에서 [저장]을 선택하여 기억하기 쉽고 원하는 장소에 내려받아 압축을 풀어둔다.

이미지를 클릭하면 원본을 보실 수 있습니다.


2-5) 압축을 풀었다면 다음 그림과 같은 폴더가 생겼을 것이다. 여기까지 확인이 되었다면 우리의 넷빈을 실행 시켜 보자!

이미지를 클릭하면 원본을 보실 수 있습니다.


2-6) 다음과 같은 넷빈 초기화면에서 앞서 준비했던 Oracle 라이블러리와 iBatis 라이블러리를 사용할 수 있도록 [Libraties]에 등록해 보자!

이미지를 클릭하면 원본을 보실 수 있습니다.


2-7) 다음과 같은 Library Manager창에서 아래쪽에 있는 [New Labrary...]버튼을 선택하여 새로운 라이블러리를 등록하기 위해 그림과 같이 입력한다.

이미지를 클릭하면 원본을 보실 수 있습니다.


2-8) 그리고 다음과 같이 Oracle라이블러리 파일을 선택하여 추가 한다.

이미지를 클릭하면 원본을 보실 수 있습니다.


2-9) 여기까지 했다면, 다음 그림과 같이 jar파일이 추가 됐음을 알 수 있다.

이미지를 클릭하면 원본을 보실 수 있습니다.

물론 이미 Oracle라이블러리가 등록되어 있었다면 지금까지 2-7에서 2-9까지의 작업은 생략해도 된다.


2-10) 이제 iBatis 라이블러리를 등록하기 위해 [New Labrary...]버튼을 선택한 후, 다음 그림과 같이 입력하고 [OK]버튼을 선택하자!

이미지를 클릭하면 원본을 보실 수 있습니다.


2-11) 그리고 [Add Jar/Folder...]버튼을 선택하여 앞서 iBatis 라이블러리 파일을 압축 해제한 곳에 [lib]라는 폴더가 있다. 그곳의 내용이 다음 그림과 같을 것이다. 선택한 후 추가 하자!

이미지를 클릭하면 원본을 보실 수 있습니다.


이렇게 해서 iBatis 라이블러리를 등록함과 동시에 준비 작업은 끝이 났다. 첫 강좌를 마무리 하면서 이런 iBatis 라이블러리의 Tutorial문서는 앞 2-3화면에서 살~짝만 내리면 다음 그림과 같이 [Korean]항목이 보기 드물게 [Japanese]보다 우위에 있는 것을 확인 할 수 있다.

원하는 국적을 선택하여 접근해 보자! Tutorial문서가 pdf파일로 제공됨을 알 수 있다. ^^;

이미지를 클릭하면 원본을 보실 수 있습니다.

http://cafe.daum.net/javaclubj
Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:18
반응형



iBATIS 에 대한 보다 자세한 소개는 http://ibatis.com 을 참고하세요

iBATIS  도입 이유

iBATIS 는 그들의 말로는 Data Mapper 라고 합니다.(iBATIS 개발자 가이드 참고)

그들이 이야기하듯이 복잡할 수도 있지만 일부러 간단한 사용법을 제공함으로써 사용의 혼란과 난이도를 낮추고자 했습니다.

아마 제작자들도 ORM 툴과는 그 성격이 다르게 생각하고 있는 모양입니다.

JAVA 에서 Object 를 사용함에 있어서 getXXX , setXXX 로의 접근은 사용법과 유지보수 측면에서 이미 standard 로 여겨지고 있습니다.

즉 대부분의 작업자들이 이렇게 작업을 합니다.

그런데 이러한 객체를 만들어 낼때 ORM(혹은 Data Mapper) 을 사용하기 전 아래와 같은 코드를 가지고 작업을 할것입니다.

 

MemberData mb=new MemberData();
  
  mb.setId(req.getParameter("ID"));
  mb.setPassword(req.getParameter("PASSWORD"));
  mb.setAddress(req.getParameter("ADDRESS"));
  mb.setAdminMemo(req.getParameter("ADMIN_MEMO"));
  mb.setBirthDay(req.getParameter("BIRTHDAY1")+req.getParameter("BIRTHDAY2")+req.getParameter("BIRTHDAY3"));
  mb.setBirthGubun(req.getParameter("BIRTH_GUBUN"));
  mb.setEngName(req.getParameter("ENG_NAME"));
  mb.setGender(req.getParameter("GENDER"));
  mb.setHandPhone(req.getParameter("HAND_PHONE1")+"-"+req.getParameter("HAND_PHONE2")+"-"+req.getParameter("HAND_PHONE3"));
  mb.setHomepage(req.getParameter("HOMEPAGE"));
  mb.setKrName(req.getParameter("KR_NAME"));
  mb.setMail(req.getParameter("MAIL"));

mgr.insert(mb);

 

정말 단순한 노가다의 연속이라는 ;;;

하지만 iBATIS 를 사용하게 되면 아래와 같이 코드가 심플해 집니다.

 

mb=(MemberData)sqlMap.queryForObject("getMember",id);

mgr.insert(mb);

 

위의 단순한 비교에서 보시다 시피 상당한 코딩의 축소로 인해 개발자로 하여금 지겨운 노가다에서 해방하게 됩니다.

(익숙해 질경우 20%로 축소 시켜 준다고 합니다.)

개발자가 필수로 갖춰어야 할 "게을러질 수 있는 권리' 를 찾을 수 있도록 해줍니다.

 

iBATIS 의 설치

설치는 한가지 설정으로 끝납니다.

http://ibatis.com 을 방문해서 iBATIS 최신 버젼을 다운로드 하셔서 lib 디렉토리에 있는 ibatis-XXX 인 세개의 파일을 APP_HOME/WEB-INF/lib 디렉토리에 넣으시고 CLASSPATH 를 설정하시기 바랍니다.

 

iBATIS 설정 파일의 종류

설정 파일은 DATABASE 의 환경을 선택하는 xml 파일과 각 Table 에 쿼리를 저장하는 xml 파일의 두가지 종류로 나뉩니다.

파일명과 위치는 전혀 상관이 없습니다만 WEB-INF/classes 하위에 두어야 찾아가기 쉽겠죠?

아래의 스크린 샷은 사용중인 xml 파일의 위치입니다.

 

 

sqlMapConfig.xml 파일이 설정 파일이며 Member.xml 파일은 Member 에 대한 쿼리문을 담고 있습니다.

db.properties 파일은 Reader 가 읽어갈 Database 접속 정보를 담고 있습니다.

 

Beans 만들기

회원 관리를 위해서 간단한 Beans를 만들어 보겠습니다.

Bean 이름은 MemberBean으로 하고 member 패키지 하위에 만들어 보도록 하겠습니다.

먼저 이클립스에서 회원이 가질수 있는 정보를 설정하도록 합니다.

아래 이미지 처럼 설정이 되겠지요?

 

그 이후에 이클립스에서 간단하게 getter 와 setter 를 설정할 수 있습니다.

 

 

이렇게 만들면 bean 작성이 끝났습니다.

이후의 작업들은 다음과 같습니다.
환경 설정 xml 파일 만들기
쿼리문 xml 파일 만들기\Connection Mannager 만들기
각 Mannager Class 에서 사용하기

 

저작자 표시
      Tag - ibatis
      DB/iBATIS  |  2008/10/06 02:41





이 기사는 ZDNet Korea의 자매지인 마이크로소프트웨어에 게재된 내용이며 저작권에 관련한 모든 사항은 마이크로소프트웨어에 있습니다.
이 문구를 삭제하고 배포시 저작권에 위배될수 있습니다.

저자 정보 : 이동국(mailto:fromm0@gmail.com)

현재 울산에 있는 아이티스타에서 근무하고 있으며 주로 현대자동차 관련 프로젝트에 참여하고 있다. 네이버 자바프레임워크 카페 스탭으로 할동하고 있고 오픈프레임워크 사이트를 관리하고 있다. 평소 개발 프레임워크와 웹서비스에 관심을 가지고 있으며 요즘은 AndroMDA에 관련해서 공부를 시작했다.

ORM의 또 다른 핵 iBATIS SQLMaps

작년을 비롯해서 현재까지 해외 자바 관련 커뮤니티에서 가장 많이 논의가 되고 있는 부분 중 하나는 ORM이다. 그 이유를 생각해보면 자바개발의 많은 부분을 차지하고 있는 분야가 웹프로그래밍이고 그 웹프로그래밍의 거의 대부분을 차지하는 분야가 데이터베이스관련 처리이기 때문일 것이다. 현재 그 ORM을 대표하는 두 가지가 있다. 하나는 hibernate이고 또 하나는 이번에 소개할 iBATIS의 SQLMaps이다. 한때 객체와 관계형 데이터베이스의 관계 맵핑을 설정하면 SQL문을 자동 생성해 주는 hibernate에 비해 SQL문 자동 생성 기능이 없는 SQLMaps는 진정한 의미의 ORM이 아니다 라는 분위기도 있었지만 관계형 데이터베이스와 통신할 수 있는 유일한 매개체인 SQL문과 객체를 맵핑 시켜준다는 의미에서 현재는 SQLMaps또한 ORM이라고 보고 있다. hibernate의 경우 자바 관련 커뮤니티에서 많이 다루어 진 것으로 알고 있다. 우리는 여기서 SQLMaps에 대해서 알아보고자 한다. SQLMaps의 장점은 무엇일까.? iBATIS에서 밝히는 SQLMaps의 최대 장점은 간단함(simple)이다. 이것은 필자를 포함한 SQLMaps를 사용한 대부분의 개발자들이 인정하는 부분이다. 그 간단함이란 기존의 SQL문을 별다른 수정 없이 그대로 사용할 수 있는 상황에서 단순히 객체와 SQL문의 맵핑을 위한 몇몇 설정값만 설정해 주면 간단하게 ORM의 기능을 경험해 볼 수 있다는 것이다. 이러한 장점은 단순한 설명만으로는 잘 느껴지지 않으리라 본다. 잠시 후 예제를 통해서 정말 SQLMaps가 기존의 JDBC프로그램에 비해 얼마나 간단하게 구현이 되는지 보자.

SQLMaps는

앞에서 필자가 SQLMaps는 SQL과 객체를 맵핑시켜주는 도구라고 소개했지만 사실 엄밀하게 따지면 자바빈즈를 PreparedStatement 파라미터와 ResultSet으로 맵핑시켜주는 기능을 담당한다. 즉 PreparedStatement의 ? 에 각각의 파라미터 값을 setString()과 같은 메소드를 사용해서 셋팅하는 과정이나 ResultSet에서 getString()과 같은 메소드를 통해서 임의의 VO객체를 생성하는 과정을 자동으로 해준다. 결과적으로 간단한 xml셋팅만으로 일일이 손으로 작성하던 기존 JDBC형식을 버릴 수가 있다. SQLMaps의 구조를 도식화하면 다음과 같다.

1.jpg

iBATIS에서는 결과적으로 기존 데이터베이스 프로그램 코드의 20%정도만 사용해도 80%이상의 같은 기능을 수행할 수가 있게 된다고 밝히고 있고 필자 또한 그렇게 생각하고 있다.

이론적인 설명은 이쯤에서 잠시 접어두고 실제 사용하는 것을 보고 과연 어떻게 다른가를 알아보자. 아래의 소스는 얼마 전 오픈시드 프로젝트(http://openseed.net)의 ORM 연구회에서 진행한 1차 테스트 프로젝트의 소스이다. 일단 SQLMaps를 사용하기 위해서는 최소 두 가지의 설정파일이 필요하다. SQLMaps 설정파일과 SQLMaps 맵핑 파일이다. 추가적으로 프라퍼티값을 외부로 빼내기 위해서 사용되는 properties파일이 필요할 수 도 있다. 먼저 SQLMaps설정 파일인 SqlMapsConfig.xml을 보자.

SQLMaps 설정파일

소스 : SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" 
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
   <properties resource="SqlMapConfig.properties" />

   <settings cacheModelsEnabled="true" enhancementEnabled="true"
lazyLoadingEnabled="true" maxRequests="32" 
maxSessions="10" maxTransactions="5" 
useStatementNamespaces="false" />

   <typeAlias alias="comment" type="net.openseed.orm.openboard.domain.Comment"/>
   <typeAlias alias="message" type="net.openseed.orm.openboard.domain.Message"/>
   <typeAlias alias="user" type="net.openseed.orm.openboard.domain.User"/>

   <transactionManager type="JDBC">
      <dataSource type="DBCP">
         <property name="JDBC.Driver" value="${driver}" />
         <property name="JDBC.ConnectionURL" value="${url}" />
         <property name="JDBC.Username" value="${username}" />
         <property name="JDBC.Password" value="${password}" />
         <property name="JDBC.DefaultAutoCommit" value="false" />
      </dataSource>
   </transactionManager>

   <sqlMap resource="Comment.xml" />
   <sqlMap resource="Message.xml" />
   <sqlMap resource="User.xml" />
</sqlMapConfig>

xml파일 내용을 보면 크게 5가지로 분류되어 있다. 첫 번째인 <properties>는 프라퍼티값을 외부로 빼내서 사용하기 위해 프라퍼티파일을 사용할 때 해당 프라퍼티파일의 경로를 지정해준다. 프라퍼티 파일은 ‘키=값’ 의 형태로 값을 지정하고 SqlMapConfig.xml에서는 ${키} 의 형태로 사용할 수 있다.

두 번째인 <settings>은 SQLMaps에서 사용되는 다양한 옵션과 최적화를 위한 값들이다. 각각의 값들은 다음의 표를 참조하길 바란다.

cacheModelsEnabled SqlMapClient 를 위한 모든 캐시모델을 가능 유무.
Default: true (enabled)
enhancementEnabled 런타임시 바이트코드 향상을 가능유무.
Default: false (disabled)
lazyLoadingEnabled 모든 늦은(lazy)로딩을 가능유무.
Default: true (enabled)
maxRequests 동시에 SQL문을 수행할 수 있는 쓰레드의 수. 셋팅값보다 많은 쓰레드는 다른 쓰레드가 수행을 완료할 때까지 블록 된다.
Default: 512
maxSessions 주어진 시간동안 활성화될 수 있는 세션의 수.
Default: 128
maxTransactions 한꺼번에 SqlMapClient.startTransaction()에 들어갈 수 있는 쓰레드의 최대갯수. 셋팅값보다 많은 쓰레드는 다른 쓰레드가 나올 때까지 블록 된다.
Default: 32
useStatementNamespaces 이 셋팅을 가능하게 하면 당신은 sqlmap이름과 statement이름으로 구성된 전체적인 이름(fully qualified name)으로 맵핑된 statement를 참조해야 한다.
예를 들면: queryForObject("sqlMapName.statementName");
Default: false (disabled)

세 번째인 <typeAlias>는 패키지 명을 포함한 클래스가 너무 길 때 각각의 SQLMaps맵핑 파일에서 사용하기 번거로우므로 별칭을 두어서 간단하게 사용할 수 있다. 하지만 미리 정의된 별칭이 있다. 그 값들은 다음과 같다.

transactionManager에서 사용되는 별칭
JDBC com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig
JTA com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig
EXTERNAL com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig
dataSource에서 사용되는 별칭
SIMPLE com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory
DBCP com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory
JNDI com.ibatis.sqlmap.engine.datasource.JndiDataSourceFactory

네 번째인 <transactionManager>는 트랜잭션에 관련된 값을 셋팅하고 하위 데이터소스값을 지정하는 <dataSource>를 가진다. transactionManager의 type속성 값은 JDBC, JTA, EXTERNAL 중에 하나를 사용할 수 있는데 그 각각의 값은 다음과 같은 특징을 가진다.

  • JDBC - Connection commit()과 rollback()메소드를 통해 트랜잭션을 제어하기 위한 JDBC를 사용하게 된다.
  • JTA - 이 트랜잭션관리자는 SQL Maps가 다른 데이터베이스나 트랜잭션 자원을 포함하는 더욱 넓은 범위의 트랜잭션을 포함하도록 하는 JTA전역트랜잭션를 사용한다.
  • EXTERNAL - 이것은 개발자가 직접 트랜잭션을 관리할 때 사용하는 것으로 주로 분산 컴포넌트 기반 시스템 환경에서 컨테이너가 트랜잭션을 관리하는 경우에 사용된다.

dataSource의 type속성 값은 SIMPLE, DBCP, JNDI 중에 하나를 사용할 수 있다. 이 값은 각각 iBATIS SimpleDataSource 커넥션 풀링, Jakarta DBCP, JNDI를 통한 데이터소스을 사용하게 한다.

다섯 번째인 <sqlMap>은 SQLMaps맵핑 파일의 위치를 지정한다. resource속성 값에 전체패키지경로명을 포함하는 클래스 명을 써주면 된다.

기본이 되는 SQLMaps의 설정파일은 이렇게 특별히 어려운 설정 없이 간단하게 설정할 수 있다. 그럼 이제부터는 데이터베이스 작업별로 SQLMaps를 어떻게 사용하는지 보여주기 위한 요소인 자바소스와 SQLMaps맵핑 파일을 보도록 하자. SQLMaps맵핑 파일은 SQLMaps설정파일처럼 전체적인 구조를 볼 필요 없이 이후 설명되는 데이터베이스 작업의 종류에 따라 세부적인 부분만을 보도록 하겠다.

먼저 객체간의 관계는 다음과 같다.

2.jpg

SQLMaps를 사용하기 위한 객체 생성

SQLMaps를 사용하기 위해서 기본적으로 생성해줘야 하는 객체는 SqlMapClient 이다. 이 객체를 생성하기 위해서는 SQLMaps설정파일인 SqlMapsConfig.xml을 인자로 다음과 같이 코드를 작성하면 기본적으로 SQLMaps의 기능을 사용할 수 있는 SQLMapClient 객체가 생성된다. 이 소스는 getSqlMapConfig()라는 메소드이며 반환되는 객체는 이 문서에서 계속 사용된다.

Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
SqlMapClient sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);

조회

조회 작업의 경우 크게 한 개의 테이블을 조회하는 단순조회와 여러 개의 테이블이 서로 관계를 가지는 상태에서 조회하는 다중 테이블 조회의 두 가지의 경우로 나누어서 보겠다.

1. 테이블 한 개의 대한 단순조회

단순조회의 경우를 보기 위해 일단 사용자 정보를 보는 부분을 살펴보기로 하겠다. 사용자 정보에 해당되는 User.java소스는 위의 클래스다이어그램을 통해 파악을 할 수 있을 것이다.

다음은 조회를 위해 사용된 SQLMaps맵핑 파일의 일부이다.

소스 : User.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap>
  <select id="getUser" parameterClass="string" resultMap="user">
  <![CDATA[
    select 
      user_id as id, 
      password as password, 
      nick_name as nickName
    from userinfo
    where user_id=#id#
  ]]>
  </select>
</sqlMap>

dtd문서를 보면 알겠지만 SQL문을 포함할 수 있는 요소는 <statement>, <insert>, <update>, <delete>, <select>, <procedure> 정도가 된다. 각각의 의미는 짐작이 갈 것이다. statement는 모든 SQL문의 형태, insert, update, delete, select는 각각의 데이터베이스 작업, procedure는 프로시저작업을 할때 사용하면 된다. 일단 여기서는 조회작업을 수행하기 때문에 <select>를 사용했다. 속성으로는 id, parameterClass, resultClass가 사용되었는데 id 속성 값은 자바소스에서 해당 쿼리를 참조하기 위한 키의 역할을 담당한다. 키에 대해서는 이 xml 뒤에 나오는 자바소스를 통해 좀 더 자세히 설명할 것이다. parameterClass 속성값이 string이면 인자로 넘어오는 값이 문자열타입임을 나타낸다. 즉 user_id에는 인자로 넘어오는 문자열값이 자동으로 셋팅된다. resultClass속성값인 user는 조회 후 각각의 레코드가 자동으로 user객체 타입으로 생성이 된다는 것을 의미한다. 여기서 user라는 값은 앞에서 별칭된 net.openseed.orm.openboard.domain.User를 나타낸다. 내부적으로는 user객체가 생성이 되서 각각의 칼럼의 값이 setId(id칼럼값), setPassword(password칼럼값), setNickName(nickName칼럼값)를 차례로 호출하여 값을 할당하게 되는 셈이다.

이렇게 내부적으로 처리가 된다면 DAO계층에서 개발자가 어떻게 코딩을 하면 될까.? 그 형식은 다음과 같다.

    public User get(String id) {
        SqlMapClient sqlMap = null;
        User user = null;

        try {
            sqlMap = getSqlMapConfig();
            user = (UsersqlMap.queryForObject("getUser", id);
        catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return user;
    }

단순히 소스코드에서 SQL문과 자바소스만 분리한 것 같은데도 너무 간단하지 않은가.? 사실. 예제의 SQL문 자체가 너무 단순해서 간단함이 잘 느껴지지 않을 수 있으나 테이블 자체가 굉장히 많은 칼럼을 가지고 있었던 예전 경험을 생각해 보라. 이것은 사용자로 하여 SQLMaps를 사용하면 굉장히 지겨운 JDBC코드작성에서 벗어나고 그 과정에서 발생할 수 있는 많은 버그도 줄일 수가 있을 것이다.

2. 두개이상의 테이블에 대한 조회

그렇다면 다른 테이블과 1:1, 1:M, 또는 M:N 등의 다중 관계를 가지는 경우에는 어떻게 처리할까.? 그 예는 메시지관련 부분을 보면 된다. Message는 게시판에서 사용자에게 보여지게 되는 실제 글에 해당되는 객체로써 Message.java를 사용한다.

이러한 관계를 SQLMaps맵핑 파일에서는 어떻게 표현할까.? 그 표현방식은 다음에서 볼 수 있다.

소스 : Message.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="Message">
  <resultMap id="get-message" class="message">
    <result property="id" column="id"/>
    <result property="subject" column="subject"/>
    <result property="content" column="content"/>
    <result property="hitCount" column="hitCount"/>
    <result property="logTime" column="logTime"/>
  </resultMap>
  <resultMap id="get-message-result" class="message" extends="get-message">
    <result property="user.id" column="user_id"/>
    <result property="user.password" column="password"/>
    <result property="user.nickName" column="nickName"/>
    <result property="comments" column="{id=id}" select="getCommentList" />
  </resultMap>

  <select id="getMessageList" resultMap="get-message-result">
  <![CDATA[
    select a.seq as id,
      a.subject as subject,
      a.content as content,
      a.user_id as user_id,
      a.hit_count as hitCount,
      a.log_time as logTime,
      b.password as password,
      b.nick_name as nickName
    from message a, userinfo b
    where a.user_id=b.user_id
    order by a.seq desc
  ]]>
  </select>
</sqlMap>

여기서 눈여겨보아야 하는 것은 resultMap이다. 이는 복합적인 타입을 사용할 때 굉장히 유용하다. 물론 복합타입에만 사용할 필요는 없지만 필자는 기본적으로 복합타입이 아닐 경우 resultClass를 사용한다. 여기에서 첫 번째 resultMap은 기본적인 message정보를 가진다. 두 번째는 짐작할 수 있듯이 메시지 객체의 사용자정보와 여러 개의 덧글에 대한 정보를 가진다. 여기서 두번째 resultMap은 첫번째 resultMap의 정보를 가져오기 위해 extends 속성을 사용한다. 즉 extends는 두개의 resultMap를 연결하는 연결자 역할을 하게 된다. 이 resultMap에는 내포된 객체에 값을 셋팅하는 방법도 포함하고 있다. <result property="user.id" column="user_id"/> 여기서 property값인 user.id가 셋팅될 객체의 프라퍼티를 가리킨다. 다시 말해 Message객체내의 user객체의 id값을 user_id칼럼의 값으로 셋팅하는 것이다. 굳이 자바 소스로 표현해 본다면 다음과 같을 것이다. message.getUser().setId( rs.getString("user_id") ); 이런 처리방식으로 내포된 다양한 객체에 대한 접근도 가능하다. 그렇다면 메시지 객체내의 여러 개의 덧글을 의미하는 Collection형태의 객체는 어떻게 생성할까.? 그 방법은 <result property="comments" column="{id=id}" select="getCommentList" /> 에 모두 담겨있다. 여기서는 두 가지가 설명되어야 한다. 먼저 select값인데 이는 getCommentList라는 id값을 가지는 쿼리를 호출한다. getCommentList라는 id값을 가지는 쿼리는 다음과 같다.

소스 : Comment.xml

   <select id="getCommentList" parameterClass="comment" resultMap="get-comment-result">
  <![CDATA[
    select cu.seq as seq, 
      cu.content as content, 
      cu.log_time as logTime,
      cu.user_id as userId, 
      cu.message_seq as messageSeq, 
      cu.nick_name as nickName,
      cu.password as password, 
      m.seq as mseq,
      m.subject as subject, 
      m.content as mcontent,
      m.user_id as muserId, 
      m.hit_count as hitCount, 
      m.log_time as mlogTime
      from (select c.seq, c.content,
      c.log_time, c.user_id, c.message_seq,
      u.nick_name, u.password
    from comment c, userinfo u
    where c.user_id=u.user_idcu, message m
    where cu.message_seq=m.seq
    and cu.message_seq=#id#
    order by cu.seq asc
  ]]>
  </select>
]]>
</select>

이 쿼리는 실제 Comment.xml이라는 xml파일에 있는데 SQLMaps맵핑파일이 SQLMaps설정파일에 모두 선언이 되어 있다면 각각의 맵핑파일내 SQL문은 어디서든 호출이 가능하다. 이것은 굉장히 큰 장점이다. 해당 SQL문이 xml내에서 뿐 아니라 자바소스내에서도 어디서든 호출된다는 것은 해당 테이블의 구조가 변경될시 기존의 JDBC형태의 프로젝트처럼 프로그램마다 쿼리문을 수정했던 어려움 없이 해당 맵핑 파일의 SQL문 하나만을 수정하면 전체 프로그램에 반영이 된다는 것이다. 필자는 독자 여러분이 이런 부분을 정말 큰 장점이라고 인식해주길 바란다. select="getCommentList"을 사용함으로써 자동적으로 <result>에는 List형태의 값이 자동으로 셋팅된다. 그럼 많은 독자분들이 조회조건에 해당되는 값은 어떻게 넘겨야 하는지에 대한 의문이 들것이다. 이에 대한 답은 column속성에 모두 담겨있다. 지금 이 소스의 column값은 {id=id} 이다. 이것은 특정객체의 id값을 id칼럼의 값으로 셋팅한다는 것이다. 즉 {id=id}에서 전자의 id는 값이 셋팅되는 객체의 id라는 변수이고 후자의 id는 id칼럼의 값이다. 결과적으로 현재 getCommentList쿼리의 parameterClass인 comment객체의 id값을 id칼럼의 값으로 셋팅해서 넘기게 된다. 만약에 두개 이상의 조회조건, 예를 들면 comment객체의 id값과 content값을 인자로 넘겨야 한다면 ,(콤마)를 구분자로 주어서 {id=id,content=content} 형태로 column값을 설정하면 된다. 지금까지 설명한 것은 getCommentList의 parameterClass가 원시타입이 아닌 특정객체타입 일때의 경우이고 단순히 원시타입인 string이나 int타입이라면 column값을 id 라고 지정해 주면 된다. 즉 칼럼명만 지정해주면 된다. 이는 id칼럼값을 그냥 그대로 넘기는 것을 의미한다. 결과적으로 id가 getMessageList인 SQL문을 호출하면 메시지에 관련된 값이 자동으로 get-message-result라는 값의 resultMap에 셋팅되고 이에 자동적으로 다시 getCommentList를 호출해서 해당 값을 다시 Collection형태의 값으로 채워준다.

자바소스에서의 처리는 다음과 같다.

PaginatedList list = (PaginatedListsqlMap.queryForPaginatedList("getMessageList""", range);
for (int i = 0; i < page - 1; i++) {
  list.nextPage();
}

여기서 queryForPaginatedList 메소드는 SQLMaps에서 페이지처리를 위해 기본으로 제공하는 메소드이다. 이것을 사용하면 페이지처리를 자동으로 해준다. 조회는 이렇게 두 가지 경우만 살펴보면 거의 대부분의 상황에 대해 설명이 되는 듯 하다. 그럼 입력의 경우를 살펴보도록 하자.

입력

  <insert id="insertMessage" parameterClass="message">
    <selectKey resultClass="int" keyProperty="id">
    <![CDATA[
      select nextval('hibernate_sequence');
    ]]>
    </selectKey>
  <![CDATA[
    insert into message(seq, subject, content, user_id, hit_count, log_time)
    values(#id#, #subject#, #content#, #user.id#, 0'today')  
  ]]>
  </insert>

입력 또한 조회의 경우와 크게 다르지 않다. parameterClass에 해당되는 객체의 값이 각각의 #값# 에 셋팅된다. 조회의 경우와 다르게 추가되는 부분은 <selectKey>요소이다. 이 요소는 선택사항으로 key에 해당되는 값을 임의로 조회결과값으로 설정할 수가 있도록 지원하는 기능을 가진다. 즉 selectKey내의 쿼리문에 의해 반환되는 값이 #id#값에 대치가 된다는 것이다.

자바소스 내에서는 다음과 같이 처리할 수 있다.

        try {
            sqlMap = getSqlMapConfig();
            sqlMap.startTransaction();

            result = (IntegersqlMap.insert("insertMessage", message);

            sqlMap.commitTransaction();
            success = true;
        catch (NestedException ne) {
            logger.error(ne.getMessage(), ne);
        catch (Exception e) {
            logger.error(e.getMessage(), e);
        finally {
            sqlMap.endTransaction();
        }

이 소스의 insert()메소드는 결국 다음과 같은 작업을 수행하는 셈이다.

String sql = "insert into message(seq, subject, content, user_id, hit_count, log_time)
values(?, ?, ?, ?, 0, 'today')"
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, message.getId());
pstmt.setString(2, message.getSubject());
pstmt.setString(3, message.getContent());
pstmt.setString(4, message.getUser().getId());
result = pstmt.executeUpdate();

여기서 필자는 추가적으로 SQLMaps내에서의 트랜잭션 처리에 대해서 언급하도록 하겠다. SQLMaps의 트랜잭션은 sqlMap.startTransaction() 메소드를 호출함으로써 시작된다. 커밋을 수행하기 위해서는 sqlMap.commitTransaction() 메소드를 호출하면 된다. 하지만 SQLMaps API를 잠시 들여다 보면 SQLMaps에는 롤백에 관련된 메소드가 없다. 아니 없다기 보단 임의로 롤백을 수행하는 메소드가 없다. SQLMaps에서는 예외가 발생할 경우 sqlMap.endTransaction()메소드를 호출함으로써 롤백을 수행할 수 있다. 즉 sqlMap.endTransaction()은 정상적으로 모든 작업이 수행이 되면 트랜잭션을 커밋하고 connection을 닫는 작업을 자동으로 수행하게 되고 만약 예외가 발생했을 경우 트랜잭션을 롤백하고 connection을 닫는 작업을 자동으로 수행한다.

이런 트랜잭션처리를 하더라도 결과적으로 제대로 트랜잭션처리가 안되는 경우가 있다. MySQL예전 버전(필자가 테스트한 바로는 4.0.x버전)에서는 정상적인 처리가 되지 않고 각각의 작업이 수행 직후 바로 커밋되어버린다. 이는 iBATIS에서도 공식적으로 알려진 부분이고 다른 데이터베이스나 MySQL 4.1.x버전을 포함한 이후버전에서는 문제가 없다.

수정과 삭제의 작업은 입력의 작업과 사실 거의 유사하다. 그래서 수정과 삭제에 대한 추가적인 설명은 여기서 생략하도록 하겠다.

Spring에서 SQLMaps 사용하기.

Spring은 현재 비즈니스 레이어를 담당하는 오픈소스 프레임워크중에 거의 독보적인 위치를 차지하고 있다. Spring을 사용하면 IoC형태로 트랜잭션을 관리할 수도 있고 SQLMaps를 좀더 쉽게 사용할 수 있도록 도와준다.

Spring에서는 SQLMaps 1.x와 2.x를 지원하기 위한 클래스가 다르다. 물론 xml설정파일에서 셋팅하는 값이 다르다. 여기서는 2.x버전을 지원하는 내용만을 다룬다.

SqlMapConfig.xml을 applicationContext.xml으로 옮기기

소스 : applicationContext.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<!-- 이 설정파일은 openseed의 스터디때 생성된 파일이 아니다. 
글을 설명하기 위해 임의로 생성된 파일이나 설정자체는 제대로 작동하는것을 확인했다. -->
<beans>
  <!-- iBATIS SQLMaps의 설정파일 위치를 지정한다. 
  
  class값은 
  SQLMaps 1.x버전을 사용할때는 org.springframework.orm.ibatis.SqlMapFactoryBean
  SQLMaps 2.x버전을 사용할때는 org.springframework.orm.ibatis.SqlMapClientFactoryBean 를 사용한다.
  -->
  <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
    <property name="configLocation"><value>WEB-INF/SqlMapConfig.xml</value></property>
  </bean>

  <!-- dataSource를 사용하는것에 대한 정보를 나타낸다. 
  여기서 사용될수 있는 dataSource타입은 다른 문서를 참조하길 바란다. 
  
  여기선 apache의 DBCP Connection pooling을 사용하는 것이다. -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName"><value>org.postgresql.Driver</value></property>
    <property name="url"><value>jdbc:postgresql://localhost:5432/openseed</value></property>
    <property name="username"><value>openseed</value></property>
    <property name="password"><value>openseed</value></property>
    <property name="defaultAutoCommit"><value>false</value></property>
  </bean>
  
  <!-- DB연결에 관련된 설정을 DataSource형태로 지정을 했기 때문에 트랜잭션 관리를 
  org.springframework.jdbc.datasource.DataSourceTransactionManager 가 담당하도록 지정한다. -->
  <bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource"><ref local="dataSource"/></property>
  </bean>  
  
  <!-- 각각의 메소드 별로 트랜잭션관리 속성을 지정한다. -->
  <bean id="guestService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">  
    <property name="transactionManager"><ref local="myTransactionManager"/></property>
    <property name="target"><ref local="guestTarget"/></property>
    <property name="transactionAttributes">
      <props>
        <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
        <prop key="save*">PROPAGATION_REQUIRED</prop>
        <prop key="update*">PROPAGATION_REQUIRED</prop>
        <prop key="delete*">PROPAGATION_REQUIRED</prop>
      </props>
    </property>
  </bean>  
  
  <!-- 소위 Spring을 사용하게 되는 비지니스 객체의 클래스를 지정하는 부분이다. 
  즉 여기선 gikim.dongguk.guestboard.spring.GuestSpringImpl 클래스가 Spring의 여러가지 기능을 담당하게 되는것이다.
  그리고 관리하게 되는 DAO는 guestDAO로 지정한다. 
  -->
  <bean id="guestTarget" class="gikim.dongguk.guestboard.spring.GuestSpringImpl">
    <property name="guestDAO"><ref local="guestDAO"/></property>
  </bean>  
  
  <!-- DAO에 관련된 셋팅이다. 실제로 iBATIS SQLMaps를 사용하게 되는 클래스를 지정하게 된다. 
  DB정보인 dataSource값과. iBATIS SQLMaps설정파일의 위치에 해당하는 sqlMapClient를 지정한다. 
  -->
  <bean id="guestDAO" class="gikim.dongguk.guestboard.dao.IbatisGuestDAOImpl">
    <property name="dataSource"><ref local="dataSource"/></property>
    <property name="sqlMapClient"><ref local="sqlMapClient"/></property>
  </bean>
</beans>

SqlMapConfig.xml의 내용을 applicationContext.xml 로 옮기면 사실 SqlMapConfig.xml에는 <settings>와 <sqlMap>요소만 정의를 해주면 된다. 기존의 <properties>와 <typeAlias>, <transactionManager> 는 applicationContext.xml의 내용으로 대체되는 것이다. 그 외의 SQLMaps맵핑 파일 같은 경우는 그대로 사용가능하다.

SqlMapClient 객체를 대체하는 SqlMapClientTemplate

spring은 SqlMapClient객체를 대체하는 SqlMapClientTemplate를 제공하는데 이를 생성하는 메소드는 getSqlMapClientTemplate()이다.

Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
SqlMapClient sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
user = (UsersqlMap.queryForObject("getUser", id);

사용자 정보를 가져오는 소스가 위와 같다고 할 때 spring의 SqlMapClientTemplate를 사용하면 아래와 같이 될 것이다..

User user = getSqlMapClientTemplate().queryForObject("getUser", id);

눈에 띄게 소스가 간편해진다. 이것밖에 장점이 없을까.? 아니다. 다음의 소스를 보자. spring이 자동적으로 트랜잭션을 처리해 주기 때문에 조금 전 입력예제로 사용되었던 소스가 아래처럼 다시 변경될 수 있다.

getSqlMapClientTemplate().insert("insertMessage", message);
return true;

spring의 트랜잭션 관리를 사용하면 위와 같이 소스가 간단해진다.

이것은 transactionAttributes 속성 하위의 설정값들에 의해 spring의 DI(dependency injection)을 사용하여 가능한 것이지만, 일단 개발자들에게는 SQLMaps로 인해 간단해진 소스가 더욱 심플해졌다는 사실 자체로 행복한 일일 것이다. 이건 개발 프레임워크를 제대로 사용할 때 개발 프레임워크가 개발자에게 주는 혜택이다.

결과적으로 SQLMaps는

지금까지의 설명에서 SQLMaps는 기존의 JDBC프로그래밍 방식과 근본적으로 프로그래밍 방식이 바뀌는 건 아님을 알 수 있다. 사실 이런 부분이 기존의 코딩 방식을 그대로 가져가기 때문에 획기적인 개선이 없다는 단점을 낳기도 한다. 개인적으로 SQLMaps는 JDBC프로그래밍 방식과 hibernate와 같은 완전히 다른 방식의 데이터베이스 처리 방식 사이의 중간에 위치한다고 생각한다. 둘의 단점을 모두 가지고 있지만 둘의 장점 또한 모두 가지고 있는 ORM이다. 그 둘의 단점을 피하고 장점만을 제대로 사용한다면 정말 좋은 ORM이라는 건 어느 누구도 의심하지 않을 것이다. 필자는 다음과 같은 경우에 hibernate보다는 SQLMaps가 적합할 것 같다고 생각을 한다.

  • JDBC코딩의 단점은 버리고 싶으나 hibernate와 같은 전혀 다른 방식을 사용하는 것은 부담스러운 프로젝트
  • 기존 JDBC형태의 프로젝트를 ORM 기술을 사용해서 변경하고자 할 때
  • 데이터베이스에 관련된 DBA가 회사 내에 있어서 기존의 SQL에 대한 지식과 경험이 많거나 HQL의 성능이 의심되는 경우

개발자는 SQLMaps의 설정방법과 맵핑파일에 관련된 몇몇 지식만 가지면 기존의 JDBC방식을 사용하던 개발자가 SQLMaps를 사용하는 건 크게 어렵지 않다. 하지만 JDBC 코드를 SQLMaps로 바꾸었을 때의 결과는 굉장한 긍정적인 차이를 가질 것이라는 점은 위에서 계속 설명했던 바이다

필자가 설명하는 SQLMaps의 장점이 제대로 전달되었는지는 잘 모르겠다. 사실 이외에도 SQLMaps에서 지원하는 기능은 많다. 물론 여기서 필히 논의가 되었어야 할 dynamic SQL부분이나 로깅관련 사항들은 지면이 모든 내용을 포함하도록 할 수 없는 제한으로 인해 설명되지 못했다. 이 사항들은 관련 문서와 커뮤니티에서 충분한 정보를 얻을 수 있을 것이다. 현재 가장 빠르게 발전하고 있는 ORM은 hibernate임에는 필자도 이견을 달지 않는다. 하지만 오픈소스가 생성된 배경과 의도가 다른 만큼 개발자들이 단순히 기술적인 부분에만 치우쳐 오픈소스를 사용하는 일은 없었으면 한다. 기술적인 부분이 어느 정도 지원하는 수준에서 프로젝트에서 요구하는 상황과 해당 오픈소스의 생성의도가 제대로 맞아떨어질 때 그 오픈소스가 정말 강력한 기능을 발휘할 것이라는 점은 모두가 동의할 것이다. 현재 hibernate는 jboss의 지원을 받고 있고 SQLMaps는 아파치의 지원을 받고 있다. 두 오픈소스가 충분히 발전할 가능성을 가지고 있고 이미 강력한 기능을 지원하는 만큼 적재적소에 해당 오픈소스를 사용하길 바란다.

참고자료

iBATIS 홈페이지 - http://incubator.apache.org/ibatis/site/index.html
SQLMaps 2.0개발자 가이드(한글) http://openframework.or.kr/JSPWiki/attach/Hibernate/iBATIS-SqlMaps-2_ko.pdf
SQLMaps 2.0튜토리얼(한글) http://openframework.or.kr/JSPWiki/attach/Hibernate/iBATIS-SqlMaps-2-Tutorial_ko.pdf
SQLMaps를 이용한 객체-관계맵핑 http://openframework.or.kr/JSPWiki/Wiki.jsp?page=ObjectRelationalMappingwithSQLMaps
네이버 자바프레임워크 http://cafe.naver.com/deve
네이버 자바프레임워크 위키 http://openframework.or.kr

Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:17
반응형



이 기사는 ZDNet Korea의 자매지인 마이크로소프트웨어에 게재된 내용이며 저작권에 관련한 모든 사항은 마이크로소프트웨어에 있습니다.
이 문구를 삭제하고 배포시 저작권에 위배될수 있습니다.

저자 정보 : 이동국(mailto:fromm0@gmail.com)

현재 울산에 있는 아이티스타에서 근무하고 있으며 주로 현대자동차 관련 프로젝트에 참여하고 있다. 네이버 자바프레임워크 카페 스탭으로 할동하고 있고 오픈프레임워크 사이트를 관리하고 있다. 평소 개발 프레임워크와 웹서비스에 관심을 가지고 있으며 요즘은 AndroMDA에 관련해서 공부를 시작했다.

ORM의 또 다른 핵 iBATIS SQLMaps

작년을 비롯해서 현재까지 해외 자바 관련 커뮤니티에서 가장 많이 논의가 되고 있는 부분 중 하나는 ORM이다. 그 이유를 생각해보면 자바개발의 많은 부분을 차지하고 있는 분야가 웹프로그래밍이고 그 웹프로그래밍의 거의 대부분을 차지하는 분야가 데이터베이스관련 처리이기 때문일 것이다. 현재 그 ORM을 대표하는 두 가지가 있다. 하나는 hibernate이고 또 하나는 이번에 소개할 iBATIS의 SQLMaps이다. 한때 객체와 관계형 데이터베이스의 관계 맵핑을 설정하면 SQL문을 자동 생성해 주는 hibernate에 비해 SQL문 자동 생성 기능이 없는 SQLMaps는 진정한 의미의 ORM이 아니다 라는 분위기도 있었지만 관계형 데이터베이스와 통신할 수 있는 유일한 매개체인 SQL문과 객체를 맵핑 시켜준다는 의미에서 현재는 SQLMaps또한 ORM이라고 보고 있다. hibernate의 경우 자바 관련 커뮤니티에서 많이 다루어 진 것으로 알고 있다. 우리는 여기서 SQLMaps에 대해서 알아보고자 한다. SQLMaps의 장점은 무엇일까.? iBATIS에서 밝히는 SQLMaps의 최대 장점은 간단함(simple)이다. 이것은 필자를 포함한 SQLMaps를 사용한 대부분의 개발자들이 인정하는 부분이다. 그 간단함이란 기존의 SQL문을 별다른 수정 없이 그대로 사용할 수 있는 상황에서 단순히 객체와 SQL문의 맵핑을 위한 몇몇 설정값만 설정해 주면 간단하게 ORM의 기능을 경험해 볼 수 있다는 것이다. 이러한 장점은 단순한 설명만으로는 잘 느껴지지 않으리라 본다. 잠시 후 예제를 통해서 정말 SQLMaps가 기존의 JDBC프로그램에 비해 얼마나 간단하게 구현이 되는지 보자.

SQLMaps는

앞에서 필자가 SQLMaps는 SQL과 객체를 맵핑시켜주는 도구라고 소개했지만 사실 엄밀하게 따지면 자바빈즈를 PreparedStatement 파라미터와 ResultSet으로 맵핑시켜주는 기능을 담당한다. 즉 PreparedStatement의 ? 에 각각의 파라미터 값을 setString()과 같은 메소드를 사용해서 셋팅하는 과정이나 ResultSet에서 getString()과 같은 메소드를 통해서 임의의 VO객체를 생성하는 과정을 자동으로 해준다. 결과적으로 간단한 xml셋팅만으로 일일이 손으로 작성하던 기존 JDBC형식을 버릴 수가 있다. SQLMaps의 구조를 도식화하면 다음과 같다.

1.jpg

iBATIS에서는 결과적으로 기존 데이터베이스 프로그램 코드의 20%정도만 사용해도 80%이상의 같은 기능을 수행할 수가 있게 된다고 밝히고 있고 필자 또한 그렇게 생각하고 있다.

이론적인 설명은 이쯤에서 잠시 접어두고 실제 사용하는 것을 보고 과연 어떻게 다른가를 알아보자. 아래의 소스는 얼마 전 오픈시드 프로젝트(http://openseed.net)의 ORM 연구회에서 진행한 1차 테스트 프로젝트의 소스이다. 일단 SQLMaps를 사용하기 위해서는 최소 두 가지의 설정파일이 필요하다. SQLMaps 설정파일과 SQLMaps 맵핑 파일이다. 추가적으로 프라퍼티값을 외부로 빼내기 위해서 사용되는 properties파일이 필요할 수 도 있다. 먼저 SQLMaps설정 파일인 SqlMapsConfig.xml을 보자.

SQLMaps 설정파일

소스 : SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" 
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
   <properties resource="SqlMapConfig.properties" />

   <settings cacheModelsEnabled="true" enhancementEnabled="true"
lazyLoadingEnabled="true" maxRequests="32" 
maxSessions="10" maxTransactions="5" 
useStatementNamespaces="false" />

   <typeAlias alias="comment" type="net.openseed.orm.openboard.domain.Comment"/>
   <typeAlias alias="message" type="net.openseed.orm.openboard.domain.Message"/>
   <typeAlias alias="user" type="net.openseed.orm.openboard.domain.User"/>

   <transactionManager type="JDBC">
      <dataSource type="DBCP">
         <property name="JDBC.Driver" value="${driver}" />
         <property name="JDBC.ConnectionURL" value="${url}" />
         <property name="JDBC.Username" value="${username}" />
         <property name="JDBC.Password" value="${password}" />
         <property name="JDBC.DefaultAutoCommit" value="false" />
      </dataSource>
   </transactionManager>

   <sqlMap resource="Comment.xml" />
   <sqlMap resource="Message.xml" />
   <sqlMap resource="User.xml" />
</sqlMapConfig>

xml파일 내용을 보면 크게 5가지로 분류되어 있다. 첫 번째인 <properties>는 프라퍼티값을 외부로 빼내서 사용하기 위해 프라퍼티파일을 사용할 때 해당 프라퍼티파일의 경로를 지정해준다. 프라퍼티 파일은 ‘키=값’ 의 형태로 값을 지정하고 SqlMapConfig.xml에서는 ${키} 의 형태로 사용할 수 있다.

두 번째인 <settings>은 SQLMaps에서 사용되는 다양한 옵션과 최적화를 위한 값들이다. 각각의 값들은 다음의 표를 참조하길 바란다.

cacheModelsEnabled SqlMapClient 를 위한 모든 캐시모델을 가능 유무.
Default: true (enabled)
enhancementEnabled 런타임시 바이트코드 향상을 가능유무.
Default: false (disabled)
lazyLoadingEnabled 모든 늦은(lazy)로딩을 가능유무.
Default: true (enabled)
maxRequests 동시에 SQL문을 수행할 수 있는 쓰레드의 수. 셋팅값보다 많은 쓰레드는 다른 쓰레드가 수행을 완료할 때까지 블록 된다.
Default: 512
maxSessions 주어진 시간동안 활성화될 수 있는 세션의 수.
Default: 128
maxTransactions 한꺼번에 SqlMapClient.startTransaction()에 들어갈 수 있는 쓰레드의 최대갯수. 셋팅값보다 많은 쓰레드는 다른 쓰레드가 나올 때까지 블록 된다.
Default: 32
useStatementNamespaces 이 셋팅을 가능하게 하면 당신은 sqlmap이름과 statement이름으로 구성된 전체적인 이름(fully qualified name)으로 맵핑된 statement를 참조해야 한다.
예를 들면: queryForObject("sqlMapName.statementName");
Default: false (disabled)

세 번째인 <typeAlias>는 패키지 명을 포함한 클래스가 너무 길 때 각각의 SQLMaps맵핑 파일에서 사용하기 번거로우므로 별칭을 두어서 간단하게 사용할 수 있다. 하지만 미리 정의된 별칭이 있다. 그 값들은 다음과 같다.

transactionManager에서 사용되는 별칭
JDBC com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig
JTA com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig
EXTERNAL com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig
dataSource에서 사용되는 별칭
SIMPLE com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory
DBCP com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory
JNDI com.ibatis.sqlmap.engine.datasource.JndiDataSourceFactory

네 번째인 <transactionManager>는 트랜잭션에 관련된 값을 셋팅하고 하위 데이터소스값을 지정하는 <dataSource>를 가진다. transactionManager의 type속성 값은 JDBC, JTA, EXTERNAL 중에 하나를 사용할 수 있는데 그 각각의 값은 다음과 같은 특징을 가진다.

  • JDBC - Connection commit()과 rollback()메소드를 통해 트랜잭션을 제어하기 위한 JDBC를 사용하게 된다.
  • JTA - 이 트랜잭션관리자는 SQL Maps가 다른 데이터베이스나 트랜잭션 자원을 포함하는 더욱 넓은 범위의 트랜잭션을 포함하도록 하는 JTA전역트랜잭션를 사용한다.
  • EXTERNAL - 이것은 개발자가 직접 트랜잭션을 관리할 때 사용하는 것으로 주로 분산 컴포넌트 기반 시스템 환경에서 컨테이너가 트랜잭션을 관리하는 경우에 사용된다.

dataSource의 type속성 값은 SIMPLE, DBCP, JNDI 중에 하나를 사용할 수 있다. 이 값은 각각 iBATIS SimpleDataSource 커넥션 풀링, Jakarta DBCP, JNDI를 통한 데이터소스을 사용하게 한다.

다섯 번째인 <sqlMap>은 SQLMaps맵핑 파일의 위치를 지정한다. resource속성 값에 전체패키지경로명을 포함하는 클래스 명을 써주면 된다.

기본이 되는 SQLMaps의 설정파일은 이렇게 특별히 어려운 설정 없이 간단하게 설정할 수 있다. 그럼 이제부터는 데이터베이스 작업별로 SQLMaps를 어떻게 사용하는지 보여주기 위한 요소인 자바소스와 SQLMaps맵핑 파일을 보도록 하자. SQLMaps맵핑 파일은 SQLMaps설정파일처럼 전체적인 구조를 볼 필요 없이 이후 설명되는 데이터베이스 작업의 종류에 따라 세부적인 부분만을 보도록 하겠다.

먼저 객체간의 관계는 다음과 같다.

2.jpg

SQLMaps를 사용하기 위한 객체 생성

SQLMaps를 사용하기 위해서 기본적으로 생성해줘야 하는 객체는 SqlMapClient 이다. 이 객체를 생성하기 위해서는 SQLMaps설정파일인 SqlMapsConfig.xml을 인자로 다음과 같이 코드를 작성하면 기본적으로 SQLMaps의 기능을 사용할 수 있는 SQLMapClient 객체가 생성된다. 이 소스는 getSqlMapConfig()라는 메소드이며 반환되는 객체는 이 문서에서 계속 사용된다.

Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
SqlMapClient sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);

조회

조회 작업의 경우 크게 한 개의 테이블을 조회하는 단순조회와 여러 개의 테이블이 서로 관계를 가지는 상태에서 조회하는 다중 테이블 조회의 두 가지의 경우로 나누어서 보겠다.

1. 테이블 한 개의 대한 단순조회

단순조회의 경우를 보기 위해 일단 사용자 정보를 보는 부분을 살펴보기로 하겠다. 사용자 정보에 해당되는 User.java소스는 위의 클래스다이어그램을 통해 파악을 할 수 있을 것이다.

다음은 조회를 위해 사용된 SQLMaps맵핑 파일의 일부이다.

소스 : User.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap>
  <select id="getUser" parameterClass="string" resultMap="user">
  <![CDATA[
    select 
      user_id as id, 
      password as password, 
      nick_name as nickName
    from userinfo
    where user_id=#id#
  ]]>
  </select>
</sqlMap>

dtd문서를 보면 알겠지만 SQL문을 포함할 수 있는 요소는 <statement>, <insert>, <update>, <delete>, <select>, <procedure> 정도가 된다. 각각의 의미는 짐작이 갈 것이다. statement는 모든 SQL문의 형태, insert, update, delete, select는 각각의 데이터베이스 작업, procedure는 프로시저작업을 할때 사용하면 된다. 일단 여기서는 조회작업을 수행하기 때문에 <select>를 사용했다. 속성으로는 id, parameterClass, resultClass가 사용되었는데 id 속성 값은 자바소스에서 해당 쿼리를 참조하기 위한 키의 역할을 담당한다. 키에 대해서는 이 xml 뒤에 나오는 자바소스를 통해 좀 더 자세히 설명할 것이다. parameterClass 속성값이 string이면 인자로 넘어오는 값이 문자열타입임을 나타낸다. 즉 user_id에는 인자로 넘어오는 문자열값이 자동으로 셋팅된다. resultClass속성값인 user는 조회 후 각각의 레코드가 자동으로 user객체 타입으로 생성이 된다는 것을 의미한다. 여기서 user라는 값은 앞에서 별칭된 net.openseed.orm.openboard.domain.User를 나타낸다. 내부적으로는 user객체가 생성이 되서 각각의 칼럼의 값이 setId(id칼럼값), setPassword(password칼럼값), setNickName(nickName칼럼값)를 차례로 호출하여 값을 할당하게 되는 셈이다.

이렇게 내부적으로 처리가 된다면 DAO계층에서 개발자가 어떻게 코딩을 하면 될까.? 그 형식은 다음과 같다.

    public User get(String id) {
        SqlMapClient sqlMap = null;
        User user = null;

        try {
            sqlMap = getSqlMapConfig();
            user = (UsersqlMap.queryForObject("getUser", id);
        catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return user;
    }

단순히 소스코드에서 SQL문과 자바소스만 분리한 것 같은데도 너무 간단하지 않은가.? 사실. 예제의 SQL문 자체가 너무 단순해서 간단함이 잘 느껴지지 않을 수 있으나 테이블 자체가 굉장히 많은 칼럼을 가지고 있었던 예전 경험을 생각해 보라. 이것은 사용자로 하여 SQLMaps를 사용하면 굉장히 지겨운 JDBC코드작성에서 벗어나고 그 과정에서 발생할 수 있는 많은 버그도 줄일 수가 있을 것이다.

2. 두개이상의 테이블에 대한 조회

그렇다면 다른 테이블과 1:1, 1:M, 또는 M:N 등의 다중 관계를 가지는 경우에는 어떻게 처리할까.? 그 예는 메시지관련 부분을 보면 된다. Message는 게시판에서 사용자에게 보여지게 되는 실제 글에 해당되는 객체로써 Message.java를 사용한다.

이러한 관계를 SQLMaps맵핑 파일에서는 어떻게 표현할까.? 그 표현방식은 다음에서 볼 수 있다.

소스 : Message.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="Message">
  <resultMap id="get-message" class="message">
    <result property="id" column="id"/>
    <result property="subject" column="subject"/>
    <result property="content" column="content"/>
    <result property="hitCount" column="hitCount"/>
    <result property="logTime" column="logTime"/>
  </resultMap>
  <resultMap id="get-message-result" class="message" extends="get-message">
    <result property="user.id" column="user_id"/>
    <result property="user.password" column="password"/>
    <result property="user.nickName" column="nickName"/>
    <result property="comments" column="{id=id}" select="getCommentList" />
  </resultMap>

  <select id="getMessageList" resultMap="get-message-result">
  <![CDATA[
    select a.seq as id,
      a.subject as subject,
      a.content as content,
      a.user_id as user_id,
      a.hit_count as hitCount,
      a.log_time as logTime,
      b.password as password,
      b.nick_name as nickName
    from message a, userinfo b
    where a.user_id=b.user_id
    order by a.seq desc
  ]]>
  </select>
</sqlMap>

여기서 눈여겨보아야 하는 것은 resultMap이다. 이는 복합적인 타입을 사용할 때 굉장히 유용하다. 물론 복합타입에만 사용할 필요는 없지만 필자는 기본적으로 복합타입이 아닐 경우 resultClass를 사용한다. 여기에서 첫 번째 resultMap은 기본적인 message정보를 가진다. 두 번째는 짐작할 수 있듯이 메시지 객체의 사용자정보와 여러 개의 덧글에 대한 정보를 가진다. 여기서 두번째 resultMap은 첫번째 resultMap의 정보를 가져오기 위해 extends 속성을 사용한다. 즉 extends는 두개의 resultMap를 연결하는 연결자 역할을 하게 된다. 이 resultMap에는 내포된 객체에 값을 셋팅하는 방법도 포함하고 있다. <result property="user.id" column="user_id"/> 여기서 property값인 user.id가 셋팅될 객체의 프라퍼티를 가리킨다. 다시 말해 Message객체내의 user객체의 id값을 user_id칼럼의 값으로 셋팅하는 것이다. 굳이 자바 소스로 표현해 본다면 다음과 같을 것이다. message.getUser().setId( rs.getString("user_id") ); 이런 처리방식으로 내포된 다양한 객체에 대한 접근도 가능하다. 그렇다면 메시지 객체내의 여러 개의 덧글을 의미하는 Collection형태의 객체는 어떻게 생성할까.? 그 방법은 <result property="comments" column="{id=id}" select="getCommentList" /> 에 모두 담겨있다. 여기서는 두 가지가 설명되어야 한다. 먼저 select값인데 이는 getCommentList라는 id값을 가지는 쿼리를 호출한다. getCommentList라는 id값을 가지는 쿼리는 다음과 같다.

소스 : Comment.xml

   <select id="getCommentList" parameterClass="comment" resultMap="get-comment-result">
  <![CDATA[
    select cu.seq as seq, 
      cu.content as content, 
      cu.log_time as logTime,
      cu.user_id as userId, 
      cu.message_seq as messageSeq, 
      cu.nick_name as nickName,
      cu.password as password, 
      m.seq as mseq,
      m.subject as subject, 
      m.content as mcontent,
      m.user_id as muserId, 
      m.hit_count as hitCount, 
      m.log_time as mlogTime
      from (select c.seq, c.content,
      c.log_time, c.user_id, c.message_seq,
      u.nick_name, u.password
    from comment c, userinfo u
    where c.user_id=u.user_idcu, message m
    where cu.message_seq=m.seq
    and cu.message_seq=#id#
    order by cu.seq asc
  ]]>
  </select>
]]>
</select>

이 쿼리는 실제 Comment.xml이라는 xml파일에 있는데 SQLMaps맵핑파일이 SQLMaps설정파일에 모두 선언이 되어 있다면 각각의 맵핑파일내 SQL문은 어디서든 호출이 가능하다. 이것은 굉장히 큰 장점이다. 해당 SQL문이 xml내에서 뿐 아니라 자바소스내에서도 어디서든 호출된다는 것은 해당 테이블의 구조가 변경될시 기존의 JDBC형태의 프로젝트처럼 프로그램마다 쿼리문을 수정했던 어려움 없이 해당 맵핑 파일의 SQL문 하나만을 수정하면 전체 프로그램에 반영이 된다는 것이다. 필자는 독자 여러분이 이런 부분을 정말 큰 장점이라고 인식해주길 바란다. select="getCommentList"을 사용함으로써 자동적으로 <result>에는 List형태의 값이 자동으로 셋팅된다. 그럼 많은 독자분들이 조회조건에 해당되는 값은 어떻게 넘겨야 하는지에 대한 의문이 들것이다. 이에 대한 답은 column속성에 모두 담겨있다. 지금 이 소스의 column값은 {id=id} 이다. 이것은 특정객체의 id값을 id칼럼의 값으로 셋팅한다는 것이다. 즉 {id=id}에서 전자의 id는 값이 셋팅되는 객체의 id라는 변수이고 후자의 id는 id칼럼의 값이다. 결과적으로 현재 getCommentList쿼리의 parameterClass인 comment객체의 id값을 id칼럼의 값으로 셋팅해서 넘기게 된다. 만약에 두개 이상의 조회조건, 예를 들면 comment객체의 id값과 content값을 인자로 넘겨야 한다면 ,(콤마)를 구분자로 주어서 {id=id,content=content} 형태로 column값을 설정하면 된다. 지금까지 설명한 것은 getCommentList의 parameterClass가 원시타입이 아닌 특정객체타입 일때의 경우이고 단순히 원시타입인 string이나 int타입이라면 column값을 id 라고 지정해 주면 된다. 즉 칼럼명만 지정해주면 된다. 이는 id칼럼값을 그냥 그대로 넘기는 것을 의미한다. 결과적으로 id가 getMessageList인 SQL문을 호출하면 메시지에 관련된 값이 자동으로 get-message-result라는 값의 resultMap에 셋팅되고 이에 자동적으로 다시 getCommentList를 호출해서 해당 값을 다시 Collection형태의 값으로 채워준다.

자바소스에서의 처리는 다음과 같다.

PaginatedList list = (PaginatedListsqlMap.queryForPaginatedList("getMessageList""", range);
for (int i = 0; i < page - 1; i++) {
  list.nextPage();
}

여기서 queryForPaginatedList 메소드는 SQLMaps에서 페이지처리를 위해 기본으로 제공하는 메소드이다. 이것을 사용하면 페이지처리를 자동으로 해준다. 조회는 이렇게 두 가지 경우만 살펴보면 거의 대부분의 상황에 대해 설명이 되는 듯 하다. 그럼 입력의 경우를 살펴보도록 하자.

입력

  <insert id="insertMessage" parameterClass="message">
    <selectKey resultClass="int" keyProperty="id">
    <![CDATA[
      select nextval('hibernate_sequence');
    ]]>
    </selectKey>
  <![CDATA[
    insert into message(seq, subject, content, user_id, hit_count, log_time)
    values(#id#, #subject#, #content#, #user.id#, 0'today')  
  ]]>
  </insert>

입력 또한 조회의 경우와 크게 다르지 않다. parameterClass에 해당되는 객체의 값이 각각의 #값# 에 셋팅된다. 조회의 경우와 다르게 추가되는 부분은 <selectKey>요소이다. 이 요소는 선택사항으로 key에 해당되는 값을 임의로 조회결과값으로 설정할 수가 있도록 지원하는 기능을 가진다. 즉 selectKey내의 쿼리문에 의해 반환되는 값이 #id#값에 대치가 된다는 것이다.

자바소스 내에서는 다음과 같이 처리할 수 있다.

        try {
            sqlMap = getSqlMapConfig();
            sqlMap.startTransaction();

            result = (IntegersqlMap.insert("insertMessage", message);

            sqlMap.commitTransaction();
            success = true;
        catch (NestedException ne) {
            logger.error(ne.getMessage(), ne);
        catch (Exception e) {
            logger.error(e.getMessage(), e);
        finally {
            sqlMap.endTransaction();
        }

이 소스의 insert()메소드는 결국 다음과 같은 작업을 수행하는 셈이다.

String sql = "insert into message(seq, subject, content, user_id, hit_count, log_time)
values(?, ?, ?, ?, 0, 'today')"
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, message.getId());
pstmt.setString(2, message.getSubject());
pstmt.setString(3, message.getContent());
pstmt.setString(4, message.getUser().getId());
result = pstmt.executeUpdate();

여기서 필자는 추가적으로 SQLMaps내에서의 트랜잭션 처리에 대해서 언급하도록 하겠다. SQLMaps의 트랜잭션은 sqlMap.startTransaction() 메소드를 호출함으로써 시작된다. 커밋을 수행하기 위해서는 sqlMap.commitTransaction() 메소드를 호출하면 된다. 하지만 SQLMaps API를 잠시 들여다 보면 SQLMaps에는 롤백에 관련된 메소드가 없다. 아니 없다기 보단 임의로 롤백을 수행하는 메소드가 없다. SQLMaps에서는 예외가 발생할 경우 sqlMap.endTransaction()메소드를 호출함으로써 롤백을 수행할 수 있다. 즉 sqlMap.endTransaction()은 정상적으로 모든 작업이 수행이 되면 트랜잭션을 커밋하고 connection을 닫는 작업을 자동으로 수행하게 되고 만약 예외가 발생했을 경우 트랜잭션을 롤백하고 connection을 닫는 작업을 자동으로 수행한다.

이런 트랜잭션처리를 하더라도 결과적으로 제대로 트랜잭션처리가 안되는 경우가 있다. MySQL예전 버전(필자가 테스트한 바로는 4.0.x버전)에서는 정상적인 처리가 되지 않고 각각의 작업이 수행 직후 바로 커밋되어버린다. 이는 iBATIS에서도 공식적으로 알려진 부분이고 다른 데이터베이스나 MySQL 4.1.x버전을 포함한 이후버전에서는 문제가 없다.

수정과 삭제의 작업은 입력의 작업과 사실 거의 유사하다. 그래서 수정과 삭제에 대한 추가적인 설명은 여기서 생략하도록 하겠다.

Spring에서 SQLMaps 사용하기.

Spring은 현재 비즈니스 레이어를 담당하는 오픈소스 프레임워크중에 거의 독보적인 위치를 차지하고 있다. Spring을 사용하면 IoC형태로 트랜잭션을 관리할 수도 있고 SQLMaps를 좀더 쉽게 사용할 수 있도록 도와준다.

Spring에서는 SQLMaps 1.x와 2.x를 지원하기 위한 클래스가 다르다. 물론 xml설정파일에서 셋팅하는 값이 다르다. 여기서는 2.x버전을 지원하는 내용만을 다룬다.

SqlMapConfig.xml을 applicationContext.xml으로 옮기기

소스 : applicationContext.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<!-- 이 설정파일은 openseed의 스터디때 생성된 파일이 아니다. 
글을 설명하기 위해 임의로 생성된 파일이나 설정자체는 제대로 작동하는것을 확인했다. -->
<beans>
  <!-- iBATIS SQLMaps의 설정파일 위치를 지정한다. 
  
  class값은 
  SQLMaps 1.x버전을 사용할때는 org.springframework.orm.ibatis.SqlMapFactoryBean
  SQLMaps 2.x버전을 사용할때는 org.springframework.orm.ibatis.SqlMapClientFactoryBean 를 사용한다.
  -->
  <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
    <property name="configLocation"><value>WEB-INF/SqlMapConfig.xml</value></property>
  </bean>

  <!-- dataSource를 사용하는것에 대한 정보를 나타낸다. 
  여기서 사용될수 있는 dataSource타입은 다른 문서를 참조하길 바란다. 
  
  여기선 apache의 DBCP Connection pooling을 사용하는 것이다. -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName"><value>org.postgresql.Driver</value></property>
    <property name="url"><value>jdbc:postgresql://localhost:5432/openseed</value></property>
    <property name="username"><value>openseed</value></property>
    <property name="password"><value>openseed</value></property>
    <property name="defaultAutoCommit"><value>false</value></property>
  </bean>
  
  <!-- DB연결에 관련된 설정을 DataSource형태로 지정을 했기 때문에 트랜잭션 관리를 
  org.springframework.jdbc.datasource.DataSourceTransactionManager 가 담당하도록 지정한다. -->
  <bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource"><ref local="dataSource"/></property>
  </bean>  
  
  <!-- 각각의 메소드 별로 트랜잭션관리 속성을 지정한다. -->
  <bean id="guestService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">  
    <property name="transactionManager"><ref local="myTransactionManager"/></property>
    <property name="target"><ref local="guestTarget"/></property>
    <property name="transactionAttributes">
      <props>
        <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
        <prop key="save*">PROPAGATION_REQUIRED</prop>
        <prop key="update*">PROPAGATION_REQUIRED</prop>
        <prop key="delete*">PROPAGATION_REQUIRED</prop>
      </props>
    </property>
  </bean>  
  
  <!-- 소위 Spring을 사용하게 되는 비지니스 객체의 클래스를 지정하는 부분이다. 
  즉 여기선 gikim.dongguk.guestboard.spring.GuestSpringImpl 클래스가 Spring의 여러가지 기능을 담당하게 되는것이다.
  그리고 관리하게 되는 DAO는 guestDAO로 지정한다. 
  -->
  <bean id="guestTarget" class="gikim.dongguk.guestboard.spring.GuestSpringImpl">
    <property name="guestDAO"><ref local="guestDAO"/></property>
  </bean>  
  
  <!-- DAO에 관련된 셋팅이다. 실제로 iBATIS SQLMaps를 사용하게 되는 클래스를 지정하게 된다. 
  DB정보인 dataSource값과. iBATIS SQLMaps설정파일의 위치에 해당하는 sqlMapClient를 지정한다. 
  -->
  <bean id="guestDAO" class="gikim.dongguk.guestboard.dao.IbatisGuestDAOImpl">
    <property name="dataSource"><ref local="dataSource"/></property>
    <property name="sqlMapClient"><ref local="sqlMapClient"/></property>
  </bean>
</beans>

SqlMapConfig.xml의 내용을 applicationContext.xml 로 옮기면 사실 SqlMapConfig.xml에는 <settings>와 <sqlMap>요소만 정의를 해주면 된다. 기존의 <properties>와 <typeAlias>, <transactionManager> 는 applicationContext.xml의 내용으로 대체되는 것이다. 그 외의 SQLMaps맵핑 파일 같은 경우는 그대로 사용가능하다.

SqlMapClient 객체를 대체하는 SqlMapClientTemplate

spring은 SqlMapClient객체를 대체하는 SqlMapClientTemplate를 제공하는데 이를 생성하는 메소드는 getSqlMapClientTemplate()이다.

Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
SqlMapClient sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
user = (UsersqlMap.queryForObject("getUser", id);

사용자 정보를 가져오는 소스가 위와 같다고 할 때 spring의 SqlMapClientTemplate를 사용하면 아래와 같이 될 것이다..

User user = getSqlMapClientTemplate().queryForObject("getUser", id);

눈에 띄게 소스가 간편해진다. 이것밖에 장점이 없을까.? 아니다. 다음의 소스를 보자. spring이 자동적으로 트랜잭션을 처리해 주기 때문에 조금 전 입력예제로 사용되었던 소스가 아래처럼 다시 변경될 수 있다.

getSqlMapClientTemplate().insert("insertMessage", message);
return true;

spring의 트랜잭션 관리를 사용하면 위와 같이 소스가 간단해진다.

이것은 transactionAttributes 속성 하위의 설정값들에 의해 spring의 DI(dependency injection)을 사용하여 가능한 것이지만, 일단 개발자들에게는 SQLMaps로 인해 간단해진 소스가 더욱 심플해졌다는 사실 자체로 행복한 일일 것이다. 이건 개발 프레임워크를 제대로 사용할 때 개발 프레임워크가 개발자에게 주는 혜택이다.

결과적으로 SQLMaps는

지금까지의 설명에서 SQLMaps는 기존의 JDBC프로그래밍 방식과 근본적으로 프로그래밍 방식이 바뀌는 건 아님을 알 수 있다. 사실 이런 부분이 기존의 코딩 방식을 그대로 가져가기 때문에 획기적인 개선이 없다는 단점을 낳기도 한다. 개인적으로 SQLMaps는 JDBC프로그래밍 방식과 hibernate와 같은 완전히 다른 방식의 데이터베이스 처리 방식 사이의 중간에 위치한다고 생각한다. 둘의 단점을 모두 가지고 있지만 둘의 장점 또한 모두 가지고 있는 ORM이다. 그 둘의 단점을 피하고 장점만을 제대로 사용한다면 정말 좋은 ORM이라는 건 어느 누구도 의심하지 않을 것이다. 필자는 다음과 같은 경우에 hibernate보다는 SQLMaps가 적합할 것 같다고 생각을 한다.

  • JDBC코딩의 단점은 버리고 싶으나 hibernate와 같은 전혀 다른 방식을 사용하는 것은 부담스러운 프로젝트
  • 기존 JDBC형태의 프로젝트를 ORM 기술을 사용해서 변경하고자 할 때
  • 데이터베이스에 관련된 DBA가 회사 내에 있어서 기존의 SQL에 대한 지식과 경험이 많거나 HQL의 성능이 의심되는 경우

개발자는 SQLMaps의 설정방법과 맵핑파일에 관련된 몇몇 지식만 가지면 기존의 JDBC방식을 사용하던 개발자가 SQLMaps를 사용하는 건 크게 어렵지 않다. 하지만 JDBC 코드를 SQLMaps로 바꾸었을 때의 결과는 굉장한 긍정적인 차이를 가질 것이라는 점은 위에서 계속 설명했던 바이다

필자가 설명하는 SQLMaps의 장점이 제대로 전달되었는지는 잘 모르겠다. 사실 이외에도 SQLMaps에서 지원하는 기능은 많다. 물론 여기서 필히 논의가 되었어야 할 dynamic SQL부분이나 로깅관련 사항들은 지면이 모든 내용을 포함하도록 할 수 없는 제한으로 인해 설명되지 못했다. 이 사항들은 관련 문서와 커뮤니티에서 충분한 정보를 얻을 수 있을 것이다. 현재 가장 빠르게 발전하고 있는 ORM은 hibernate임에는 필자도 이견을 달지 않는다. 하지만 오픈소스가 생성된 배경과 의도가 다른 만큼 개발자들이 단순히 기술적인 부분에만 치우쳐 오픈소스를 사용하는 일은 없었으면 한다. 기술적인 부분이 어느 정도 지원하는 수준에서 프로젝트에서 요구하는 상황과 해당 오픈소스의 생성의도가 제대로 맞아떨어질 때 그 오픈소스가 정말 강력한 기능을 발휘할 것이라는 점은 모두가 동의할 것이다. 현재 hibernate는 jboss의 지원을 받고 있고 SQLMaps는 아파치의 지원을 받고 있다. 두 오픈소스가 충분히 발전할 가능성을 가지고 있고 이미 강력한 기능을 지원하는 만큼 적재적소에 해당 오픈소스를 사용하길 바란다.

참고자료

iBATIS 홈페이지 - http://incubator.apache.org/ibatis/site/index.html
SQLMaps 2.0개발자 가이드(한글) http://openframework.or.kr/JSPWiki/attach/Hibernate/iBATIS-SqlMaps-2_ko.pdf
SQLMaps 2.0튜토리얼(한글) http://openframework.or.kr/JSPWiki/attach/Hibernate/iBATIS-SqlMaps-2-Tutorial_ko.pdf
SQLMaps를 이용한 객체-관계맵핑 http://openframework.or.kr/JSPWiki/Wiki.jsp?page=ObjectRelationalMappingwithSQLMaps
네이버 자바프레임워크 http://cafe.naver.com/deve
네이버 자바프레임워크 위키 http://openframework.or.kr

Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:13
반응형

---다이나믹쿼리------------------------------------------

 

 <statement id="UserLikeGenre" resultClass="common.bean.Genre">
  SELECT * FROM GENRE
  <dynamic prepend="WHERE">
   <isNotEmpty prepend="OR" property="genrename1">
    GENRENAME LIKE '%$genrename1$%'
   </isNotEmpty>
   <isNotEmpty prepend="OR" property="genrename2">
    GENRENAME LIKE '%$genrename2$%'
   </isNotEmpty>
   <isNotEmpty prepend="OR" property="genrename3">
    GENRENAME LIKE '%$genrename3$%'
   </isNotEmpty>
   <isNotEmpty prepend="OR" property="genrename4">
    GENRENAME LIKE '%$genrename4$%'
   </isNotEmpty>
   <isNotEmpty prepend="OR" property="genrename5">
    GENRENAME LIKE '%$genrename5$%'
   </isNotEmpty>
  </dynamic>
 </statement>

 

값이 비어있지 않다면 생성해준다~

 

statement 를사용하면 '$$' 로 해야 하며~ %%로 검색하기 위해서

statement를 사용

 

 

---간단한 select 문------------------------------------------

 

<select id="UserCouponeCheck" resultClass="common.bean.Coupone">
  SELECT * FROM COUPONE WHERE ID=#id# AND COUPONEENDDAY
  <![CDATA[>]]>           <-- 요놈은 xml 검증 무시~하기 위해
  SYSDATE
 </select>

 

 

 

---간단한 insert 문------------------------------------------

 

 <insert id="UserInsertGenre">
  INSERT INTO MEM_LIKEGENRE(MEMLIKEGENRENO, ID, GENRECODE)
  VALUES(MEM_LIKEGENRE_SEQ.NEXTVAL,#id#,#likegenreno#)
 </insert>

 

 

---간단?한 update 문---------------------------------------

 

 <statement id="UserUpdate">
  UPDATE MEMBER SET
  ID='$id$',NAME='$name$',PASSWORD='$password$',SSN='$ssn$',
  DETAILADDR='$detailaddr$',PHONE='$phone$',EMAIL='$email$',
  BIRTHDAY=TO_DATE('$birthday$','yy/mm/dd'),
  MILEAGE='$mileage$',SEQ='$seq$' WHERE ID LIKE '%$id$%' AND
  PASSWORD LIKE '%$password$%' AND ssn LIKE '%$ssn$%'
 </statement>

 

Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:12
반응형

- iBATIS가 적합하지 않은 경우 ( 정통 ORM 프레임웍이 더 적합한 경우)


 
1.
개발자가 데이터베이스에 대한 전권을 가지고 있는 경우.
    개발 완료 후 운영시점에는 데이터베이스에 대한 권한을 가지지 못할 가능성이 있는 경우는 제외.

 2. 개발팀내에 데이터베이스 쿼리에 대한 구루 개발자가 없는 경우

 3. 개발팀내의 개발자들이 새로운 툴 또는 프레임웍에 대한 도전의식이 넘치는 경우 혹은 새로운
    기술에 대한 오픈 마인드를 가지고 있는 경우

 4. 한번의 데이터 액세스 구문 작성으로 여러 데이터베이스를 지원해야 할 경우

 5. 어플리케이션의 전체 레이어가 모두 객체지향적 아키텍쳐를 지향하고 싶은 경우

 6. 데이터베이스의 정규화가 잘 적용되어 있는 경우
 

 

- iBATIS가 더 적합한 경우 ( 정통 ORM 프레임웍이 적합하지 않은 경우)


 
1.
데이터베이스에 대한 전권을 가지고 있지 못한 경우

 2. SQL 쿼리에 대한 완벽한 제어를 해야 하거나 하고 싶은 경우

 3. DBA와 사이가 좋지 않은 경우 혹은 DBA가 직접 쿼리작성을 원하는 경우

 4. 개발팀원이 정통 ORM 툴에 대해 사전 지식이 없거나 학습할 수 있는 기간이 불충분한 경우

 5. 프로시져처럼 SQL 쿼리 구문만을 별도의 공간에 보관하고 싶은 경우

 6. 반복적인 쿼리 작성을 피하고 싶거나, 쿼리 구문의 재활용이 필요한 경우
 7. 데이타베이스의 정규화가 미비한 경우 (물론 잘 정규화된 DB에 대해서는 말할 것도 없고.. ㅡ.ㅡ)
 8. 이미 존재하는 데이터베이스에 대해서 새로운 어플리케이션을 개발해야 하는 경우

Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:08
반응형

가장 간단히 설명하면, JAVA에서 DB관련 작업을 편하게 해주는 프레임웍정도라고 할까?

iBATIS in action에서 iBATIS는 "SQL 실행 결과를 자바빈즈 혹은 Map 객체에 매핑해주는 퍼시스턴스 솔루션으로 SQL을 소스 코드가 아닌 XML로 따로 분리해 관리하여 지겨운 SQL 문자열 더하기에서 해방시켜 줍니다. 또한 XML에서 동적 SQL 요소를 사용하여 쿼리 문장을 프로그래밍 코딩 없이 자유롭게 변환할 수 있게 해줍니다. 이러한 접근방식으로 인해 iBATIS를 사용하면 JDBC를 사용할 때보다 약 60% 정도의 코드만으로 프로그램 작성이 가능하다" 라고 한다.

말로만 하면 뭔소리인지 모르겠으니 간단한 예제 정도를 들어보자.

- 일반적인 JDBC 예제
import javax.naming.*;
import javax.sql.*;
import java.sql.*;

public class Employee {
  public Account getAccount(int id) throws SQLException, NamingException{
    Account account = null;
    
    String sql = "select * from employee where id= ?";
    
    Connection conn      = null;
    PreparedStatement ps = null;
    ResultSet rs       = null;
    
    try {      
      Context ctx = new InitialContext();
      DataSource ds =
              (DataSource)ctx.lookup(
                 "java:comp/env/jdbc/TestDB"); 
      conn = ds.getConnection();
      ps = conn.prepareStatement(sql);
      ps.setInt(1, id);
      rs = ps.executeQuery();
      
      while( rs.next()){
        account = new Account();
        account.setId(rs.getInt("ID"));        
      }
    } finally {
      try {
        if ( rs != null ) rs.close();
      } finally {
        try {
          if (ps != null) ps.close();
        } finally {
          if (conn != null) ps.close();
        }
      } 
    } 
    return account;  
  }
}   

뭐다들 아시겠지만 간단히 쿼리를 날려서 Acount 객체에 담아가지고 오는 소스이다. 대충봐도 무척이나 길다,
이걸 iBATIS를 이용해서 처리하는 예를 보자, 

- iBATIS 를 이용한 예
acount.xml
<select id="getAcount" resultClass="Acount" parameterClass="java.lang.Integer">

    select * from employee where id= #id#
</select>

java
Acount act = (Acount) sqlMap.queryForObject("getAcount",new Integer(5));
 
보면 알겠지만 상단에 쿼리를 닮고있는 xml과 아래 간단히 크 쿼리를 실행시키는 java 한줄정도?이다.
사실 iBATIS를 설정하는 config파일과 sqlMap객체를 불러오는 부분이 있긴하지만, 무척이나 좋아보이도록,
이것만 쓰겠다. -_-;;

iBATIS 의 목표와 특징은 몇마디로 짧게정의하다면,

쉽고, 간단하고, 의존성이 적은 프레임웍이라는 것이다. 
sql문과 java코드와의 분리만으로도 java개발자는 쿼리문을 신경쓰지 않아도 된다. sql문이 변경되더라도,
파라미터 값만 변경되지 않는다면, java소스에서는 수정할 부분이 없다.

~ 이론적인 면은 대충 접어두고 실전으로 넘어가자(사실 나도잘몰라서;;ㅈㅅ) 

다음 포스트는 실제로 이클립스에서 오라클 디비와 연동하겠습니다.

beans.tistory.com
Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 11. 13. 10:08
반응형

1. 우선 iBATIS 를 다운받습니다.

2. 다운을 풀고, /lib 폴더에 ibatis-2.3.4.726 를 톰캣의 lib폴더나 프로젝트의 web-inf/lib 폴더에 넣습니다.
- /현제프로젝트/WebContent/WEB-INF/lib/
- /톰캣/lib/   (톰캣6버전)

두폴더중 편한곳에다가 jar파일을 저장합니다.


3. example 폴더를 타고 들어가보면 sqlMapConfi.xml 파일이 있습니다. 이파일을 복사해서 붙여넣기 하셔도되고
src폴더에서 새로 xml 파일을 만드셔도 됩니다.

sqlMapClient.xml

<?xml version="1.0" encoding="UTF-8" ?>

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

<sqlMapConfig>

  <!-- Configure a built-in transaction manager.  If you're using an 
       app server, you probably want to use its transaction manager 
       and a managed datasource -->
  <transactionManager type="JDBC" commitRequired="false">
    <dataSource type="SIMPLE">
      <property name="JDBC.Driver" value="org.hsqldb.jdbcDriver"/>
      <property name="JDBC.ConnectionURL" value="jdbc:oracle:thin:@192.168.10.103:1521:db"/>
      <property name="JDBC.Username" value="ahm"/>
      <property name="JDBC.Password" value="ahm"/>
    </dataSource>
  </transactionManager>

  <!-- List the SQL Map XML files. They can be loaded from the 
       classpath, as they are here (com.domain.data...) -->
  <sqlMap resource="db/Account.xml"/>
  <!-- List more here...
  <sqlMap resource="com/mydomain/data/Order.xml"/>
  <sqlMap resource="com/mydomain/data/Documents.xml"/>
  -->

</sqlMapConfig>

빨간부분으로 표시된부분이 제가 수정한 부분입니다. db정보는 propertie로 빼서 관리하는방법도 있으나, 
우선은 이렇게 하도록 하겠습니다. 

4. 자바빈즈를 생성합니다. 

db라는 패키지를 만들고 그 밑에 Acount.java 파일을 생성합니다. 결과를 담아올 빈즈입니다. 
Account.java

package
 db;

public class Account {
  private int id;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }
}

5. XML을 생성합니다.

우리가 사용할 쿼리를 작성하는 XML입니다.
Account.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="Account">
  <!-- Select with no parameters using the result map for Account class. -->
  <select id="getAcount" resultClass="db.Acount">  다음값을 이용하여 쿼리와 맵핑합니다.
    select id from ACCOUNT where name=#value#
  </select>
</sqlMap>

6. 이제 쿼리를 실행할 JAVA를 작성한다.

SimpleExample.java

package db;

import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
import com.ibatis.common.resources.Resources;

import java.io.Reader;
import java.io.IOException;
import java.sql.SQLException;

public class SimpleExample {

  private static SqlMapClient sqlMapper;

  static {
    try {
      Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
      sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);
      reader.close(); 
    } catch (IOException e) {
      // Fail fast.
      throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
    }
  }

  public static Account getAcount() throws SQLException {
    return (Account)sqlMapper.queryForObject("getAcount","ahm");
    // 이부분에서 쿼리를 실행한다. queryForObject는 한개의 데이터를 가져올떄 사용하는 메소드이다.
  }
  
  public static void main(String[] args){
    
    try {
    Account temp = getAcount();    
    System.out.println(temp.getId());
  } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  }
  }
}

물론 이 java파일을 실행하기 전에는 DB에 TABLE과 데이터가 준비되어있어야겠다.

현제 이클립스내에 폴더구조이다.


이미지와 같이 , iBATIS 를 설정하는 설정파일인 sqlMapConfig.xml 파일과
               실제 SQL이 담겨져 있는 Account.xml 파일
               결과를 담아올 객체인   Account.java 파일
               이러한 과정을 호출하는 SimpleExample.java 파일
               그리고 WEB-INF/lib 에 ibatis-**.jar 파일 등을 해당폴더에 넣고 위에 순서대로 따라한다면
               DB에서 요청사항을 무난히 가져올수 있을것이다. 

다음엔 좀더 세부적인 설정사항이라던지 자세한 정보를 보여드리겠습니다. 
                  HTTP://BEANS.TISTORY.COM

Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 7. 25. 16:04
반응형

<JDBC 연결을 위한 DB서버 정보 세팅>

database.propertis

#ORACLE 10g  DB server관련 드라이버,URL,계정,비밀번호 세팅
db_driver=oracle.jdbc.driver.OracleDriver
db_url=jdbc:oracle:thin:@localhost:1521:XE
db_usernm=scott
db_pw=tiger

앞의 프러퍼티 이름은 임의로 정해주면 된다.


ORM을 위한 POJO 객체를 생성한다.

Member.java

//POJO
package com;

import java.io.Serializable;

public class Member implements Serializable {
 private String id;
 private String pw;
 private String nm;
 public String getId() {
  return id;
 }
 public void setId(String id) {
  this.id = id;
 }
 public String getNm() {
  return nm;
 }
 public void setNm(String nm) {
  this.nm = nm;
 }
 public String getPw() {
  return pw;
 }
 public void setPw(String pw) {
  this.pw = pw;
 }
}

테이블과 연관하기 위한 정보및 쿼리를 작성한다.

Member.xml

<?xml version="1.0" encoding="EUC-KR" standalone="no"?>
<!DOCTYPE sqlMap          
                                PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"          
                                "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="Member">
 <!--
  Member
 -->
 <typeAlias alias="member" type="com.Member" /><!-- pojo클래스 별칭주기 -->

 <resultMap id="getMemberAll" class="member"><!-- 클래스 프러퍼티와 디비 컬럼맵핑 -->
  <result property="id" column="id" />
  <result property="pw" column="pw" />
  <result property="nm" column="nm" />
 </resultMap>

 <select id="getMemberAll" resultMap="getMemberAll">
  SELECT id, pw, nm FROM MEMBER
 </select>
 
 <select id="getMember" resultMap="getMemberAll">
  SELECT id, pw, nm FROM MEMBER

  WHERE ID=#id#
 </select>

 <insert id="insertMember" parameterClass="member">
  INSERT INTO MEMBER (id, pw, nm) VALUES (#id#, #pw#, #nm#)
 </insert>

 <update id="updateMember" parameterClass="member">
  UPDATE MEMBER SET pw = #pw#, nm = #nm#

  WHERE ID = #id#
 </update>

 <delete id="deleteMember" parameterClass="member">
  DELETE FROM MEMBER WHERE ID = #id#
 </delete>

</sqlMap>

PreparedStatement에서 동적으로 값을 할당하기 위해 ?를 사용 하듯이

#id#처럼 동적으로 값이 할당 되는 부분을 정한다.


SqlMapClientManager.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig 
                                PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
                                "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
        <properties resource="com/database.properties" /><!-- 디비 정보 프러퍼티  세팅 -->
        <settings cacheModelsEnabled="false"
                  useStatementNamespaces="true"
        />

        <transactionManager type="JDBC"> <!--  프러퍼티 값을 세팅-->
                <dataSource type="SIMPLE">
                    <property name ="JDBC.Driver" value="${db_driver}"/>
                    <property name ="JDBC.ConnectionURL" value="${db_url}"/>
            <property name ="JDBC.Username" value="${db_usernm}"/>
            <property name ="JDBC.Password" value="${db_pw}"/>
                </dataSource>
        </transactionManager>

        <sqlMap resource="com/Member.xml" /> <!-- pojo, 테이블을 맵핑한 xml들 -->
</sqlMapConfig>
프러퍼티에 세팅한 정보를 가져와 JDBC관련 세팅을 하고,

pojo와 연관시킨 xml를 읽어 들인다.


SqlMapClientManager.java

package com;
import java.io.Reader;

import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
/**
*
* @author Administrator
*
*/
public class SqlMapClientManager {
       
        private static final SqlMapClient sqlMap;

        static {
                try {
                        String resource = "com/SqlMapClientManager.xml";
                        Reader reader = Resources.getResourceAsReader(resource);
                        sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
                       
                } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException(e);
                }
        }
       
        private SqlMapClientManager(){}
       
        public static SqlMapClient getSqlMapClient() {
                return sqlMap;  
        }

}

SqlMapClientManager.xml 파일을 읽어 들여 SqlMapClient 인스턴스를 생성한다.


MemberDAO.java

package com;

import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;

import com.ibatis.sqlmap.client.SqlMapClient;

public class MemberDAO {

 private static SqlMapClient sqlMap = SqlMapClientManager.getSqlMapClient();
 public List listAll(){//모든 회원 조회

  Member paramMember = new Member();
  Member returnMember = new Member();

  List list=null;
  try {
   list = sqlMap.queryForList("Member.getMemberAll", paramMember);
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

  return list;

 }

 public Member getMember(Member member){  //회원 정보 조회

  Member paramMember = member;
  Member returnMember = new Member();

  try {
   member = (Member) sqlMap.queryForObject("Member.getMember", paramMember);
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return member;
 }


 public void insert(Member member){ //회원 등록

  Member paramMember = member;
  System.out.println("insert Member");
  System.out.println(member.getId());
  System.out.println(member.getNm());
  System.out.println(member.getPw());
  try {
   sqlMap.insert("Member.insertMember",paramMember);
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

 }
 public void update(Member member){
  // 사용자 수정
  Member paramMember = member;

  try {
   sqlMap.update("Member.updateMember",paramMember);
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

 public void delete(Member member){
  // 사용자 삭제
  Member paramMember = member;
  try {
   sqlMap.update("Member.deleteMember",paramMember);
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

 }
}

sqlMapClient 인스턴스를 통해 MEMBER테이블에 접근하기 위한 API제공

Posted by 1010
54.iBATIS, MyBatis/iBatis2008. 7. 25. 16:03
반응형

Hibernate 첫 번째 예제 - 튜토리얼

                                  원문 : http://www.laliluna.de/first-hibernate-example-tutorial.html

                                  역자 : 김종대(jdkim528@korea.com)

이 튜토리얼은 Hibernate를 사용하는 간단한 예제를 보여준다. 우리는 Hibernate가 어떻게 동작하는지를 보여주는 간단한 자바 어플리케이션을 생성시킬 것이다.

(광고 생략...)

개괄

저자: Sebastian Hennebrueder

날짜: December, 19th 2005

사용된 소프트웨어와 프레임워크

Eclipse 3.x

MyEclipse 4.x가 권장되지만 선택사항임

Hibernate 3.x (3.1을 사용함)

소스 코드: http://www.laliluna.de/download/first-hibernate-example-tutorial.zip

소스들은 라이브러리들을 포함하지 않는다. hibernate.org에서 라이브러리들과 당신의 데이터베이스 드라이버를 내려 받고 그것들을 아래에 설명되어 있는 프로젝트에 추가한다. 예제는 당신의 데이터베이스 설정들과 동작하도록 구성되어야 한다! 튜토리얼을 읽기 바란다.

 

튜토리얼의 PDF 버전:  http://www.laliluna.de/download/first-hibernate-example-tutorial-en.pdf

Hibernate2용 이전 PDF 버전:  http://www.laliluna.de/download/first-hibernate-2-example-tutorial-en.pdf

짧은 개요

Hibernate는 객체 관계형 매핑을 위한 솔루션이고 영속 관리 솔루션 또는 영속 계층이다. 이것은 아마 Hibernate를 배우는 아무나 이해 가능한 것은 아니다.

당신이 생각할 수 있는 것은 아마 당신이 당신의 어플리케이션에 몇몇 기능들(비지니스 로직)을 갖도록 하고 데이터베이스 내에 데이터를 저장하고 싶어하는 것이다. 당신이 자바를 사용할 때 모든 비지니스 로직은 통상적으로 다른 클래스 타입들인 객체들과 동작한다. 당신의 데이터베이스 테이블들은 전혀 객체들이 아니다.

Hibernate는 데이터베이스 테이블들을 어떤 클래스로 매핑시키는 솔루션을 제공한다. 그것은 데이터베이스 데이터를 어떤 클래스로 복사한다. 반대 방향으로 그것은 객체들을 데이터베이스에 저장하는 것을 지원한다. 이 과정에서 객체는 하나 이상의 테이블들로 전환된다.

저장소에 데이터를 저장시키는 것은 영속이라 명명된다. 그리고 테이블들을 객체들에 복사하는 것 등등은 객체 관계형 매핑이라 명명된다.

Java 프로젝트를 생성한다

Eclipse를 사용하여 새로운 프로젝트를 생성시키기 위해 Ctrl+n (Strg+n) 키를 누른다. Java 프로젝트를 선택한다. 우리는 그것을 FirstHibernateExample라 명명할 것이다.
 

MyEclipse를 사용하여 Hibernate용 프로젝트를 준비한다

MyEclipse를 사용하고 있다면, 패키지 탐색기 내에서 당신의 프로젝트 상을 마우스 오른쪽 버튼 클릭하고 Add Hibernate capabilities.를 선택한다.




 

마법사를 계속하고 src 디렉토리 내에 새로운 hibernate.cfg.xml 을 생성시킨다.

마지막 단계에서 당신은 Hibernate SessionFactory를 생성시킬 수 있다. 나는 내 자신의 것을 생성 시키는 것을 선호한다. 당신은 아래에서 그것을 찾을 수 있다.

Hibernate용 프로젝트를 준비한다

당신이 MyEclipse를 사용하고 있지 않을 때 http://www.hibernate.org/ 웹 사이트로부터 Hibernate를 내려 받아라.

파일을 추출한다. Hibernate는 많은 라이브러리들의 목록으로 구성되어 있다. 당신은 그것들 모두를 필요로 하지 않는다. lib 디렉토리 내에 필수적인 것을 설명하고 있는 README 파일이 존재한다. 당신의 프로젝트 properties를 열고, “Java Build Path”를 선택하고, “Add External Jars”을 클릭하고 아래에 보이는 라이브러리들을 당신의 프로젝트 경로에 추가한다.

 



 

SessionFactory를 생성시킨다

세션 팩토리는 Hibernate에서 중요하다. 그것은 단위 쓰레드 당 오직 한 개의 세션 인스턴스만이 사용됨을 보증하는 설계 패턴을 구현하고 있다. 당신은 단지 이 팩토리로부터 당신의 Hibernate 세션을 얻을 것이다.

de.laliluna.hibernate 패키지 내에 HibernateSessionFactory로 명명된 클래스를 생성시키고 아래의 소스 코드를 추가한다.

/**
 * 
 * @author Sebastian Hennebrueder
 * created Feb 22, 2006
 * copyright 2006 by http://www.laliluna.de
 */
package de.laliluna.hibernate;

import javax.naming.InitialContext;

import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;

/**
 * @author hennebrueder 이 클래스는 오직 한 개의 SessionFactory 만이 초기화 되고
 *         컨피그레이션이 싱글톤으로 쓰레드 안전하게 행해진다는 점을 보증한다.
 *         실제로 그것은 단지 Hibernate SessionFactory를 포장한다.
 *         JNDI 이름이 구성될 때 세션은 JNDI에 바인드 되고, 
 *         그 밖의 경우 그것은 오직 로컬 상으로 저장된다.
 *         당신은 임의의 종류의 JTA 또는 Thread transactionFactory들을 사용하는 것이 자유롭다. 
 */
public class InitSessionFactory {

	/**
	 * Default constructor.
	 */
	private InitSessionFactory() {
	}

	/**
	 * hibernate.cfg.xml 파일의 위치. 주의: 위치는 Hibernate가 사용하는 classpath 상에 있어야 한다
	 * #resourceAsStream 스타일은 그것의 구성 컨피그레이션 파일을 검색한다.
	 * 그것은 Java 패키지 내에 있는 config 파일에 위치된다 - 
	 * 디폴트 위치는 디폴트 Java 패키지이다.<br>
	 * <br>
	 * 예제: <br>
	 * <code>CONFIG_FILE_LOCATION = "/hibernate.conf.xml". 
	 * CONFIG_FILE_LOCATION = "/com/foo/bar/myhiberstuff.conf.xml".</code>
	 */
	private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";

	/** hibernate 컨피그레이션의 싱글톤 인스턴스 */
	private static final Configuration cfg = new Configuration();

	/** hibernate SessionFactory의 싱글톤 인스턴스 */
	private static org.hibernate.SessionFactory sessionFactory;

	/**
	 * 아직 초기화 되지 않았다면 컨피그레이션을 초기화 시키고 현재 인스턴스를 반환한다
	 * 현재 인스턴스를 반환한다
	 * 
	 * @return
	 */
	public static SessionFactory getInstance() {
		if (sessionFactory == null)
			initSessionFactory();
		return sessionFactory;
	}

	/**
	 * ThreadLocal Session 인스턴스를 반환한다. 필요하다면 Lazy는 
	 * <code>SessionFactory</code>를 초기화 시킨다.
	 * 
	 * @return Session
	 * @throws HibernateException
	 */
	public Session openSession() {
		return sessionFactory.getCurrentSession();
	}

	/**
	 * 이 메소드의 행위는 당신이 구성했던 세션 컨텍스트에 의존한다.
	 * 이 팩토리는  다음 프로퍼티 is intended to be used with a hibernate.cfg.xml
	 * <property name="current_session_context_class">thread</property>를 
	 * 포함하는 hibernate.cfg.xml과 함께 사용되게 기안되어 있다. 이것은 
	 * 현재의 열려진 세션을 반환할 것이거나 존재하지 않을 경우 새로운 세션을 생성시킬 것이다
	 * 
	 * @return
	 */
	public Session getCurrentSession() {
		return sessionFactory.getCurrentSession();
	}

	/**
	 * 심지어 하나 이상의 쓰레드가 sessionFactory를 빌드하려고 시도하는 경우조차도 
	 * 안전한 방법으로 sessionFactory를 초기화 시킨다
	 */
	private static synchronized void initSessionFactory() {
		/*
		 * [laliluna] 다시 null을 체크한다 왜냐하면 sessionFactory가 마지막 체크와 현재의 체크 사이에
		 * 초기화 되었을 수도 있기 때문이다
		 * 
		 */
		Logger log = Logger.getLogger(InitSessionFactory.class);
		if (sessionFactory == null) {
 

			try {
				cfg.configure(CONFIG_FILE_LOCATION);
				String sessionFactoryJndiName = cfg
				.getProperty(Environment.SESSION_FACTORY_NAME);
				if (sessionFactoryJndiName != null) {
					cfg.buildSessionFactory();
					log.debug("get a jndi session factory");
					sessionFactory = (SessionFactory) (new InitialContext())
							.lookup(sessionFactoryJndiName);
				} else{
					log.debug("classic factory");
					sessionFactory = cfg.buildSessionFactory();
				}

			} catch (Exception e) {
				System.err
						.println("%%%% Error Creating HibernateSessionFactory %%%%");
				e.printStackTrace();
				throw new HibernateException(
						"Could not initialize the Hibernate configuration");
			}
		}
	}
 
	public static void close(){
		if (sessionFactory != null)
			sessionFactory.close();
		sessionFactory = null;
 
	}
}


 

Log4J 구성하기

당신이 위에서 보았듯이 우리는 log4j 라이브러리를 추가시켰다. 이 라이브러리는 소스 디렉토리 내에 컨피그레이션 파일처럼 행하거나 다음 오류로서 당신을 맞이한다.

log4j:WARN No appenders could be found for logger (TestClient).
log4j:WARN Please initialize the log4j system properly.

루트 디렉토리에 log4j.properties로 명명된 파일을 생성시키고 다음을 삽입시킨다:

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=debug, stdout

log4j.logger.org.hibernate=info
#log4j.logger.org.hibernate=debug

### log HQL query parser activity
#log4j.logger.org.hibernate.hql.ast.AST=debug

### log just the SQL
log4j.logger.org.hibernate.SQL=debug

### log JDBC bind parameters ###
log4j.logger.org.hibernate.type=info

### log schema export/update ###
log4j.logger.org.hibernate.tool.hbm2ddl=info

### log HQL parse trees
#log4j.logger.org.hibernate.hql=debug

### log cache activity ###
log4j.logger.org.hibernate.cache=info

### log transaction activity
#log4j.logger.org.hibernate.transaction=debug

### log JDBC resource acquisition
#log4j.logger.org.hibernate.jdbc=debug

### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace

데이터베이스 드라이버를 추가한다

Hibernate는 데이터베이스에 접근할 데이터베이스 드라이버를 필요로 한다. 프로젝트 properties를 열고, “Java Build Path”를 클릭하고, “Add External Jars”를 선택하고 당신의 데이터베이스 드라이버를 추가한다. 당신이 PostgreSQL를 사용할 때 당신은  http://jdbc.postgresql.org에서 당신의 데이터베이스 드라이버를 찾을 수 있고 MySQL을 사용하고 있다면 이곳 http://www.mysql.de/products/connector/j에서 찾을 수 있다.

데이터베이스와 테이블드을 생성시킨다.

Create a database with MySql 또는 PostgreSQL 또는 당신이 좋아하는 DBMS에 데이터베이스를 생성시킨다. 그것을 “firsthibernate”로 명명한다.

PostgreSql을 사용한다면 테이블을 생성시키기 위해 다음 스크립트를 사용하라:

CREATE TABLE "public"."honey" (
  id SERIAL, 
  name text, 
  taste text, 
  PRIMARY KEY(id)
);


 

MySql을 사용하고 있다면 다음 스크립트를 사용하라:

CREATE TABLE `honey` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(250) default NULL,
  `taste` varchar(250) default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

클래스를 생성시킨다

Create a new class named “Honey” in the package “de.laliluna.example”. Add three fields id, name and taste and generate (Context menu -> Source -> Generate Getter and Setter) or type the getters and setters for the fields. Then create an empty constructor.

package de.laliluna.example;

/**
 * @author laliluna
 *
 */
public class Honey {
	private Integer id;
	private String name;
	private String taste;
 
	public Honey(){
 
	}
 
	/**
	 * @return Returns the id.
	 */
	public Integer getId() {
		return id;
	}
	/**
	 * @param id The id to set.
	 */
	public void setId(Integer id) {
		this.id = id;
	}
	/**
	 * @return Returns the name.
	 */
	public String getName() {
		return name;
	}
	/**
	 * @param name The name to set.
	 */
	public void setName(String name) {
		this.name = name;
	}
	/**
	 * @return Returns the taste.
	 */
	public String getTaste() {
		return taste;
	}
	/**
	 * @param taste The taste to set.
	 */
	public void setTaste(String taste) {
		this.taste = taste;
	}
}



 

매핑 파일들을 생성시킨다

이미 생성되어 있지 않다면 루트 디렉토리에 “hibernate.cfg.xml”로 명명된 새로운 파일을 생성시킨다.

hibernate 파일 내에 다음을 추가한다. 당신의 데이터베이스 구성에 적합하게 username과 password를 변경하는 것을 잊지 말라.

PostgreSQL 버전:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
	<property name="connection.url">jdbc:postgresql://localhost/firsthibernate</property>
	<property name="connection.username">postgres</property>
	<property name="connection.driver_class">org.postgresql.Driver</property>
	<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
	<property name="connection.password">p</property>
 <property name="transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
    <!--  thread is the short name for
      org.hibernate.context.ThreadLocalSessionContext
      and let Hibernate bind the session automatically to the thread
    -->
    <property name="current_session_context_class">thread</property>
    <!-- this will show us all sql statements -->
    <property name="hibernate.show_sql">true</property>
	<!-- mapping files -->
	<mapping resource="de/laliluna/example/Honey.hbm.xml" />
</session-factory>
</hibernate-configuration>


 

MySQL 버전:


 

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
	<property name="connection.url">jdbc:mysql://localhost/firsthibernate</property>
	<property name="connection.username">root</property>
	<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
	<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
	<property name="connection.password">r</property>
 <property name="transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
    <!--  thread is the short name for
      org.hibernate.context.ThreadLocalSessionContext
      and let Hibernate bind the session automatically to the thread
    -->
    <property name="current_session_context_class">thread</property>
    <!-- this will show us all sql statements -->
    <property name="hibernate.show_sql">true</property>
 
	<!-- mapping files -->
	<mapping resource="de/laliluna/example/Honey.hbm.xml" />

</session-factory>
</hibernate-configuration>


 

이 파일은 데이터베이스에 대한 구성 우리의 경우에는 PostgreSQL 데이터베이스의 구성 그리고 모든 매핑 파일들을 포함한다. 우리의 경우 그것은 단지 파일 Honey.hbm.xml이다. 

<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>

태그는 dialect를 구성한다. 당신의 데이터베이스에 맞게 이것을 변경하라. 당신의 데이터베이스를 위한 dialect를 찾기 위해 Hibernate 레퍼런스의 “SQL Dialects” 장에서 찾아보라.

de.laliluna.example 패키지 내에 Honey.hbm.xml을 생성시키고 그것을 다음으로 변경하라:

PostgreSQL 버전:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
 <class name="de.laliluna.example.Honey" table="honey">
 <id name="id" column="id" type="java.lang.Integer">
		<generator class="sequence">
				<param name="sequence">honey_id_seq</param>
			</generator>

 </id>
 
 <property name="name" column="name" type="java.lang.String" />
 <property name="taste" column="taste" type="java.lang.String" />
 </class>
</hibernate-mapping>


 

MySQL 버전:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
 <class name="de.laliluna.example.Honey" table="honey">
 <id name="id" column="id" type="java.lang.Integer">
 <generator class="increment"/>
 </id>
 <property name="name" column="name" type="java.lang.String" />
 <property name="taste" column="taste" type="java.lang.String" />
 </class>
</hibernate-mapping>


 

이 파일에서 우리의 클래스 Honey로부터 데이터베이스 테이블 honey로의 매핑이 구성되어 있다.

테스트 클라이언트를 생성한다

“de.laliluna.example” 패키지 내에 Java 클래스 “TestClient”를 생성시킨다.

다음 소스 코드를 추가한다. 그것은 데이터베이스 내에 엔트리들을 생성시키는 메소드, 그것들을 업데이트하고 리스트하는 메소드들을 포함하고 있다.

/**
 * Test application for example 
 * @author Sebastian Hennebrueder
 * created Jan 16, 2006
 * copyright 2006 by http://www.laliluna.de
 */

package de.laliluna.example;

import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;

import de.laliluna.hibernate.InitSessionFactory;

public class TestExample {

	private static Logger log =Logger.getLogger(TestExample.class);
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Honey forestHoney = new Honey();
		forestHoney.setName("forest honey");
		forestHoney.setTaste("very sweet");
		Honey countryHoney = new Honey();
		countryHoney.setName("country honey");
		countryHoney.setTaste("tasty");
		createHoney(forestHoney);
		createHoney(countryHoney);
		// our instances have a primary key now:
		log.debug(forestHoney);
		log.debug(countryHoney);
		listHoney();
		deleteHoney(forestHoney);
		listHoney();

	}

	private static void listHoney() {
		Transaction tx = null;
		Session session = InitSessionFactory.getInstance().getCurrentSession();
		try {
			tx = session.beginTransaction();
			List honeys = session.createQuery("select h from Honey as h")
					.list();
			for (Iterator iter = honeys.iterator(); iter.hasNext();) {
				Honey element = (Honey) iter.next();
				log.debug(element);
			}
			tx.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			if (tx != null && tx.isActive())
				tx.rollback();

		}
	}

	private static void deleteHoney(Honey honey) {
		Transaction tx = null;
		Session session = InitSessionFactory.getInstance().getCurrentSession();
		try {
			tx = session.beginTransaction();
			session.delete(honey);
			tx.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			if (tx != null && tx.isActive())
				tx.rollback();
		}
	}

	private static void createHoney(Honey honey) {
		Transaction tx = null;
		Session session = InitSessionFactory.getInstance().getCurrentSession();
		try {
			tx = session.beginTransaction();
			session.save(honey);
			tx.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			if (tx != null && tx.isActive())
				tx.rollback();
		}
	}
}


 

축하합니다. 당신은 Hibernate 세계에서 당신의 첫 번째 단계들을 마쳤습니다. 

우리는 당신이 Hibernate 세계에 빠르게 진입하기를 원했다. Hibernate를 사용하는 많은 복잡한 토픽들과 더 나은 구현이 존재한다. 예를 들어, 각각의 메소드 내에서 세션을 열고 닫는 것은 좋은 연습이 아니다. 하나의 세션은 재사용될 수 있으며 그 동안에 많은 시간을 절약한다.

당신이 최상의 실전에 대해 더 많은 것을 배우고자 원할 경우 우리의 세미나 또는 다른 튜토리얼을 살펴보길 바란다.

Copyright and disclaimer

This tutorial is copyright of Sebastian Hennebrueder, laliluna.de. You may download a tutorial for your own personal use but not redistribute it. You must not remove or modify this copyright notice.(이 튜토리얼은 Sebastian Hennebrueder, laliluna.de에 저작권이 있다. 당신은 당신 자신의 개인 용도로 튜토리얼을 내려받을 수 있지만 그것을 재배포할 수 없다. 당신은 이 저작권 경고를 제거하지 말아야 하거나 변경시키지 말아야 한다.이하 생략...)

The tutorial is provided as is. I do not give any warranty or guaranty any fitness for a particular purpose. In no event shall I be liable to any party for direct, indirect, special, incidental, or consequential damages, including lost profits, arising out of the use of this tutorial, even if I has been advised of the possibility of such damage.

Posted by 1010