[iBatis] queryWithRowHandler 메소드를 사용한 RowHandler사용하기

Published on: 2013. 12. 18. 15:15 by louis.dev

iBatis는 DB쿼리문의 결과를 Java Object로 매핑시켜주는 Persistent Layer의 Framework로서 DB쿼리로 검색된 결과를 다양한 자바 오브젝트( Custom Object, List, Map 등)로 생성, 매핑 시켜주어 DB 쿼리 결과를 자바단에서 쉽게 사용할수 있도록 해줍니다.


그러나 수행한 쿼리의 Row수가 대량일 경우 iBatis는 문제를 일으키게 됩니다.


예를 들어 특정 쿼리로 검색된 row의 수가 천만건이 된다고 하면, iBatis는 해당 쿼리 결과를 자바 오브젝트에 매핑 시키기 위해 사이즈가 천만인 ArrayList 인스턴스를 생성하게 될것입니다. 그렇게 대량의 자바 인스턴스를 생성하게 되면 JVM에서 사용하는 메모리사이즈를 초과하게 되어 결국 Out of Memory Exception를 뿜으며 시스템이 뻗어버리게 됩니다.


위와 같이 대량의 데이터를 셀렉트 해오는 경우에는 RowHandler를 사용하여 Out Of Memory Exception을 피해갈수 있습니다.


RowHandler를 생성하는 방법은 com.ibatis.sqlmap.client.event.RowHandler 인터페이스를 impliments 하면 됩니다.

public class TestRowHandlerCallback implements RowHandler {
	@Override
	public void handleRow(Object arg0) {
		TestClass class = (TestClass) arg0;
		//TODO select된 row 하나하나가 Java Object로 생성되어 넘어온다.
	}
}

이렇게 만들어진 RowHandler는 SqlMapClient를 사용할때 queryWithRowHandler라는 메소드를 사용하여 RowHandler를 사용하도록 지정하면 됩니다.

TestRowHandlerCallback callback = new TestRowHandlerCallback();
//getSqlMapClientTemplate().queryWithRowHandler("Test.rowHandlerTestQuery", callback);		//스프링 사용시
getSqlMapClient().queryWithRowHandler("Test.rowHandlerTestQuery", callback);

이렇게 queryWithRowHandler 메소드로 RowHandler를 지정하게 되면 iBatis는 List 인스턴스를 생성하지 않고 Row당 하나의 자바 오브젝트를 생성한 후 RowHandler의 handleRow 메소드를 실행시킵니다. 


이렇게 되면 아무리 많은 row가 검색되었다고 하더라도 List Object를 생성하지 않기 때문에 Out Of Memory Exception이 발생하지 않게 되어 안전하게 개발할 수 있습니다.






iBatis - 기본 설정 & 게시판 만들기

Published on: 2010. 10. 26. 19:51 by louis.dev

IBatisTutorial.war



얼마전 iBatis 프로젝트가 myBatis라는 이름으로 변경되면서 apache 사이트에서 code.google로 이동하였습니다. myBatis 프로젝트로 변경되면서 많은 설정 부분이 변경되었지만, 일단 많이 사용하는 iBatis2 버전을 가지고 진행 하겠습니다.

본 설정법은 Eclipse + Tomcat 6.0 그리고 MySql 5 버전을 통해 진행하도록 하겠습니다.

iBatis란?
iBatis는 DBMS와 Java 객체간의 자동 맵핑 프레임웍 입니다.
즉, iBatis에서 select 쿼리를 수행하여 검색 된 쿼리가 다음과 같고
 idx  category  title  regDate  readCount
 1  질문  질문합니다.  2010-10-26  0

자바 객체의 property 명이 검색된 쿼리문의 column의 이름과 같다면
package net.tutorial.ibatis.vo;

import java.util.Date;

public class IbatisBoardVO {
	private int idx;
	private String category;
	private String title;
	private String contents;
	private Date regDate;
	private int readCount;
	private String writer;
}

각각의 검색된 column 명에따라 자동으로 객체에 저장이 됩니다.

이렇듯 iBatis는 쉽게 DB의 데이터를 제어할 수 있는 DBMS-JAVA 객체간의 맵핑 프레임웍입니다.

iBatis 설정방법
1. iBatis 라이브러리와 MySQL Connector 라이브러리 다운
자바 프로그래밍의 시작은 늘 라이브러리의 추가입니다~^^

iBatis-2.3.4.726 을 다운로드 후, lib안의 ibatis-2.3.4.726.jar 
MySQL Java용 Connector 다운로드 후, mysql-connectgor-java-5.1.13-bin.jar
위의 두 파일을 이클립스에서 생성한 웹프로젝트 안의 webContent\WEB-INF\lib 디렉토리에 추가합니다.

2. 게시판에 사용될 DB와 Table을 생성합니다.(콘솔에서 접근하여 DB를 생성해도 되고, 다른 어플리케이션을 통해서 테이블을 생성하셔도 상관없습니다. 전 개인적으로 SQLGate라는 프로그램을 사용해서 개발합니다.^^)

MySQL DB생성과 권한 설정 방법

-DB 생성 쿼리
CREATE DATABASE board_db DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

-Table 생성 쿼리
CREATE TABLE `ibatis_board_tb` (
  `idx` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `category` char(30) DEFAULT NULL,
  `title` text,
  `contents` text,
  `reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `read_count` int(10) DEFAULT NULL,
  `writer` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`idx`),
  UNIQUE KEY `idx` (`idx`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

위와 같은 스크립트로 board_db 라는 데이터 베이스와 ibatis_board_tb 라는 테이블을 생성했습니다.

3. 생성된 테이블과 맵핑될 Value Object를 생성해 줍니다.
- IbatisBoardVO
package net.tutorial.ibatis.vo;

import java.util.Date;

public class IbatisBoardVO {
	private int idx;
	private String category;
	private String title;
	private String contents;
	private Date regDate;
	private int readCount;
	private String writer;
	public int getIdx() {
		return idx;
	}
	public void setIdx(int idx) {
		this.idx = idx;
	}
	public String getCategory() {
		return category;
	}
	public void setCategory(String category) {
		this.category = category;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContents() {
		return contents;
	}
	public void setContents(String contents) {
		this.contents = contents;
	}
	public Date getRegDate() {
		return regDate;
	}
	public void setRegDate(Date regDate) {
		this.regDate = regDate;
	}
	public int getReadCount() {
		return readCount;
	}
	public void setReadCount(int readCount) {
		this.readCount = readCount;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("IbatisBoardVO [category=");
		builder.append(category);
		builder.append(", contents=");
		builder.append(contents);
		builder.append(", idx=");
		builder.append(idx);
		builder.append(", readCount=");
		builder.append(readCount);
		builder.append(", regDate=");
		builder.append(regDate);
		builder.append(", title=");
		builder.append(title);
		builder.append(", writer=");
		builder.append(writer);
		builder.append("]");
		return builder.toString();
	}
}


4. DB를 사용할때 필요한 설정파일을 생성합니다.
1) db.properties
이 파일은 DB Connection을 맺기 위한 driver, url, username, password 등을 저장하는 파일입니다.
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost/생성한DB이름?characterEncoding=utf-8
username=DB에접근아이디
password=DB접근패스워드


2) iBatis 설정을 할 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>
	<!-- 하단의 DataSource 설정을 위해 위에서 생성한 db.properties 파일 위치 설정 -->
	<properties resource="net/tutorial/ibatis/sqlmap/db.properties" />
	<!-- 
		iBatis사용을 위한 설정입니다. 아래 설정내용은 
		useStatementNamespaces="false"를 true로 변경한것 빼고는 Default 설정입니다.
	-->
	<settings
		cacheModelsEnabled="true"
		enhancementEnabled="true"
		lazyLoadingEnabled="true"
		maxRequests="32"
		maxSessions="10"
		maxTransactions="5"
		useStatementNamespaces="true"
	/>
	<!-- properties태그로 properties 파일 위치 경로를 선언하면 
	     ${properties파일에 선언한 이름} 으로 접근하여 데이터를 가져 올수 있습니다.
	     properties 파일을 생성하지 않고 ${driver} 대신 바로 "com.mysql.jdbc.Driver"
	     와 같이 직접 넣을 수도 있습니다.
	-->
	<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 태그는 실제 쿼리문이 들어갈 xml파일입니다. -->
	<sqlMap resource="net/tutorial/ibatis/sqlmap/BoardSqlMap.xml" />
</sqlMapConfig>


3) 사용할 쿼리문을 정의할 BoardSqlMap.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">
<!--
 	SqlMapConfig.xml 파일에서 namespace사용을 true로 변경하였기 때문에 이 XML의 네임스페이스를 
 	IbatisBoard로 정의합니다.
-->
<sqlMap namespace="IbatisBoard">
	<!--
		typeAlias태그는 type에 선언된 클래스 파일을 짧은이름으로 치환해 주는 역할을 합니다.
		즉 net....IbatisBoardVO란 java파일을 이 xml에서는 Board란 이름으로 사용하겠다는 뜻입니다
	-->
	<typeAlias alias="Board" type="net.tutorial.ibatis.vo.IbatisBoardVO"/>
	
	<!-- 
		실제 사용할 쿼리 문입니다.
		select 시에는 select,
		delete 시에는 delete
		update 시에는 update 태그를 사용하여 쿼리문을 생성합니다.
		이때 id는 필수 요소 입니다. 이 아이디를 통해 쿼리문을 접근할 수 있습니다.
		resultClass="Board" 는 아래 쿼리문을 통해 select된 데이터를 Board 클래스에 자동으로 저장하겠다는 뜻이고, 이 Board클래스는 상단의 typeAlias를 통해 alias시킨 것임으로 결국 IbatisBoardVO에 자동으로 저장될 것입니다.
		 
	-->
	<select id="getTotalBoardList" resultClass="Board">
		SELECT
			idx, category, title, reg_date AS regDate, read_count AS readCount, writer
		FROM
			ibatis_board_tb
		ORDER BY idx DESC
	</select>
</sqlMap>


4) iBatis를 사용하기 위한 자바 객체인 SqlMapClient를 생성하는 SQLManager class를 생성합니다.
package net.tutorial.ibatis.manager;

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

public abstract class SQLManager{
	private SqlMapClient sc;

    public SQLManager(){
        sc = null;
        try{
            sc = SqlMapClientBuilder.buildSqlMapClient(Resources.getResourceAsReader("net/tutorial/ibatis/sqlmap/SqlMapConfig.xml"));
        }
        catch(IOException ie){
            ie.printStackTrace();
        }
    }

    public SqlMapClient getSqlMap(){
        return sc;
    }

}


SQLMapClient객체는 SqlMapConfig.xml파일을 읽어서 생성하게 되고, getSqlMap method를 통해 SqlMapClient객체를 전달 하게 됩니다.
이 클래스는 DB접근을 위한 클래스인 DAO(Data Access Object)에서 상속받아 사용하면됩니다.

여기까지 iBatis 사용을 위한 설정이 끝났습니다. 이제 이 설정을 통해 iBatis사용 하도록 해보겠습니다.
저는 html테그를 사용할 jsp - 비지니스로직을 처리할 service class - iBatis를 사용해서 DB에 접근하기 위한 DAO class를 만들어 MVC패턴으로 진행하도록 하겠습니다.

5. SQLManager를 상속받은 DAO class를 생성합니다.
package net.tutorial.ibatis.dao;

import java.sql.SQLException;
import java.util.List;

import net.tutorial.ibatis.manager.SQLManager;
import net.tutorial.ibatis.vo.IbatisBoardVO;

public class IbatisBoardDAO extends SQLManager {
	@SuppressWarnings("unchecked")
	public List<IbatisBoardVO> getTotalBoardList(){
		try {
			return getSqlMap().queryForList("IbatisBoard.getTotalBoardList");
		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}
	}
}


getTotalBoardList란 method는 select Query를 통해 게시판 테이블의 내용을 모두 가져와 List형태로 리턴해 주는 method입니다.
이부분에서 중요하게 봐야 할부분은 두군대 인데요

첫번째. SQLManager를 상속받고 getSqlMap method로 SqlMapClient 객체를 가져옵니다.
이때 SqlMapClient객체를 통해 호출 할수 있는 method는 여러가지가 있는데 5가지 정도만 살펴보겠습니다.(제일 많이 사용하는 method이기도 합니다)
① queryForList : Select를 통해 데이터를 가져올때 사용합니다. 만약 Select를 통해 얻어오는 데이터가 여러 행일경우, iBatis는 자동으로 List형태로 변환하여 전달해 줍니다.
② queryForObject : Select를 통해 데이터를 가져올때 사용합니다. 만약 select를 통해 얻어오는 데이터가 1개일 경우(where 조건문을 통해서) queryForObject 를 사용합니다.
③ insert : insert를 할때 사용합니다.
④ update : update를 할때 사용합니다.
⑤ delete : delete를 할때 사용합니다.

두번째. queryForList메소드(queryForObject, insert, update, delete 모두 동일)의 파라미터로 IbatisBoard.getTotalBoardList 로 전달하는데 앞의 IbatisBoard는 BoardSqlMap.xml의 namespace이고, 뒤의 getTotalBoardList는 BoardSqlMap.xml의 select태그 ID입니다.
이렇게 하면 IbatisBoard 라는 네임스페이스를 갖는 xml에서 getTotalBoardList라는 아이디를 갖는 쿼리를 실행하겠단 뜻이 됩니다.

6. 비지니스 로직을 처리할 Service class인 IbatisBoardServlce class를 생성합니다.
package net.tutorial.ibatis.service;

import java.util.List;

import net.tutorial.ibatis.dao.IbatisBoardDAO;
import net.tutorial.ibatis.vo.IbatisBoardVO;

public class IbatisBoardService {
	private IbatisBoardDAO ibatisBoardDAO = new IbatisBoardDAO();
	
	public List<IbatisBoardVO> getTotalBoardList() {
		return ibatisBoardDAO.getTotalBoardList();
	}
}

크게 로직을 처리하는 부분은 아직까지 없음으로 결과값만 return해 주는 역할만 하겠습니다.

7. 데이터를 보여줄 웹페이지(board_list.jsp)를 만들겠습니다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="net.tutorial.ibatis.vo.IbatisBoardVO"%>
<%@page import="net.tutorial.ibatis.service.IbatisBoardService"%>
<%
	IbatisBoardService boardService = new IbatisBoardService();
	List<IbatisBoardVO> boardList = boardService.getTotalBoardList();
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>IBatis 게시판 tutorial</title>
</head>
<body>
	<table border="1" cellpadding="0" cellspacing="0" width="800" align="center">
		<tr>
			<td>글번호</td>
			<td>카테고리</td>
			<td>제목</td>
			<td>작성자</td>
			<td>등록일</td>
			<td>조회수</td>
		</tr>
		<% for(IbatisBoardVO board : boardList ) { %>
		<tr>
			<td><%=board.getIdx() %></td>
			<td><%=board.getCategory() %></td>
			<td><%=board.getTitle() %></td>
			<td><%=board.getWriter() %></td>
			<td><%=board.getRegDate() %></td>
			<td><%=board.getReadCount() %></td>
		</tr>	
		<%} %>
	</table>
</body>
</html>


위와 같이 작업하시면 결과 페이지를 확인 하실수 있습니다. 단. DB에 글 데이터는 들어있어야 겠죠~?^^

iBatis - iterate 태그로 동적 Query를 생성해 보자

Published on: 2010. 10. 6. 13:44 by louis.dev
Query문 중 가끔 동적으로 어떠한 쿼리를 생성해야 할 경우가 있습니다.. 
예를 들면 어떠한 리스트에 있는 값을 통해 쿼리문을 생성한다던지 하는 경우 iBatis에서는 iterate 태그로 for문과 같은 역할을 하여 동적 쿼리를 생성 할 수 있습니다.

<select id="getData" parameterClass="java.util.List" resultClass="int">
	SELECT
		count(0)
	FROM data_tb
	<iterate prepend="WHERE id IN" open="(" close=")" conjunction="," >
        	#[]#
     	</iterate>
</select>

위와 같이 parameterClass를 java의 List형태로 전달해 주면 동적쿼리를 생성할수 있습니다. 만약 list에 1,2,3,4 의 데이터가 들어가 있으면 결과적으로
다음과 같은 쿼리가 실행되는 것과 같습니다.
<select id="getData" parameterClass="java.util.List" resultClass="int">
	SELECT
		count(0)
	FROM data_tb
	WHERE id IN (1,2,3,4)
</select>
이렇게  iterate를 사용하여 동적으로 쿼리문을 생성할 수 있습니다.

또한 parameterClass가 list형태가 아닌 Map이 List를 가지고 있는 형태도 iterate의 property 속성을 통해 접근할 수 있습니다.
만약 HashMap에 paramList라는 이름으로 list형이 들어가 있다고 가정하면
예)
 HashMap<String, Object> paramMap = new HashMap<String, Object>();
List<Integer> paramList = new ArrayList<Integer>();
paramMap.put("paramList", paramList);
<select id="getData" parameterClass="HashMap" resultMap="myResult">
	SELECT
		*
	FROM data_tb
	<iterate property="paramList" prepend="WHERE id IN" open="(" close=")" conjunction="," >
                #paramList[]#
     	</iterate>
 </select>

위와 같이 HashMap에 들어있는 list는 iterate의 property 속성을 통해 List를 가져 옵니다. ##사이는 HashMap에 put했던 list이름을 넣어주셔야 작동합니다.