[Mybatis] 프로시져(procedure) 실행하기

Published on: 2013. 11. 14. 14:09 by louis.dev
ibatis 시절에는 procedure라는 테그가 있어서 해당 테그를 이용해 프로시져를 수행했다면 myBatis에서는 일반 쿼리문 처럼 insert, update, select 문을 통하여 프로시져를 수행할수 있다. insert, update, select 중 어떠한 테그를 사용해도 프로시저를 호출할수 있다. 대신 statementType="CALLABLE" 이라는 속성을 추가해 줘야 프로시져로 인식하여 프로시져를 수행한다.
<update id="execureProcedure" statementType="CALLABLE">
	{
		call procedure_name(#{id}, #{name})
	}
</update>

[Mybatis] 쿼리 파라미터 null 처리방법

Published on: 2013. 11. 14. 14:08 by louis.dev
myBatis와 iBatis에서 쿼리를 실행할때 PrepareStatement 방식으로 작동을 하게 되고 이때 쿼리로 전달값은 값을 각각 아래처럼 적용한다.
<!-- ibatis 방식-->
<insert id="insertQuery" parameterType="java.util.Map">
	INSERT INTO table (id , name, title) VALUES (#id#, #name#, #title#)
</select>

<!-- mybatis 방식 -->
<insert id="insertQuery">
	INSERT INTO table (id , name, title) VALUES (#{id}, #{name}, #{title})
</insert>
그런데 이때 insert 하려는 값 중에 null이 전달되었을 경우 오라클에서는
uncategorized SQLException for SQL []; SQL state [99999]; error code [17004]; 부적합한 열 유형: 1111; nested exception is java.sql.SQLException: 부적합한 열 유형: 1111
이런 에러를 발생시키게 된다. Spring을 사용했을 경우는 이런에러를 발생시킨다.
Caused by: org.springframework.jdbc.UncategorizedSQLException: Error setting null parameter.  Most JDBC drivers require that the JdbcType must be specified for all nullable parameters. Cause: java.sql.SQLException : 부적합한 열 유형: 1111
그렇다고 항상 쿼리에서 null을 체크하는 방식을 사용하는것은 너무나도 귀찮은 일!! 그래서 if(myBatis)문 혹은 isNotEmpty(ibatis)를 사용하지 않는 방법이 있다. (자세히 보면 Spring Framework에서 발생시키는 에러에 답이있다.)  
<!-- ibatis 방식-->
<insert id="insertQuery" parameterType="java.util.Map">
	INSERT INTO table (id , name, title) VALUES (#id:VARCHAR#, #name:VARCHAR#, #title:VARCHAR#)
</insert>

<!-- mybatis 방식 -->
<insert id="insertQuery">
	INSERT INTO table (id , name, title) VALUES (#{id, jdbcType=VARCHAR}, #{name, jdbcType=VARCHAR}, #{title, jdbcType=VARCHAR})
</insert>
위와 같이 전달되는 파라미터 값의 jdbcType이 무엇인지를 정해 주면 쿼리는 자연스럽게 null로 변경되어 insert query가 수행된다.(물론 해당 column이 NULL값을 저장가능한 column 이어야 한다) 지원하는 jdbcType은 아래와 같다.
  1. BIT
  2. FLOAT
  3. CHAR
  4. TIMESTAMP
  5. OTHER
  6. UNDEFINED
  7. TINYINT
  8. REAL
  9. VARCHAR
  10. BINARY
  11. BLOB
  12. NVARCHAR
  13. SMALLINT
  14. DOUBLE
  15. LONGVARCHAR
  16. VARBINARY
  17. CLOB
  18. NCHAR
  19. INTEGER
  20. NUMERIC
  21. DATE
  22. LONGVARBINARY
  23. BOOLEAN
  24. NCLOB
  25. BIGINT
  26. DECIMAL
  27. TIME
  28. NULL
  29. CURSOR
쿼리마다 적당이 jdbcType을 지정해서 사용하자.

[Mybatis]resultMap을 이용한 1:N select시 주의 점

Published on: 2013. 11. 14. 14:07 by louis.dev
mybatis에서 테이블간의 1:N관계를 select 할때 resultMap을 통한 일종의 서브쿼리 형식으로 데이터를 가져올수 있다. 예를들어 게시판(BOARD)라는 테이블과 댓글(COMMENT)라는 테이블이 있고, 하나의 게시글에는 여러개의 댓글이 생성될수 있음으로 게시판과 댓글의 관계는 1:N관계이다. 이런 구조를 자바 코드로 클래스를 만들어 보면 다음과 같다.
class  Board{
	private String board;
	private String title;
	private String content;
	private String writer;
	private List<Comment> comments;

	//getter, setter 생략
}
소스에서도 볼수 있듯이 Board라는 클래스는 comment의 List형태인 comments라는 프로퍼티를 가지고 있게된다. 이렇게 1:N의 구조일때 Mybatis에서는 쿼리를 저장하는 xml파일(iBatis에서는 sqlmap으로 불렀으나 mybatis에서는 mapper라고 부름)에 resultMap 엘리먼트로 다음과 같이 설정할 수 있다.
<resultMap id="boradResult" type="net.krespo.mybatis.Board">
	<result property="boardid" column="BOARDID"/>
	<result property="title" column="TITLE"/>
	<result property="content" column="CONTENT"/>
	<collection property="comments" column="BOARDID" javaType="java.util.ArrayList" ofType="net.krespo.mybatis.Comment" select="getCommentListById"/>
</resultMap>

<select id="getBoardById" resultMap="boardResult">
	SELECT boardid, title, content FROM board WHERE boardid = #{boardid}
</select>
<select id="getCommentListById" resultType="net.krespo.mybatis.Comment">
	SELECT commentid, boardid, writer, content FROM comment WHERE boardid = #{boardid}
</select>
위와같이 getBoardById라는 쿼리는 게시글을 읽어올때 실행되는 쿼리이다. 이때 getBoardByIdresultMap으로 boardResult를 지정하고 있고 boardResultcollection 선언을 통해(select="getCommentListByBoardId" 쿼리를 실행할때 parameter는 column으로 선언된 boardid를 가지고) 하위 댓글 리스트를 가져온다. 만약 댓글을 가져올때 자기가 쓴글에 자기가 쓴 댓글을 가져오려면 어떻게 해야할까?  그럴때는 boardid와 writer를 getCommentListById로 넘겨주어야 한다. 이처럼 여러개의 파라미터를 collection에서 넘길때는
column="{prop1=COLUMN1, prop2=COLUMN2}"
로 쓰면된다. . 즉
<collection property="comments" column="{boardid=BOARDID,writer=WRITER}" javaType="java.util.ArrayList" ofType="net.krespo.mybatis.Comment" select="getMyCommentListById"/>

<select id="getMyCommentListById" parameterType="java.util.Map" resultType="net.krespo.mybatis.Comment">
	SELECT id, writer, content FROM comment WHERE boardid = #{boardid} AND writer = #{writer}
</select>
로 쓰면 된다. 이때 select문에서 사용하는 파라미터명과 collection에서 선언한 column의 prop1, prop2명이 반드시 동일해야 한다.(column="{prop1=COLUMN1, prop2=COLUMN2}", #{prop1}, #{prop2}) 그리고 또 반드시 주의 해야 할점은 파라미터가 한개만 전달할때는(맨 첫번째 예제인 게시글에 댓글을 가져올때) getCommentListById에 parameterType을 지정하지 않아도 됐었다. 그러나 위와 같이 collection으로 파라미터를 여러개 전달해야 할때는 반드시 parameterType="java.util.Map"을 지정해 줘야 한다. 그렇지 않으면 다음과 같은 에러를 내뿜는다.
Caused by: org.apache.ibatis.reflection.ReflectionException: There is no setter for property named 'boardid' in 'class java.lang.Object'

MyBatis - MyBatis를 사용하자 (MyBatis 초기 설정)

Published on: 2011. 1. 8. 02:50 by louis.dev


기존의 아파치프로젝트로 존재하던 iBatis가 2.xx 버전에서 3.xx로 버전을 업그레이드 뒤, 아파치 프로젝트에서 코드 구글로 이동하면서 이름도 MyBatis로 변경되었습니다. 제가 직접 사용해 본 결과 iBatis와 비교해 보았을때 많은 부분이 변경되어 튜토리얼을 작성하게 되었습니다. ( 기존에 ibatis를 많이 사용해 보셨던 분이라면 무난하게 세팅하고 사용하실수 있을테지만 처음 접하는 분들을 위해 초기설정을 해보도록 하겠습니다.)

저는 eclipse와 mysql 5.5를 통해 튜토리얼을 진행해 보겠습니다.

작업방법
1. MyBatis 라이브러리와 Mysql connector library를 다운받습니다.
자바 프로그래밍의 시작은 라이브러리의 추가입니다.(ㅎㅎ) 다음의 라이브러리를 다운 받습니다.
- mybatis-3.0.4 [다운로드]
- mysql-java-connector-5.1.14 [다운로드

2. eclipse에서 JavaProject를 생성합니다.
저는 간단하게 MyBatis만을 세팅하고 DB의 데이터를 긁어와 콘솔창에 뿌려주는 테스트를 할것임으로, Java Project를 생성하겠습니다. 목적에 따라서 Dynamic Web Project를 하셔도 상관이 없습니다.

3. 해당 프로젝트를 생성후 build path에 mybatis 라이브러리와 mysql connector 라이브러리를 추가해 줍니다.
위에서 다운받은 두개의 파일을 압축푸시면 jar파일이 나옵니다. 해당 jar파일을 build path에 추가해 줍니다.

4. 테이블을 생성합니다.
간단하게 테스트 하기 위해서 다음과 같은 쿼리로 테이블을 생성했습니다.

CREATE TABLE `mybatis` (
  `idx` int(10) NOT NULL AUTO_INCREMENT,
  `title` text,
  `contents` text,
  PRIMARY KEY (`idx`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;



5. MyBatis 세팅파일인 Configuration.xml파일을 생성합니다.
net.mybatis.tutorial.conf 패키지를 생성한후 아래와 같이 Configuration.xml 파일을 생성했습니다.

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Valid 한 설정 xml을 만들기 위해 DTD를 추가해 줍니다. -->
<!DOCTYPE configuration
	PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
	"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--
		properties : 해당위치의 properties파일을 참조하여 현재 xml내에서 
		${name} 으로 값을 사용할 수 있습니다.
		즉 properties에 선언된 db.properties에 username=xxxxx 라고 설정되어 있으면
		아래의 <property name="username" value="${username}"/> 부분의 value에 xxxx가
		치환되어 들어갑니다.
	-->
	<properties resource="net/mybatis/tutorial/conf/db.properties"/>
	<!--
		SQL이 선언되어 있는 xml( ibatis에서는 SqlMap이라고 불렀지만, myBatis에서는 
		mapper 라고 불리고 있습니다)에서 사용할 VO객체의 별명을 붙여 줍니다.

		기존의 ibatis에서는 해당설정이 SqlMap쪽에 존재 했던 반면 myBatis에서는 설정쪽으로
		이동한 것이 달라진 점이라 하겠네요~^^ 
	-->
	<typeAliases>
		<typeAlias alias="Mybatis" type="net.mybatis.tutorial.vo.MybatisVO"/>
	</typeAliases>
	<!-- 
		DB Connection을 맺기 위한 정보들을 입력해 줍니다. ${}는 위에 선언한 properties의 
		데이터로 치환됩니다.

		이부분에서 보아야 할부분은 dataSource 부분입니다. 지금은 type="POOLED"라고 설정되어
		있습니다. 이 설정은 Connection pool을 사용하겠다는 뜻입니다.
		이부분의 설정은 좀더 있지만 나중에 알아보도록 하겠습니다.
	-->
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>
		</environment>
	</environments>
	<!-- 
		위에서도 말했듯 mapper.xml은 실질적으로 query문이 선언되어 있는 부분입니다.
	-->
	<mappers>
		<mapper resource="net/mybatis/tutorial/mapper/MybatisMapper.xml" />
	</mappers>
</configuration>


6. properties 파일을 생성합니다.
위에서도 설정하였듯 해당 위치에 db.properties파일을 생성합니다.
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db이름
username=아이디
password=비밀번호

자신의 환경에 맞게 다음과 같이 설정합니다.

7. 실질적인 query문이 선언되어 있는 mapper.xml을 만들어 보겠습니다.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
	PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 
	root 엘리먼트 이름은 mapper입니다. 
	ibatis에서는 SqlMap이었습니다.
	namespace는 지정을 해도 되고 안해도 됩니다. ibatis에서는 네임스페이스 사용여부를
	설정에서 명시적으로 선언해줘야 했지만, myBatis에서는 따로 설정할 필요가 없이, namespace attribute
	가 없으면 네임스페이스 안쓰는걸로, 있으면 쓰는걸로 자동으로 되는듯 싶습니다.
-->
<mapper namespace="net.mybatis.tutorial.mapper.MyBatisMapper">
	<!--
		select 태그를 사용하여 select 쿼리를 만듭니다.
		parameterType: iBatis의 parameterClass의 역할로 전달 인자의 타입을 선언합니다
		resultType : iBatis의 resultClass와 같은 역할로 처음 선언했던 Configuration.xml의 
		typeAlias를 나타냅니다.
	-->
	<select id="selectMybatis" parameterType="int" resultType="Mybatis">
		select * from mybatis where idx = #{idx}
	</select>
</mapper>


8. query를 실행하기 위해 myBatis의 핵심 인스턴스인 SqlSessionFactory 인스턴스를 생성하는 Util클래스를 작성합니다.
iBatis의 SqlMapClient클래스가 있다면 MyBatis에는 SqlSessionFactory라는 클래스가 있습니다. query를 실행하기 위해서는 SqlSessionFactory를 통해 SqlSession 객체를 생성하여 query를 실행 할 수 있습니다.

public class SqlSessionManager { public static SqlSessionFactory sqlSession; static { String resource = "net/mybatis/tutorial/conf/Configuration.xml"; Reader reader; try { reader = Resources.getResourceAsReader( resource ); sqlSession = new SqlSessionFactoryBuilder().build( reader ); } catch (IOException e) { e.printStackTrace(); } } public static SqlSessionFactory getSqlSession() { return sqlSession; } }



위와같이 Configuration.xml 을 읽어 SqlSessionFactory 인스턴스를 생성합니다. myBatis는 SqlSessionFactory를 싱글톤 패턴으로 단 하나의 인스턴스만 생성하게 해줍니다

9. DB에 접근할 DAO클래스를 생성합니다.
SqlSessionFactory를 통해 SqlSession을 얻어와서 SqlSession을 통해 query문을 실행합니다.
selectOne는 select query를 실행하여 하나의 Object를 가져오는 method입니다.
쿼리의 선택은 selectOne 첫번째 전달인자로 선택합니다. 첫번째 전달인자는 "mapper xml의 namespace" + "query id" 입니다. 두번째 전달인자는 해당 쿼리로 전달될 값입니다.
public class MybatisDAO {
	private SqlSessionFactory sqlSessionFactory = SqlSessionManager.getSqlSession();
	public MybatisVO getMyBatis( int id ){
		SqlSession session = sqlSessionFactory.openSession();
		try {
			MybatisVO mybatis = ( MybatisVO ) session.selectOne( "net.mybatis.tutorial.mapper.MyBatisMapper.selectMybatis" , id );
			return mybatis;
		} finally {
			session.close();
		}
	}
}


테스트케이스를 작성하신후 junit으로 실행을 하시던, main method를 하나 만들어 테스트를 하시던 데이터를 잘 가져오는것을 확인 할 수 있습니다.

위와 같은 설정이 default입니다.

위의 5, 7번 설정과 같이 Configuration.xml, mapper.xml 과 같이 설정과 query문을 xml로 선언합니다. 하지만 프레임웍을 처음 써보시는 분이나, xml설정방법이 익숙하지 않으신 분들, 그리고 저렇게 복잡한 설정을 거치지 않아도 되는 쿼리를 실행해야 하는 분들은 5, 7번 단계를 only java로 대체할수가 있습니다

1. only java로 설정하실 분들은 5번 단계를 뛰어넘고 8번 단계에서 다음과 같은 class를 작성합니다.

public class SqlSessionManager {
	public static SqlSessionFactory sqlSession;
	static {
		DataSource dataSource = new PooledDataSource("com.mysql.jdbc.Driver","jdbc:mysql://localhost:3306/디비이름","아이디","비밀번호");
		TransactionFactory transactionFactory = new JdbcTransactionFactory();
		Environment environment = new Environment( "development", transactionFactory, dataSource );
		Configuration configuration = new Configuration( environment );
		//아래의 2번에 설정된 인터페이스를 지정합니다.
		configuration.addMapper( MybatisMapper.class );
		sqlSession = new SqlSessionFactoryBuilder().build( configuration );
	}
	public static SqlSessionFactory getSqlSession() {
		return sqlSession;
	}
}


2. mapper.xml도 xml대신 java interface로 대체합니다.
아래와 같이 어노테이션을 통해 간단한 쿼리 문을 대체 합니다.
public interface MybatisMapper {
	@Select("SELECT * FROM mybatis WHERE idx = #{idx}")
	MybatisVO getMybatis( int id );
}


3. dao를 작성합니다.
public class MybatisDAO {
	private SqlSessionFactory sqlSessionFactory = SqlSessionManager.getSqlSession();
	public MybatisVO getMyBatis( int id ){
		SqlSession session = sqlSessionFactory.openSession();
		try {
			MybatisMapper mapper = session.getMapper( MybatisMapper.class );
			MybatisVO mybatis = mapper.getMybatis( id );
			return mybatis;
		} finally {
			session.close();
		}
	}
}


위와 같이 작성하시면 설정 xml없이 myBatis를 사용할수 있습니다.

이상으로 myBatis의 기본설정을 알아봤습니다.

다음에 기회가 된다면 좀더 알아봐야 겠네요~^^

iBATIS 인 액션
국내도서>컴퓨터/인터넷
저자 : 브랜든구딘,클린턴비긴,래리메도스,손권남 / 이동국역
출판 : 위키북스 2007.05.22
상세보기