잊지 않겠습니다.

14. iBatis (myBatis)

Java 2013. 9. 10. 10:27

* 사내 강의용으로 사용한 자료를 Blog에 공유합니다. Spring을 이용한 Web 개발에 대한 전반적인 내용에 대해서 다루고 있습니다.



ibatis (mybatis)

지금까지 직접 JDBC를 이용한 DB접속 방법과 Hibernate를 이용한 DB 접속 방법을 알아봤습니다.
DB라는 관계형 데이터를 java라는 객체 지향의 언어에 mapping을 시키는 과정은 참 여러가지 기술적인 요인들과 방법들을 계속해서 내보이고 있습니다.
그 중에서 SQL을 직접 사용하는 가장 low level의 JDBC, 그리고 SQL을 최대한 사용하지 않고 객체만으로 표현하는 Hibernate는 그 대표적인 기술이라고 할 수 있습니다. 그런데, 국내에서는 다른 기술을 더 많이 쓰고 있는 것이 사실입니다. 

iBatis(아래 부터는 myBatis)가 바로 그 기술입니다. 다른 것보다는 이 기술은 직접 sql을 사용한다는 큰 차이를 가지고 있습니다. 그렇지만, 기존에 우리가 만들었던 것과 같은 entity-dao-service layer를 충실히 지킬 수 있으며 기존의 SP와 같은 legacy sql query를 직접적으로 사용하고, code로서 관리한다는 점이 가장 큰 차이입니다.

iBatis는 JDBC를 이용한 반복적인 코드를 획기적으로 줄이는 것을 목표로 가지고 있으며, 개발자들에게 게을러질 수 있는 권리를 보장하고 있습니다. 

ibatis의 구조

Hibernate와 비슷하게 SqlSessionFactory, SqlSession이라는 두개의 객체를 가지고 있습니다. 기본적으로 mybatis.cfg.xml 파일을 이용해서 DB에 대한 연결 설정과 각각의 mapper를 구성하는 설정으로 구성되어 있습니다.

ibatis에서 DB에 대한 연결을 구성하는 방법은 다음과 같습니다. 

1. SqlSessionFactory 구성 
2. SqlSessionFactory를 통한 SqlSession을 구성
3. SqlSession을 통해 mapper를 구성

여기서 새로운 개념들이 조금 나오게 되는데요. Hibernate에서 많은 개념들을 차용해온것들이 나오게 됩니다. 

먼저, SqlSessionFactory는 이름 그대로 SessionFactory입니다. 

일단 SqlSession을 통해서 얻어지는 mapper는 기본적으로 dao 객체들입니다.

 
백문이 불여 일타. 한번 지금까지 만들어진 코드를 구성해보도록 합시다. 

기존 jdbcTemplate에서 사용한 Book, User, History 객체와 Dao에 대한 interface를 모두 카피해와서 새로운 프로젝트를 구성합니다.
myBatis는 maven central repository에 없습니다. 다음 repository 설정을 추가하고, dependency를 추가하도록 합니다. 이와 같이 maven central repository에서 지원하지 않는 library들은 자신만의 repository를 갖는 경우가 많습니다. 그리고 사내에서는 nexus라는 maven server를 설치해서, 사내 repository를 구성해서 사용하는 경우도 많습니다.


  <repositories>
    <repository>
      <id>mybatis-snapshot</id>
      <name>MyBatis Snapshot Repository</name>
      <url>https://oss.sonatype.org/content/repositories/snapshots</url>
    </repository>
  </repositories>

그 후, myBatis를 추가합니다.
   <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.1.1</version>
    </dependency>

   
myBatis에서 DB에 접근하기 위한 순서인 SqlSessionFactory를 먼저 구성해보도록 하겠습니다. 
SqlSessionFactory는 xml 파일로 설정하게 되며, 다음과 같이 설정할 수 있습니다. 

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="bookstore">
    <environment id="bookstore">
      <transactionManager type="JDBC" />
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost/bookstore" />
        <property name="username" value="root" />
        <property name="password" value="qwer12#$"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="com/xyzlast/mybatis/bookstore/dao/mappers/userDao.mapper.xml" />
    <mapper resource="com/xyzlast/mybatis/bookstore/dao/mappers/bookDao.mapper.xml" />
    <mapper resource="com/xyzlast/mybatis/bookstore/dao/mappers/historyDao.mapper.xml"/>
  </mappers>
</configuration>

먼저 myBatis는 한개의 xml에 여러개의 connection 정보들을 담을 수 있습니다. 각각의 환경에 대한 id를 설정하고, 그 환경에 대한 기본 설정을 해주게 됩니다. DataSource에서 자주 보던 driver, url, username, password에 대한 설정을 하게 되는 것을 알 수 있습니다. 그리고 mapper들을 설정해주게 됩니다. 이 mapper들은 xml로 구성되어 있으며, 1 개의 method에 1:1로 mapping되는 SQL query가 들어가 있습니다. 일반적으로 interface 이름 + mapper.xml 의 명명 규칙을 따르게 됩니다.

다음은 bookDao.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">
<mapper namespace="com.xyzlast.mybatis.bookstore.dao.BookDao">
  <resultMap type="com.xyzlast.mybatis.bookstore.entities.Book" id="BookResult">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="author" column="author"/>
    <result property="publishDate" column="publishDate"/>
    <result property="rentUserId" column="rentUserId"/>
    <result property="status" column="status" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
  </resultMap>

  <select id="get" parameterType="int" resultMap="BookResult">
    SELECT * FROM books WHERE id = #{bookId}
  </select>
  <select id="getAll" resultMap="BookResult">
    SELECT * FROM books
  </select>
  <select id="countAll" resultType="int">
    SELECT count(*) FROM books
  </select>
  <insert id="add" parameterType="com.xyzlast.mybatis.bookstore.entities.Book" useGeneratedKeys="true" keyColumn="id">
    INSERT INTO books(name, author, publishDate, comment, status, rentUserId)
    VALUES(#{name}, #{author}, #{publishDate}, #{comment}, #{status.value}, #{rentUserId})
  </insert>
  <delete id="delete" parameterType="int">
    DELETE FROM books WHERE id = #{bookId}
  </delete>
  <delete id="deleteAll">
    DELETE FROM books
  </delete>
  <update id="update" parameterType="com.xyzlast.mybatis.bookstore.entities.Book">
    UPDATE books SET
    name = #{name}, author = #{author}, publishDate=#{publishDate}, status=#{status.value}, rentUserId=#{rentUserId}
    WHERE id = #{id}
  </update>
  <select id="search" parameterType="String" resultMap="BookResult">
    SELECT * FROM books
    WHERE name like "%${value}%"
  </select>
</mapper>


먼저, 가장 주시해서 봐야지 되는 것은 mapper의 namespace입니다. namespace에는 interface에 대한 package명이 포함된 full name이 들어가야지 됩니다. 이 부분에 대한 설정이 잘못 되어 있으면 사용할 수 없습니다. 그 다음으로 봐야지 될 것은 resultMap입니다. return되는 query문의 결과가 어떤 DTO/VO 객체에 어떤 property에 mapping되는지에 대한 설정이 여기에 기록이 됩니다. 재미있는 것이 BookStatus enum값의 mapping입니다. org.apache.ibatis.type.EnumOrdinalTypehandler를 이용해서 enum값과 BookStatus를 mapping시킬 수 있습니다. 

BookDao interface와 mapping.xml 파일을 한번 비교해보도록 하겠습니다.




query를 각각 type에 맞추어 select/insert/delete/update로 나눠 등록을 하게 됩니다. 또한 query의 id는 각 method의 이름과 1:1로 mapping이 되게 됩니다. myBatis를 이용하는 경우에는 interface에 대한 객체를 따로 만들지 않기 때문에 코딩양이 줄어 들 수 있습니다. 또한, sql query를 project에서 관리하고 있기 때문에 query에 대한 관리 역시 용의한 장점을 가지고 있습니다. 

이제 SqlSessionFactory를 얻어내는 과정은 모두 완료되었습니다. SqlSessionFactory는 application에서 딱 1개만 존재하면 됩니다. 이제 이 SqlSessionFactory에서 SqlSession을 얻어오는 과정은 Hibernate에서 SessionFactory를 얻어내는 과정과 완전히 동일합니다. 다음은 BookDao의 테스트 코드의 일부입니다. 

public class BookDaoTest {
    private SqlSession session;
    private BookDao bookDao;
    private SqlSessionFactoryGenerator sqlSessionFactoryGenerator;

    @Before
    public void setUp() throws IOException {
        sqlSessionFactoryGenerator = new SqlSessionFactoryGenerator();
        sqlSessionFactoryGenerator.setXmlFilename("mybatis.xml");
        SqlSessionFactory factory = sqlSessionFactoryGenerator.getSqlSessionFactory();

        session = factory.openSession();
        bookDao = session.getMapper(BookDao.class);
        bookDao.deleteAll();
        session.commit();
        assertThat(bookDao.countAll(), is(0));
    }

    @After
    public void tearDown() {
        session.close();
    }

    @Test
    public void add() {
        int count = bookDao.countAll();
        List<Book> books = getBooks();
        for(Book book : books) {
            bookDao.add(book);
            session.commit();
            count++;
            assertThat(bookDao.countAll(), is(count));
        }
    }

SqlSessionFactory를 통해, SqlSession을 얻어내고 update/delete/insert에 대한 commit을 직접 행하도록 코드를 작성했습니다. 또한 모든 method가 완료되면 Hibernate와 동일하게, 반드시 Session을 닫아줘야지만 됩니다. 

Hibernate는 객체에 대한 xml 설정 또는 annotation 설정을 하는데 반하여, myBatis는 action에 설정을 하는 것을 주목해주세요. 이 둘의 차이는 매우 큰 차이를 가지고 오게 됩니다. 그리고, 지금 객체를 entities package에서 얻어오게 되었지만, 실질적으로 이 객체는 DTO또는 VO 객체가 됩니다. DB에서 값을 가지고 오는 역활만을 담당하는 객체로 보고 개발을 진행하는 것이 좋습니다. 


Summary

myBatis를 이용한 DAO 객체에 대해서 알아봤습니다. 지금까지 구성한 서비스까지 한번 구현해보세요. 이번에는 Spring을 사용하지 않고 구현하는 것이 목표입니다. 다음 장에서는 Spring을 이용해서 더욱더 간단하게 myBatis의 설정을 구축하는 것을 보여드리도록 하겠습니다. 





Posted by Y2K
,