'개발자 센터/iBatis'에 해당되는 글 6건

  1. 2009.12.13 간결한 데이터 매퍼의 대명사, iBATIS SQLMaps 활용법
  2. 2009.12.13 iBatis 에서 페이징하는 방법
  3. 2009.12.13 iBatis 참고 사이트
  4. 2009.12.13 iBatis SqlMapClient API 1
  5. 2009.12.13 iBatis Framework의 구성
  6. 2009.12.13 iBatis Framework

iBATIS SQLMaps 활용법

목차

  1. ORM? 데이터 매퍼?
  2. 기능에 따른 사용형태
    1. 일반적인 CRUD작업
    2. 프로시저
    3. N+1 문제
    4. 동적 SQL
  3. 아이바티스 3.0 소식
  4. 정리

 

ORM? 데이터 매퍼?

iBATIS SQLMaps(이하 아이바티스)는 이미 국내외 많은 개발자들이 사용하고 있는 퍼시스턴스 계층의 프레임워크이다. 실제로 주요한 두 포털 사이트인 Naver와 Daum이 아이바티스를 기본적으로 사용하고 있고 다른 업체들도 아이바티스를 그대로 사용하거나 약간 변형시킨 형태로 사용하고 있는 것으로 알고 있다. 과거 ORM의 대표인 하이버네이트와 데이터 매퍼인 아이바티스간에 어느 프레임워크가 더 좋으냐? 하는 논쟁이 있었던 적도 있었을 만큼 많은 자바 개발자들이 데이터베이스에 관련된 ORM프레임워크에 대한 관심이 크다. 이런 논쟁 가운데 아이바티스는 ORM이 아니기 때문에 논쟁의 대상이 될 수 없다는 의견이 있었다. 사용 목적이라는 관점에서 본다면 하이버네이트와 아이바티스는 논쟁의 대상이 될 수 있으나 ORM이냐 아니냐 라는 관점에서 본다는 역시 아이바티스는 ORM이 아니라고 할 수 있다. 이 부분은 아이바티스의 핵심 개발자인 래리 메도스는 다음처럼 ORM과 데이터 매퍼를 구분한다.

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

여기서 말하는 개념은 두 프레임워크의 성격을 제대로 이해할 수 있게 해준다. 이러한 개념적인 차이점은 결과적으로 ORM의 캐싱전략은 데이터베이스 객체에 매핑된 자바 객체가 캐시에 저장되는 게 기본이고 데이터매퍼는 객체가 아닌 SQL구문 자체가 캐시에 저장이 되는 것과 같은 차이점을 낳게 된다. 엄밀히 따지면 이렇겠지만 역시 사용목적으로 본다면 대체적으로 아이바티스도 ORM의 범주에 넣는 게 크게 무리가 없다고 보는 게 사실이다. ORM이 생겨난 원인을 무엇일까.? 가장 기본적인 것은 현재의 데이터베이스 프로그래밍에서 일반 JDBC형태의 개발 방법이 개발자에게 필요 이상의 많은 타이핑과 자원관리를 요구한다는 것이다. ORM을 사용해본 개발자는 이 부분에 대해 ORM이 어느 정도 효과를 보인다고 생각할 것이다. 물론 새로운 프레임워크에 대한 학습과 해당 프레임워크가 요구하는 설정작업, 그리고 개발방식은 분명히 개발자에게 또 다른 부담으로 작용하는 것 또한 사실이다. 하지만 개인적으로 개발자는 항상 새로운 어떤 것을 배우고 습득해야 한다고 생각한다. 그런 면에서 이런 프레임워크로 인한 부담을 느끼지 말고 자기 계발의 수단으로 생각했으면 좋겠다. 필자가 처음으로 아이바티스를 접했던 3년 전과는 달리 현재는 국내에 서적도 나와있고 많은 관련문서가 있어서 개발자들이 아이바티스를 학습하는데 전혀 무리가 없다고 봐진다. 그래서 이번에는 특정 부분에 집중하지 않고 아이바티스를 활용할 수 있는 부분들을 전체적으로 살펴보고 아이바티스 홈페이지를 통해 논의가 되고 있는 아이바티스 3.0이 나아가고자 하는 방향을 여기서 간단히 살펴보고자 한다.

 

기능에 따른 사용형태

필자가 테스트 한 환경은 다음과 같다.
eclipse-europa, iBATIS 2.3.0, MySQL 5.0.45, mysql-connector-java-5.0.3-bin.jar

필자는 기본적으로 아이바티스의 환경정보를 설정하는 파일을 SQLMaps파일,
SQL구문을 저장하는 파일을 SQLMap 파일이라고 명명했다.

아이바티스를 사용하는 개발에서는 크게 설정정보를 가지는 SQLMaps파일, SQL구문을 가지는 SQLMap파일 그리고 아이바티스 API를 사용하는 자바 코드로 구성이 된다고 볼 수 있다. 먼저 설정정보를 가지는 SQLMaps 파일을 보자. SQLMaps 파일 설정은 이미 많이 알려져 있기 때문에 첨부된 예제소스를 참조하도록 한다[아래 설정파일 제거] 샘플용 소스이기 때문에 typeAlias와 SQLMap 파일의 개수가 적다. 하지만 실제 프로젝트에 아이바티스를 적용할 때는 사전에 typeAlias에 대한 명명규칙을 정해서 중복이 되지 않도록 하거나 각각의 SQLMap파일에 정의해서 StatementNamespaces 를 사용해서 각각을 구분하도록 작업을 해주어야 설정파일의 증가로 복잡해질 때 혼동의 여지가 줄어들 것이다. 즉 설정정보의 증가로 인해 발생할 수 있는 혼동을 사전 정의를 통해 충분히 보완이 되어 있어야 할 것이다.

<sqlMapConfig>
  <properties resource="jdbc.properties" />

  <settings 
    cacheModelsEnabled="true" 
    enhancementEnabled="true"
    lazyLoadingEnabled="true" 
    maxRequests="32" 
    maxSessions="10"
    maxTransactions="5" 
    useStatementNamespaces="false" />
  
  <typeAlias alias="Account" type="kr.or.openframework.domain.Account"/>
  <typeAlias alias="Family" type="kr.or.openframework.domain.AccountFamily"/>

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

  <sqlMap resource="kr/or/openframework/dao/ibatis/MySQLAccount.xml" />
</sqlMapConfig>  

전역 셋팅에 해당되는 설정과 typeAlias, 그리고 트랜잭션 관리자 및 SQLMap파일에 대한 위치 정보를 가지고 있다. 전역 셋팅에 해당되는 설정은 이외에도 더 있으며 상세한 설명은 이 글의 범위를 벗어나는 듯하다. 각각의 설정 값에 대한 상세한 정보는 kldp.net에서 호스팅 중인 아이바티스 개발자 가이드 한글문서나 공식 영문문서를 보면 알 수 있다. typeAlias는 말 그대로 타입에 대한 별칭이다. 자바의 패키지 구조로 인해 전체 클래스의 이름이 길 경우 아무래도 사용시 어렵기 때문에 여기서 별칭 형태로 정의해서 차후 불필요한 중복 타이핑을 줄이고자 하는 목적을 지닌다. 트랜잭션 관리자 부분은 간단히 볼 때 데이터베이스 셋팅이다. 간혹 커뮤니티를 통해 아이바티스에서 데이터소스를 2개 이상 설정할 수 없냐는 질문이 나오는데 1.x 버전에서는 가능했으나 2.x 부터는 혼란의 소지가 있어 불가능하도록 만들었다. 꼭 필요한 경우라면 이 설정파일을 데이터소스 개수만큼 생성해서 동적으로 읽어 들이는 수밖에 없을 듯하다. 마지막으로 SQL구문을 가지게 되는 SQLMap파일이다. 여기서는 클래스 패스 기준으로 설정되었다. 아이바티스 사용자 사이에 항상 고민이 되는 WAS 재 시작 없이 SQL구문이 갱신되는 기능은 현재 아이바티스 단독으로만 사용할 때는 SQLMap파일을 파일 경로로 지정해주는 방법 외에는 없을 듯 하다. 물론 이 방법 또한 정상적으로 갱신이 안 되는 경우가 있다고 하니 필자로서는 Spring프레임워크를 사용하는 방법을 권장한다. Spring프레임워크를 사용해서 WAS의 재 시작 없이 SQL구문이 갱신되는 것은 AppFuse를 통해 간단히 체크해볼 수 있다.

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

- ER 다이어그램


일반적인 CRUD작업

데이터 매퍼인 아이바티스는 SQL "구문"을 자바 객체로 매핑하기 때문에 기본적으로 정적인 CRUD작업에 가장 최적화가 되어 있다고 할 수 있다. 물론 데이터베이스 함수 및 프로시저, 동적 SQL 처리에도 사용은 가능하나 제약성을 일부 볼 수 있게 마련이다. 여기서는 SQL구문의 재사용을 위해 <sql> 요소를 사용했고 이를 <include> 요소를 사용해서 다른 구문에서 가져다가 사용하고 있다. 쿼리문을 일종의 특정 id값을 가지는 xml에 저장하고 그 id를 기준으로 호출해서 사용하는 게 아이바티스의 기본 사용법이다. 이 파일을 이해하는데 별무리가 없으리라 생각된다. 그러면 이 SQL구문들을 어떻게 호출해서 사용할까? 자바 코드를 보자.

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="Account">
<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>

<update id="updateAccount" parameterClass="Account">
update account set
  acc_first_name = #firstName#,
  acc_last_name = #lastName#,
  acc_email = #emailAddress#
where acc_id = #id#
</update>

<delete id="deleteAccount" parameterClass="int">
delete from account 
where acc_id = #id#
</delete> 

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

자바 코드

private static SqlMapClient sqlMapper;

static {
  try {
    // log4j setting
    Properties configProps = new Properties();
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    InputStream inputStream = classLoader.getResourceAsStream("log4j.xml");
    configProps.load(inputStream);      
    PropertyConfigurator.configure(configProps);

    // 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";
}
public static String updateAccount(Account account) throws SQLException {
  sqlMapper.update("updateAccount", account);
  return "SUCCESS";
}

public static String deleteAccount(int id) throws SQLException {
  sqlMapper.delete("deleteAccount", id);
  return "SUCCESS";
} 

프로시저

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

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보다 간단하다고 할 수 있으나 직접 해보면 쉽지 않다는 것을 알 수 있다. 생각처럼 명확하게 처리가 되지 않는 경우도 있고 에러를 보기 쉽다.

SQLMap 파일

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

자바 코드

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

프로시저의 처리는 아이바티스 홈페이지나 메일링 리스트를 통해 끊임없이 나오는 문제거리 중에 하나이다. 개발자와 데이터베이스마다 구현방식이 조금씩 다른 것도 있겠지만 여러 가지 눈에 띄는 어려움이 존재하기 때문에 아이바티스 문서를 보면 꼭 표준형태의 프로시저를 사용하도록 권장하고 있다. 필자의 경우에도 Oracle에서는 정상적으로 프로시저 형태의 샘플 코드를 테스트 했으나 MySQL로 작성하면서 이런저런 문제에 봉착했었다. Oracle기준으로 작성된 샘플은 참고문서의 URL을 통해 보길 권한다. 실제 프로젝트에서 아이바티스를 사용하여 프로시저를 처리할 때는 프로시저 사용패턴을 정의하고 사전에 정상적으로 처리가 되는데 테스트 해보길 권하고 싶다. 프로시저 처리시 사용할 메소드에 대한 권고사항이 있다. 살펴보면 다음과 같다.

케이스 메소드명
결과세트를 반환할 때 queryForList(), queryForObject()
한개 이상의 결과 객체를 반환할 때 queryForList()
한개의 결과 객체를 반환할 때 queryforObject()
결과세트를 반환하지 않거나 OUT 파라미터에 결과세트를 셋팅하지 않을 때 update()

위 예제는 IN 파리미터에 값을 전달하고 내부적으로 처리한 후 어떠한 데이터도 반환하지 않기 때문에 update()메소드가 사용되었다.

프로시저 처리시 결과세트를 반환하고 데이터를 업데이트 할 경우, <transactionManager> 요소의 commitRequired 속성값을 true롤 셋팅해줘야 한다.


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> 

자바 코드

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

log4j를 이용해 실제로 수행되는 SQL문을 찍어보면 다음처럼 나온다. 2(N)개의 데이터를 가져오기 위해 실제로는 SQL문을 한번 더(+1) 수행하여 총 3번(Account_family에 한번, Account에 두번)을 실행하게 되는 셈이다. 이것을 N+1 문제라고 하는데 이를 개선하기 위한 방법으로는 groupBy를 사용하는 방법이 있다. groupBy 를 사용하는 방법을 계속 이어서 보자.

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:	selec acc_id, acc_first_name, acc_last_name, acc_email from account where acc_id = ?
 

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> 

자바 코드

@SuppressWarnings("unchecked")
public static List selectAccountWithFamilyAvoidNPlus1(int id) throws SQLException {
  List familys = (ArrayList)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

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>

자바코드

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

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

생성된 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은 아이바티스 사용자가 한번씩은 겪게 마련인 에러가 된다. 즉 애매한 형변화과 문자열 기반의 매핑 구문은 결과적으로 대부분의 에러가 런타임 시 발생하여 개발을 힘들게 만드는 요인이 되고 있다. 이런 점을 해결하기 위해 나온 것인 이 인터페이스 바인딩이다.

Employee employee = (Employee)sqlMapper.queryForList("getEmployee", 5);
//...그리고...
List employees = sqlMapper.queryForList("listAllEmployees");


public interface EmployeeMapper {
  Employee getEmployee (int employeeId);
  List listAllEmployees();
}  

기존에 XML에 타입을 선언하고 문자열 기반으로 구문을 호출하는 것 대신에 좀더 서술적이고 타입에 안전한 인터페이스의 사용으로 앞서 언급된 문제점을 많이 보완하고자 하고 있다. 아래의 코드는 기존 방식의 호출을 새로운 인터페이스 바인딩 방법으로 호출한 것이다. 애매한 모호한 형변환 작업이 없고 문자열 기반의 매핑 구문을 사용하지 않아 기본적으로 컴파일 시 많은 에러를 찾아낼 수 있다. 요즘처럼 IDE를 기본적으로 사용하는 환경이라면 얼마나 많은 도움이 될지는 읙심할 여지가 없다.

Employee emp = empMapper.getEmployee(5);
//...그리고...
List employees = empMapper.listAllEmployees();  

결과적으로 인터페이스 바인딩을 통해 다음의 값을 자동적으로 알 수 있게 되는 셈이다.

  • 매핑 구문명
  • 파라미터 타입
  • 결과 타입
 

다중 레벨 설정

아이바티스의 오랜 설정방식인 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에 담겨야 한다. 단 메타정보만이 어노테이션에서 처리가 가능하도록 될 것이다. 어노테이션은 예제는 다음과 같다. 앞의 관례에 의한 방식에서 칼럼 별 타입을 정의하고 조건문을 구체화하기 위해 어노테이션이 사용된 것을 볼 수 있다.

@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);

@Update({"UPDATE EMPLOYEE SET",
"EMP_ID=@id(NUMERIC:IN),
FIRST_NAME=@firstName(VARCHAR:IN),
LAST_NAME=@lastName(VARCHAR:IN)"})
void updateEmployee(Employee emp);

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

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

@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 getDepartmentsForCompany(int id);  

다음과 같이 캐싱설정또한 가능하게 된다.

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

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

<Mapper cacheContext="Employee">
  <Insert id="insertEmployee" ...> ... </Insert>
  <Update id="updateEmployee" ...> ... </Update>
  <Delete id="deleteEmployee" ...> ... </Delete>
  <Select id="getEmployee" ...> ... </Select>
  <Select id="findEmployeesLike" ...> ... </Select>
</Mapper>  
@flushCache
  List updateAndGetSpecialEmployees();  

는 다음과 같은 의미를 가진다.

<Select id="findEmployeesLike" flushCache="true"...>  ... </Select>  

예제를 통해 보면 현재의 설정을 일일히 XML파일에 적어야 할 사항을 어노테이션을 통해 설정을 함으로써 일단 XML파일내용의 갱신문제에 좀더 자유로워 질 수 있으리라 짐작이 되고 개발자가 초기 XML설정외 대부분의 작업을 자바소스에서 제어함으로써 한결 수월할 작업이 이루어질 수 있으리라 생각할 수 있다. 기본적으로 어노테이션으로 많은 설정이 가능하고 관례라는 방법에 대한 보완적인 성격으로도 사용이 가능하게 된다.

XML 설정 향상

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

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

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");
  }
}  

위 소스는 다음처럼 사용가능하다.

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

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

<Select id="getAccountList" source="org.apache.GetAccountListSQL" ...> ... </Select>

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

 

정리

개인적으로 원했던 기능 중에 3.0에서 추가되도록 활발히 진행중인 것 위주로 살펴보았다. 하지만 곰곰이 생각해보면 아이바티스가 추구했던 간결함이라는 방향에 반하는 기능이 추가되고 있는 것이 아닌가 라는 생각이 들기도 한다. 예를 들어 SQLSource를 사용한 동적 SQL생성 부분은 XML에서 어렵게 SQL문을 조작하는 방법에서 자바 개발자에게 친숙한 자바코드로 SQL문을 생성하게 되는 점에서 환영 받을만하나 기존에 XML에서 모두 관리하던 SQL문이 다소 분산되어서 관리에 어려움이 될 수도 있다고 본다. 즉 다양한 방법의 제공은 관리의 어려움을 양산하기도 한다는 것이다. 물론 이 부분은 철저히 동적 SQL문만을 SQLSource를 사용하여 자동 생성하도록 관리 체계를 수립하면 추가되는 관리상의 어려움이 크지 않을 듯 하다. 그리고 여기서 간단히 살펴본 3.0의 모습은 실제로 구현이 될 수도 있고 다른 형태로 변형이 되어서 구현이 될 수도 있다. 관심 있는 분은 아이바티스 3.0 화이트보드 페이지에 방문해서 원하는 형태의 덧글을 남겨도 좋을 듯 하다. 이 글은 비교되는 다른 ORM프레임워크와 함께 비교가 되는 글이다. 프레임워크마다 저마다의 장점과 단점을 가지게 마련인데 아이바티스의 장점은 간결함이다. 다시 말하면 사용하기가 쉽다는 것이다. JDBC에 익숙한 개발자라면 몇 시간의 교육만으로도 실제 프로젝트에 적용하기가 용이할 것이라고 생각한다. 단점이라고 하면 매핑 작업이 번거롭다는 것이다. 파라미터맵과 결과맵에 매핑해주는 작업이 JDBC를 그대로 사용하는 것보다 그렇게 쉬운 작업이 아니다. 데이터 복잡해질수록 매핑해 주는 작업이 어렵게 느껴지기도 한다. 그럼에도 불구하고 아이바티스를 많이 사용하게 되는데는 충분히 사용할만한 장점이 있다는 것이다. 앞으로도 발전하는 아이바티스의 모습을 기대한다.

참고문서

'개발자 센터 > iBatis' 카테고리의 다른 글

iBatis 에서 페이징하는 방법  (0) 2009.12.13
iBatis 참고 사이트  (0) 2009.12.13
iBatis SqlMapClient API  (1) 2009.12.13
iBatis Framework의 구성  (0) 2009.12.13
iBatis Framework  (0) 2009.12.13
Posted by 피곤키오
,

한 페이지에 10개의 게시글을 불러오는 메서드 작성

sqlMap.queryForList(String args0, Object args1)
public static List getBoardList(long rowsPerPage, long currentPage) {
  List result = new ArrayList(); 
  SqlMapClient sqlMap = MyAppSqlConfig.getSqlMapInstance();
  
  //페이지 정보를 저장한 객체생성
  BoardPage bp = new BoardPage();
  bp.setRowsPerPage(rowsPerPage);
  bp.setCurrentPage(currentPage);
  
  try {
   
   //페이지정보를 저장한 객체를 인자로 넘겨서 리스트를 구함
   result = sqlMap.queryForList("getBoardList", bp);   
   
  } catch(Exception e) {
   e.printStackTrace();
  }
  return result;
 }

 

Board.xml에서 매핑되는 코드 작성

Board.xml
<!-- select된 Board객체를 Mapping시킴 -->
 <resultMap id="boardResultMap" class="board.model.Board">
  <result property="bno" column="bno" />
  <result property="subject" column="subject" />
  <result property="writer" column="writer" />
  <result property="writedate" column="writedate" />
  <result property="hitcount" column="hitcount" />
 </resultMap>
 
  <!-- 페이지에 맞춰 게시글 검색  -->
  <select id="getBoardList" parameterClass="board.model.BoardPage" resultMap="boardResultMap" resultClass="board.model.Board">
  <![CDATA[
  select *
  from ( select rownum rnum, bno, subject, writer, writedate, hitcount
   from ( select bno, subject, writer, writedate, hitcount
             from board
       order by bno desc )
   where rownum <= ( #rowsPerPage# * #currentPage# )
   )
  where rnum >= ( #rowsPerPage# * ( #currentPage# - 1 ) + 1 )
  ]]>
 

'개발자 센터 > iBatis' 카테고리의 다른 글

간결한 데이터 매퍼의 대명사, iBATIS SQLMaps 활용법  (0) 2009.12.13
iBatis 참고 사이트  (0) 2009.12.13
iBatis SqlMapClient API  (1) 2009.12.13
iBatis Framework의 구성  (0) 2009.12.13
iBatis Framework  (0) 2009.12.13
Posted by 피곤키오
,

reference

 

참고문서

 

관련툴

 

 샘플코드

  • queryForMap() 사용예제 - iBATIS사용시 대개의 개발자는 queryForObject queryForList 사용한다. 그래서 queryForMap 대한 이해나 예제가 부족한 실정이다. 그래서 이해를 돕고자 예제를 제공한다.
  • Procedure 사용예제 - Procedure사용예제를 다룬다. IN, OUT, INOUT타입을 다룬다.
  • iBATIS Error Message - iBATIS 사용하면서 겪게 되는 에러 메시지.

'개발자 센터 > iBatis' 카테고리의 다른 글

간결한 데이터 매퍼의 대명사, iBATIS SQLMaps 활용법  (0) 2009.12.13
iBatis 에서 페이징하는 방법  (0) 2009.12.13
iBatis SqlMapClient API  (1) 2009.12.13
iBatis Framework의 구성  (0) 2009.12.13
iBatis Framework  (0) 2009.12.13
Posted by 피곤키오
,

SqlMapClient API – 데이터 조회

 

queryForObject()

데이터베이스로부터 한 개의 레코드를 가져다가 자바 객체에 저장

Object queryForObject(String id, Object parameter) throws SQLException;

-       디폴트 생성자를 가진 객체를 생성(보편적 방법)

-       디폴트 생성자가 없으면 throws "런타임 예외"

 

queryForList()

한 개 이상의 레코드를 가져와서 자바 객체의 List를 만드는 데 사용

List queryForList(String id, Object parameter) throws SQLException;

-       매핑 구문이 반환하는 모든 객체를 반환

 

queryForMap()

데이터베이스로부터 한 개 혹은 그 이상의 레코드를 가져올 때 자바 객체의 Map을 반환

Map queryForMap(String id, Object parameter, String key) throws SQLException;

-       퀴리 실행후 Map 객체를 생성하여 반환

-       key : 결과 객체를 가리키는 키 - > 지정된 프로퍼티의 값

Map queryForMap(String id, Object parameter, String key, String value) throws SQLException;

-       key : 결과 객체를 가리키는 키 - > 지정된 프로퍼티의 값

-       value : 결과 객체 - > 지정된 프로퍼티의 값

 

 

SqlMapClient API – 데이터 갱신

 

insert 메소드

Object insert(String id, Object parameterObject) throws SQLException;

-       parameterObject : 파라미터 객체(데이터베이스에 데이터 삽입하는 데 사용)

-       반환 : 객체

 

update 메소드

int update(String id, Object parameterObject) throws SQLException;

-       parameterObject : 값을 제공하는 데 사용할 파라미터 객체

-       반환: update 구문에 의해 영향을 받은 레코드의 개수

 

 

delete 메소드

int delete(String id, Object parameterObject) throws SQLException;

-       parameterObject : 값을 제공하는 데 사용할 파라미터 객체

-       반환: 삭제된 레코드의 개수

 

 

트랜잭션(Transaction) 관리

SqlMapClient인터페이스는 트랜잭션경계를 지정하기 위해 메소드를 가진다. 트랜잭션이 시작되고 SqlMapClient인터페이스의 다음과 같은 메소드를 사용함으로써 commit되거나 rollback된다.

-       public void startTransaction () throws SQLException

-       public void commitTransaction () throws SQLException

-       public void endTransaction () throws SQLException

 

private Reader reader =

             new Resources.getResourceAsReader ("com/ibatis/example/sqlMap-config.xml");

private SqlMapClient sqlMap = XmlSqlMapBuilder.buildSqlMap(reader);

            

public updateItemDescription (String itemId, String newDescription)

             throws SQLException {

             try {

                           sqlMap.startTransaction ();

                           Item item = (Item) sqlMap.queryForObject ("getItem", itemId);

                           item.setDescription (newDescription);

                           sqlMap.update ("updateItem", item);

                           sqlMap.commitTransaction ();

             } finally {

                           sqlMap.endTransaction ();

             }

}

 

 

MySqlConfig.java

public class MySqlConfig {

            

             private static SqlMapClient sqlMap;

             static {

                           Reader reader = null;

                           try {

                                        String resource = "SqlMapConfig.xml";

                                        reader = Resources.getResourceAsReader (resource);

                                        sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);

                           } catch (Exception e) {

                                        e.printStackTrace();

                           }finally{

                                        if(reader != null){

                                                     try {

                                                                  reader.close();

                                                     } catch (IOException e) {

                                                                  e.printStackTrace();

                                                     }

                                        }

                           }

             }

             public static SqlMapClient getSqlMapInstance () {

                           return sqlMap;

             }

}

 

'개발자 센터 > iBatis' 카테고리의 다른 글

간결한 데이터 매퍼의 대명사, iBATIS SQLMaps 활용법  (0) 2009.12.13
iBatis 에서 페이징하는 방법  (0) 2009.12.13
iBatis 참고 사이트  (0) 2009.12.13
iBatis Framework의 구성  (0) 2009.12.13
iBatis Framework  (0) 2009.12.13
Posted by 피곤키오
,

iBatis Framework 구성

 

수행과정

1.     파라미터로서의 객체를 제공한다(자바빈,MAP, 원시래퍼[String, Integer, etc... ]),

-  파라미터 객체는 쿼리문 내의 입력값을 셋팅하거나 where절을 셋팅하기 위해 사용된다

2.     매핑된 statement실행

-  Data Mapper프레임웤은 PreparedStatment인스턴스생성

-  제공된 파라미터객체를 이용해 파라미터 설정

-  statement실행

-  ResultSet으로 부터 결과 객체 생성

3.     update의 경우 영향을 받은 row수 반환

4.     select문일 경우 한개의 객체 또는 컬렉션 객체 반환

-  결과객체의 타입은 파라미터 객체와 같은 타입이 될수 있다

 

SqlMapConfig.xml 파일

데이터소스에 대한 설정, 쓰레드 관리와 같은 SQL Maps와 다른 옵션에 대한 설정을 제공하는 중앙집중적인 XML 설정 파일.

 

SIMPLE 방식

db.properties

driver = com.mysql.jdbc.Driver

url = jdbc:mysql://127.0.0.1:3306/struts

username = scott

password = tiger

 

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>

<properties resource="db.properties"/>

             <settings cacheModelsEnabled="true" enhancementEnabled="true"

                          lazyLoadingEnabled="false" useStatementNamespaces="false" />

                          <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= "Product.xml"/>

</sqlMapConfig>

 

JNDI 방식

context.xml

<?xml version="1.0" encoding="euc-kr"?>

<Context>

 

             <Resource name="jdbc/StrutsDB"

                           auth="Container"

                           type="javax.sql.DataSource"

                           username="scott"

                           password="tiger"

                           driverClassName="com.mysql.jdbc.Driver"

                           url="jdbc:mysql://127.0.0.1:3306/struts"

                           maxActive="5"

                           maxIdle="3"/> 

                          

</Context>

 

SqlMapConfig.xml

<transactionManager type="JDBC">

    <dataSource type="JNDI">

      <property name="DataSource" value="java:comp/env/jdbc/StrutsDB"/>

    </dataSource>

</transactionManager>

 

내장 별칭트랜잭션 매니저

별칭

Fully Qualified Class Name

JDBC

com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig

JTA

com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig

EXTERNAL

com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig

 

내장 별칭 데이터 타입

별칭

Fully Qualified Class Name

string

java.lang.String

byte

java.lang.Byte

long

java.lang.Long

short

java.lang.Short

int

java.lang.Integer

double

java.lang.Double

float

java.lang.Float

boolean

java.lang.Boolean

decimal

java.math.BigDecimal

object

java.lang.Object

map

java.util.Map

hashmap

java.util.HashMap

list

java.util.List

arraylist

java.util.ArrayList

collection

java.util.Collection

iterator

java.util.Iterator

 

 

내장 별칭 데이터소스 팩토리 타입

별칭

Fully Qualified Class Name

SIMPLE

com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory

DBCP

com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory

JNDI

com.ibatis.sqlmap.engine.datasource.JndiDataSourceFactory

 

내장 별칭 캐시 컨트롤러 타입

별칭

Fully Qualified Class Name

FIFO

com.ibatis.sqlmap.engine.cache.fifo.FifoCacheController

LRU

com.ibatis.sqlmap.engine.cache.Iru.LruCacheController

MEMORY

com.ibatis.sqlmap.engine.cache.memory.MemoryCacheController

OSCACHE

com.ibatis.sqlmap.engine.cache.OSCacheController

 

내장 별칭 – XML결과 타입

별칭

Fully Qualified Class Name

Dom

com.ibatis.sqlmap.engine.type.DomTypeMarker

domCollection

com.ibatis.sqlmap.engine.type.DomCollectionTypeMarker

Xml

com.ibatis.sqlmap.engine.type.XmlTypeMarker

XmlCollection

com.ibatis.sqlmap.engine.type.XmlCollectionTypeMarker

 

Sql Map xml파일

Product.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 id=Product>

             <cacheModel id=productCache type=LRU>

                           <flushInterval hours=24/>

                           <property name=size value=1000 />

             </cacheModel>

            

             <typeAlias alias=product type=com.ibatis.example.Product />

            

             <parameterMap id=productParam class=product>

                           <parameter property=id/>

             </parameterMap>

            

             <resultMap id=productResult class=product>

                           <result property=id column=PRD_ID/>

                           <result property=description column=PRD_DESCRIPTION/>

             </resultMap>

            

             <select id=getProduct parameterMap=productParam

                           resultMap=productResult cacheModel=product- cache>

                           select * from PRODUCT where PRD_ID = ?

             </select>

</sqlMap>

 

 

Statement 타입

Statement Element

Attributes

Child Elements

Methods

<statement>

id

parameterClass

resultClass

listClass

parameterMap

resultMap

cacheModel

All dynamic elements

Insert

Update

Delete

All query methods

<insert>

id

parameterClass

parameterMap

All dynamic elements

<selectKey>

<generate>

Insert

Update

Delete

<update>

id

parameterClass

parameterMap

extends

All dynamic elements

<generate>

Insert

Update

Delete

<delete>

id

parameterClass

parameterMap

extends

All dynamic elements

<generate>

Insert

Update

Delete

<select>

id

parameterClass

resultClass

listClass

parameterMap

resultMap

cacheModel

extends

All dynamic elements

<generate>

All query methods

<procedure>

id

parameterMap

resultClass

resultMap

cacheModel

All dynamic elements

Insert

Update

Delete

All query methods

 

iBatis에서 처리가능한 데이터 타입

-       Bean, Map, Primitive, XML

-       대부분의 경우 Bean, Map이 가장 많이 사용된다.

 

장점

단점

Bean

성능

컴파일 시 타입 및 이름 검사

형변환이 줄어듬

코드량 증가

Map

코드량 감소

느림

컴파일시 검사하지 않음

오류가 실행 시 체크됨

잦은 형 변환

 

파라미터 처리인라인 파라미터

-       인라인 파라미터

# propertyName#

# propertyName:jdbcType#

# propertyName:jdbcType:nullValue#

-       대개는 jdbcType이나 nullValue값에 대해 명시하지 않고 프로퍼티명만 명시해주는 방법을 사용

 

Director.xml

<select id="selectDirectorById1" resultMap="DirectorResultMap1"

             parameterClass="string">

             select *

             from director

             Director.xml

             where director_id = # value#

</select>

 

파라미터 처리외부 파라미터 맵

-       인라인 파라미터 매핑과 동일한 기능을 제공하면서 SQL Map 적재시 속도가 빨라지고 유지보수가 용이하다.

-       파라미터에 관련된 사항을 중앙집중적으로 관리 가능하게 해준다.

 

Director.xml

<parameterMap class="Director" id="DirectorParameterMap">

             <parameter property="id" javaType="string" jdbcType="varchar"/>

             <parameter property="name" javaType="string" jdbcType="varchar"/>

             <parameter property="age" javaType="int" jdbcType="int"/>

             <parameter property="winnerCount" javaType="int" jdbcType="int"

                           nullValue="0"/>

</parameterMap>

 

<insert id="insertDirector2" parameterMap="DirectorParameterMap">

             insert into director(

                           director_id,

                           director_name,

                           director_age,

                           director_winnerCount

             ) values(?,?,?,?)

</insert>

 

결과 데이터 처리인라인 결과 맵

-       간단하고 쉽게 사용할 수 있다.

-       프레임워크에 의해 자동 매핑 되어진다.

-       런타임시 오류 발생할 확률이 높다.

 

Director.xml

<select id="selectDirectorCount" resultClass="int">

             select

                           count(*) as value

             from director

</select>

 

<select id="selectDirectorById" resultClass="Director" parameterClass="string">

             select

                           director_id as id,

                           director_name as name,

                           director_age as age,

                           director_winnerCount as winnerCount

             from director

             where director_id = #value#

</select>

 

결과 데이터 처리외부 결과 맵

-       외부 파라미터 맵 처럼 객체의 프로터피와 SQL 결과의 컬럼명을 설정하고 각각의 데이터 타입 지정 및 null값에 대한 처리가 가능하다.

-       좀 더 나은 성능 및 유지보수가 용이하다.

 

Director.xml

<resultMap class="Director" id="DirectorResultMap2">

<result property="id" column="director_id" javaType="String" jdbcType="varchar"/>

<result property="name" column="director_name" javaType="String"

             jdbcType="varchar"/>

<result property="age" column="director_age" javaType="int" jdbcType="int"/>

<result property="winnerCount" column="director_winnerCount" javaType="int"

             jdbcType="int" nullValue="0"/>

</resultMap>

<select id="selectDirectorById2" resultMap="DirectorResultMap2"

             parameterClass="string">

             select * from director

             where director_id = #value#

</select>

'개발자 센터 > iBatis' 카테고리의 다른 글

간결한 데이터 매퍼의 대명사, iBATIS SQLMaps 활용법  (0) 2009.12.13
iBatis 에서 페이징하는 방법  (0) 2009.12.13
iBatis 참고 사이트  (0) 2009.12.13
iBatis SqlMapClient API  (1) 2009.12.13
iBatis Framework  (0) 2009.12.13
Posted by 피곤키오
,
ibatis

 

iBatis 환경설정

http://ibatis.apache.org/ 에서 java 버전의 2.x 라이브러리를 다운로드 받는다.
압축을 풀고 /lib 폴더에 있는 ibatis-2xxxx.jar 라이브러리를 클래스패스에 추가하거나 웹어플리케이션의 WEB-INF/lib 에 저장합니다.


iBatis 특징

  • 간단하면서 단순한 퍼시스턴트 프레임워크이다.
  • JDBC에 비해 약 60%이상의 코드를 줄일 수 있도 간단한 설정으로 생산성을 높일 수 있다.
  • join, 적재지연 등을 활용하여 데이터 접근 속도를 높일 수 있다.
  • 관심사의 분리로 차후에 유지보수가 용이하다.
  • 전문성을 강화하기 위한 작업의 분배가 용이하다.
  • 특정 프로그래밍 언어에 종속적이지 않다.

iBatis 장점

  • SQL문과 소스 코드 분리로 소스 코드의 간결함을 유지할 수 있다
  • 데이터베이스 자원에 대한 제어를 자동으로 해결해 준다.
  • SQL Injection으로부터 보호된다

 

JDBC와 iBatis 비교

다음은 JDBC와 iBatis를 비교해보도록 하겠습니다.


JDBC 이용

UserDAO.java
public List findUserList() throws SQLException {

	Connection con = null;
	PreparedStatement pstmt = null;
	ResultSet rs = null;
	List userList = new ArrayList();

	StringBuffer query = new StringBuffer();
	query.append("SELECT USERID,PASSWORD,NAME,EMAIL FROM USERINFO ");

	try {
		con = source.getConnection();
		pstmt = con.prepareStatement(query.toString());
		rs = pstmt.executeQuery();

		while (rs.next()) {
			User user = new User();
			user.setPassword(rs.getString("PASSWORD"));
			user.setName(rs.getString("NAME"));
			user.setEmail(rs.getString("EMAIL"));
			user.setUserId(rs.getString("USERID"));
			userList.add(user);
		}

	} finally {
		if (rs != null)
			rs.close();
		if (pstmt != null)
			pstmt.close();
		if (con != null)
			con.close();
	}
	return userList;
}

iBatis 이용

UserDAO.java
…
public List findUserList() throws SQLException{

             SqlMapClient sqlMapper = MySqlConfig.getSqlMapInstance();
             
             return sqlMapper.queryForList("selectAllUser");

}
…

User.xml
…
<select id="selectAllUser" resultClass="User">

             select * from userinfo

</select>
…
iBatis이용시 소스가 더 간결해진 것을 확인할수 있습니다.
Posted by 피곤키오
,