Joswlv

mybatis 개론

2016-09-18
web

1. mybatis소개

퍼시스턴스(Persistence)프레임워크

퍼시스턴스라는 용어는 개발 분야에서 많이 사용하는 용어이다. 데이터의 지속성을 의마하는 용어로 어플리케이션을 종료하고 다시 실행하더라도 이전에 저장한 데이터를 다시 불러올 수 있는 기술이다.

퍼시스턴스 프레임워크는 데이터의 저장, 조회, 변경, 삭제를 다루는 클래스 및 설정 파일들의 집합니다. 이를 사용하면 JDBC 프로그래밍의 복잡함이나 번거로움 없이 간단한 작업 만으로 데이터베이스와 연동되는 시스템을 빠르고 안전적으로 구동되게 개발할 수 있다.

퍼시스턴스 프레임워크에는 SQL문장으로 직접 DB데이터를 다루는 SQL맵퍼(mapper)와 자바객체를 통해 간접적으로 DB데이터를 다루는 객체 관계 맵퍼(Object-Relational mapper)가 있다.

SQL맵퍼에 대표적으로 `mybatis가 있고, 객체 관계 맵퍼에는 Hibernate와 TopLink가 있다.

객체관계맵퍼는 프레임워크에서 제공하는 API와 전용 객체 질의어를 사용하여 데이터를 다룬다. 그래서 이를 사용할려면 데이터베이스의 정규화가 잘돼 있어야 한다. 그래야만 테이블을 객체와 연결하기 쉽고, 객체를 통해 테이블의 데이터를 다루기가 쉽다. 근데, 유지보수 단계에 들어가면 잦은 기는 변경과 추가로 데이터베이스구조가 흐트러지게 되어 실무에서는 잘 사용하지 않는다.

mybatis의 핵심은 개발과 유지보수가 쉽도록 소스코드에 박혀있는 SQL을 별도의 파일로 분리하는 것이다. 또 단순하고 반복적인 JDBC코드를 캡슐화하여 데이터베이스 프로그래밍을 간결하게 하는것이다.

라이브러리 파일은 http://github.com/mybatis에서 mybatis-3로 가서 다운 받는다.


2. mybatis 구동하기

mybatis를 이용한 프로젝트 목록 가져오기 시나리오

  1. MySqlProjectDao는 SqlSessionFactory에게 SQL을 실행할 객체를 요구한다.
  2. SqlSessionFactory는 SqlSession 객체를 생성하여 반환한다.
  3. MySqlProjectDao는 SqlSession객체에게 SQL 실행을 요청한다.
  4. SqlSession객체는 SQL이 저장된 맵퍼 파일에서 SQL을 찾는다.
  5. SqlSession은 JDBC드라이버를 통해 데이터베이스에 질의를 실행한다.
  6. SqlSession은 데이터베이스로부터 가져온 데이터로 Project목록을 생성하여 반환한다.
  7. MySqlProjectDao는 사용이 끝난 SqlSession을 닫는다.

mybatis 프레임워크의 핵심 컴포넌트

컴포넌트 설명
SqlSession 실제 SQL을 실행하는 객체. 이 객체는 SQL을 처리하기 위해 JDBC 드라이버를 사용함
SqlSessionFactory SqlSession 객체를 생성함
SqlSessionFactoryBuilder mysql설정 파일의 내용을 토대로 SqlSessionFactory를 생성함.
mybatis설정파일 데이터베이스 연결정보, 트갠잭션정보, mybatis제어 정보 등의 설정 내용을 포함하고 있음. SqlSessionFactory를 만들 때 사용됨.
SQL 맵퍼 파일 SQL문을 담고 있는 파일. SqlSession객체가 참조함.


SqlSession의 주요 메서드

메서드 설명
selectList() SELECT문을 실행. 값객체(Value Object) 목록을 반환함
selectOne() SELECT문을 실행. 하나의 값 객체를 반환함
insert() INSERT문을 실행. 반환값은 입력 데이터 개수.
update() UPDATE문을 실행. 반환값은 변경한 데이터 개수.
delete() DELETE문을 실행. 반환값은 삭제한 데이터 개수.


예>

@Component("projectDao")
public class MySqlProjectDao implements ProjectDao {
  SqlSessionFactory sqlSessionFactory;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
  }

  public List<Project> selectList(HashMap<String,Object> paramMap) 
  		throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
      return sqlSession.selectList("spms.dao.ProjectDao.selectList", paramMap);
    } finally {
      sqlSession.close();
    }
  }

  public int insert(Project project) throws Exception  {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
      int count = sqlSession.insert("spms.dao.ProjectDao.insert", project);
      sqlSession.commit();
      return count;
    } finally {
      sqlSession.close();
    }
  }
  
  public Project selectOne(int no) throws Exception { 
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
      return sqlSession.selectOne("spms.dao.ProjectDao.selectOne", no);
    } finally {
      sqlSession.close();
    }
  }
  ....
  
//MySqlProjectDao.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="spms.dao.ProjectDao">
  <resultMap type="project" id="projectResultMap">
    <id column="PNO" property="no"/>
    <result column="PNAME"    property="title"/>
    <result column="CONTENT"  property="content"/>
    <result column="STA_DATE" property="startDate" javaType="java.sql.Date"/>
    <result column="END_DATE" property="endDate" javaType="java.sql.Date"/>
    <result column="STATE"    property="state"/>
    <result column="CRE_DATE" property="createdDate" javaType="java.sql.Date"/>
    <result column="TAGS"     property="tags"/>
  </resultMap>
  
  <select id="selectList" parameterType="map" resultMap="projectResultMap">
    select PNO, PNAME, STA_DATE, END_DATE, STATE
    from PROJECTS
    order by
    <choose> 
        <when test="orderCond == 'TITLE_ASC'">PNAME asc</when>
        <when test="orderCond == 'TITLE_DESC'">PNAME desc</when>
        <when test="orderCond == 'STARTDATE_ASC'">STA_DATE asc</when>
        <when test="orderCond == 'STARTDATE_DESC'">STA_DATE desc</when>
        <when test="orderCond == 'ENDDATE_ASC'">END_DATE asc</when>
        <when test="orderCond == 'ENDDATE_DESC'">END_DATE desc</when>
        <when test="orderCond == 'STATE_ASC'">STATE asc</when>
        <when test="orderCond == 'STATE_DESC'">STATE desc</when>
        <when test="orderCond == 'PNO_ASC'">PNO asc</when>
        <otherwise>PNO desc</otherwise>
    </choose>
  </select>
  
  <insert id="insert" parameterType="project">
    insert into PROJECTS(PNAME,CONTENT,STA_DATE,END_DATE,STATE,CRE_DATE,TAGS)
    values (#{title},#{content},#{startDate},#{endDate},0,now(),#{tags})
  </insert>
  
  <select id="selectOne" parameterType="int" resultMap="projectResultMap">
    select PNO, PNAME, CONTENT, STA_DATE, END_DATE, STATE, CRE_DATE, TAGS
    from PROJECTS 
    where PNO=#{value}
  </select>
  
  ...
  
package spms.listeners;

// SqlSessionFactory 객체 준비
@WebListener
public class ContextLoaderListener implements ServletContextListener {
  static ApplicationContext applicationContext;
  
  public static ApplicationContext getApplicationContext() {
    return applicationContext;
  }
   
  @Override
  public void contextInitialized(ServletContextEvent event) {
    try {
      applicationContext = new ApplicationContext();
      
      String resource = "spms/dao/mybatis-config.xml";
      InputStream inputStream = Resources.getResourceAsStream(resource);
      SqlSessionFactory sqlSessionFactory = 
          new SqlSessionFactoryBuilder().build(inputStream);	
      //SqlSessionFactory객체를 mybatis설정파일(설계도)를 통해 빌드 시킴
      //이런식의 객체 생성 패턴을 빌더 패턴(Builder Pattern)이라고 한다.
      
      applicationContext.addBean("sqlSessionFactory", sqlSessionFactory);
      
      ServletContext sc = event.getServletContext();
      String propertiesPath = sc.getRealPath(
          sc.getInitParameter("contextConfigLocation"));
      
      applicationContext.prepareObjectsByProperties(propertiesPath);
      
      applicationContext.prepareObjectsByAnnotation("");
      
      applicationContext.injectDependency();
      
    } catch(Throwable e) {
      e.printStackTrace();
    }
  }
  
  @Override
  public void contextDestroyed(ServletContextEvent event) {}
}
//mybatis 설정파일
<?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>
  <settings>
    <setting name="logImpl" value="LOG4J"/>
  </settings>
      
  <typeAliases>
    <typeAlias type="spms.vo.Project" alias="project"/>
    <typeAlias type="spms.vo.Member" alias="member"/>
  </typeAliases>
  
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="JNDI">
        <property name="data_source" value="java:comp/env/jdbc/studydb"/>
      </dataSource>
    </environment>
  </environments>
  
  <mappers>
    <mapper resource="spms/dao/MySqlProjectDao.xml"/>
    <mapper resource="spms/dao/MySqlMemberDao.xml"/>
  </mappers>
</configuration>


commit()과 rollback()메서드

DBMS는 INSERT, UPDATE, DELETE문을 실행하면 그 작업 결과를 임시 데이터베이스에 보관한다. 클라이언트 요청이 있어야만 임시 데이터베이스의 작업물을 운영 데이터베이스에 반영한다.

commit()은 임시 데이터베이스에 보관된 작업 결과를 운영 데이터베이스에 적용하라고 요청할 때 사용하는 메서드이다.

rollback()은 임시 데이터베이스의 작업 결과를 운영 데이터베이스에 반영하지 않고 취소할 때 호출한다.

SQL맵퍼 파일

DTD선언

<?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> 루트 엘리먼트

SQL 맵퍼 파일은 루트 엘리먼트 <mapper>를 작성하는 것으로 시작한다. namespace속성은 자바의 패키지처럼 SQL문을 묶는 용도로 사용한다.

<mapper namespace ="spms.dao.ProjectDao">

...

</mapper>

<select>, <insert>, <update>, <delete> 엘리먼트

SQL문을 작성할 때 명령어에 맞는 태그를 사용한다.

id속성 -> SQL문을 작성할때 각각의 SQL문을 구분하기 위해 id 속성을 사용한다.

resultType속성 -> select문을 실행하면 결과가 생성되는데, 이 결과를 담을 객체를 지정하는 속성이 resultType속성이다. 값으로는 클래스 이름(패키지 이름 포함)이 온다.

<select id="selectList" resultType="spms.vo.Project">

만약 mybatis 설정 파일에 다음과 같이 spms.vo.Project에 대한 별명이 정의 되여 있다면 resultType의 값으로 그 별명을 사용할 수 있다.

//mybatis설정 파일의 일부분
  <typeAliases>
    <typeAlias type="spms.vo.Project" alias="project"/>
    <typeAlias type="spms.vo.Member" alias="member"/>
  </typeAliases>
<select id="selectList" resultType="project">


위 방법에 약간의 번거러움이 있다. mybatis는 SELECT 결과를 저장하고자 resultTpye에 선언된 클래스의 인스턴스를 생성한다. 그리고 각 칼럼에 대응하는 값객체의 셋터 메서드를 찾아서 호출한다. 이때 셋터메서드는 대소문자 구분없이 set을 뺀 메서드의 이름이과 칼럼의 이름이 같아야지만 인스턴스가 생성된다.

만약 칼럼의 이름과 일치하는 셋터가 없다면, 해당 칼럼의 값은 인스턴스에 저장되지 않는다. 이런 점이 매우 번거럽다. 이를 해결하기 위해 다음 엘리먼트를 사용한다.


<resultMap> 엘리먼트

다음과 같이 <resultMap>태그에 칼럼과 연결될 값객체의 셋터 메서드를 정의한다.

  <resultMap type="project" id="projectResultMap">
    <id column="PNO" property="no"/>
    <result column="PNAME"    property="title"/>
    <result column="CONTENT"  property="content"/>
    <result column="STA_DATE" property="startDate" javaType="java.sql.Date"/>
    <result column="END_DATE" property="endDate" javaType="java.sql.Date"/>
    <result column="STATE"    property="state"/>
    <result column="CRE_DATE" property="createdDate" javaType="java.sql.Date"/>
    <result column="TAGS"     property="tags"/>
  </resultMap>

`태그의 type 값은 칼럼 데이터를 저장할 클래스 이름 또는 클래스의 별명이다. 클래스에 대한 별명은 mybatis설정파일에 정의 했다. 앞의 소스에서는 type 값을 project라고 하였는데, spms.vo.Project 클래스를 가리키는 별명이다.

<result> 엘리먼트

<resultMap> 태그의 자식 태그로서 칼럼과 셋터 메서드의 연결을 정의한다. column 속성에는 칼럼 이름을 지정하고, property 속성에는 객체의 프로퍼티 이름을 지정한다.

<result column="PNAME" property="title"/>

<result>태그를 사용하여 PNAME 칼럼 값을 setTitle()메서드와 연결시킨것을 보았다.


javaType속성

<result>에서 javaType을 사용하면, 칼럼의 값을 특정 자바 객체로 변환할 수 있다. 다음과 같이 STA_DATE 칼럼에 대해 javaType을 java.sql.Date으로 설정하면, 칼럼 값을 꺼낼 때 그 객체로 변환된다.


<id> 엘리먼트

<id>태그는 <result>태그와 작성법이 같다. 다만 <id>태그에서 지정한 프로퍼티는 객체 식별자로 사용된다. SELECT문을 실행하면 레코드 값을 저장하기 위해 결과 객체가 생성되는데, SELECT문을 실행할 때마다 매번 결과 객체를 생성한다면 실행 성능이 나빠진다. 이를 해결하기위해 SELECT를 통해 생성된 결과 객체들은 별도의 보관소에 저장(캐싱, chaching)해두고 재사용한다. 이때 보관소에 저장된 객체를 구분하는 값으로 <id>에서 지정한 프로퍼티를 사용한다.

SQL문의 입력 매개변수 처리

mybatis에서는 입력 매개벼수를 #{프로퍼티명}으로 표시한다.

<insert id="insert" parameterType="project">
    insert into PROJECTS(PNAME,CONTENT,STA_DATE,END_DATE,STATE,CRE_DATE,TAGS)
    values (#{title},#{content},#{startDate},#{endDate},0,now(),#{tags})
  </insert>


#{프로퍼티명}이 가리키는 값은 <insert>의 paramerType에 지정한 객체의 프로퍼티 값(겟터 메서드의 반환값)이다. 즉 #{title} 자리에는 Project객체의 getTitle() 반환값이 놓인다.


입력매개변수에 값 공급

public int insert(Project project) throws Exception  {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
      int count = sqlSession.insert("spms.dao.ProjectDao.insert", project);
      sqlSession.commit();
      return count;
    } finally {
      sqlSession.close();
    }
  }

sqlSession.insert()를 호출하면 SQL 맵퍼 파일에서 spms.dao.ProjectDao.insert아이디를가진 SQL문을 찾아 실행한다. 물론 spms.dao.ProjectDao는 SQL 맵퍼 파일의 네임스페이스 이름을 가리키고 insert는 SQL 아이드를 가리킨다. project는 INSERT문을 실행할 때 입력 매개변수에 값을 공급할 객체이다.

만약 다음과 같이 공급하는 객체가 기본 타입 객체(랩퍼클래스,wrapper class)인 경우, 자바는 해당 타입에 대응하는 랩퍼 객체를 생성하여 자동포장 한다.

  public int delete(int no) throws Exception {  
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
      int count = sqlSession.delete("spms.dao.ProjectDao.delete", no);
      sqlSession.commit();
      return count;
    } finally {
      sqlSession.close();
    }
  }

즉 컴파일 할때 다음과 같이 변한다.

int count = sqlSEssion.delete("spms.dao.ProjectDao.delete",new Integer(no));


mybatis 설정 파일

DTD선언

<?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> 루트 엘리먼트

configuration의 주요 자식 엘리먼트

엘리먼트 용도
properties 프로퍼티 파일이 있는 경로 설정. 를 사용하여 개별 프로퍼티 정의 가능
settings 프레임워크의 실행 환경을 설정
typeAliases 자바 클래스 이름(패키지 이름 포함)에 대한 별명 설저
typeHandler 칼럼의 값을 자바 객체로, 자바 객체를 칼럼의 값으로 변환해 주는 클래스를 설정
environments 프레임워크에서 사용할 데이터베이스 정보(트랜젝션 관리자, 데이터 소스)를 설정.
mappers SQL맵퍼 파일들이 있는 경로 설정


mybatis는 기본 데이터 형(byte, short, int, long,float, double, boolean, char)이나 랩퍼클래스에 대해 미리 별명을 정의했다.

<environments> 엘리먼트

<environments>태그는 데이터베이스 환경 정보를 설정할 때 사용하는 태그이다. 이 태그를 이용하면 여러개의 데이터베이스 접속 정보를 설정할 수 있다. 설정된 DB정보 중에서 하나를 선택할 때는 default속성을 사용한다.

<environments defaul="development">
	<environment id="development">...</environment>
	<environment id="test">...</environment>
	<environment id="real">...</environment>
</environments>

각각의 데이터베이스 접속 정보는 <environment>태그를 이용하여 정의 한다. id속성은 <environment>를 구분할 때 사용하는 식별자이다.

<environment> 엘리먼트

<environment>는 트랜잭션 관리 및 데이터 소스를 설정하는 태그이다.

트랜잭션 관리 방식 설정

트랜잭션(Transaction)이란, 여러개의 데이터 변경작업을 하나의 작업으로 묶은 것이다.

트랜잭션 관리 유형 설명
JDBC 직접 JDBC의 커밋, 롤백기능을 사용하여 mybatis 자체에서 트랜잭션을 관리
MANAGED 서버의 트랜잭션 관리 기능을 이용. 즉 Java EE애플리케이션 서버(JBoss, WebLogic, WebSphere등)나 서블릿 컨테이너(톰캣서버 등)에서 트랜잭션을 관리
<transactionManager type="JDBC">


데이터소스설정

mybatis는 JDBC 표준 인터페이스인 javax.sql.DataSource구현체를 이용하여 DB 커넥션을 다룬다.

데이터소스 유형 설명
UNPOOLED DB커넥션을 요청할 때마다 매번 커넥션 객체를 생성한다. 높은 성능을 요구하지 않는 단순한 어플리케이션에 적합하다.
POOLED 미리 DB커넥션 객체를 생성해두고, 요청하면 즉시 반환한다. 데이터베이스에 연결하는 과정 즉 연결을 초기화하고 사용자를 인증하는 과정이 없기 때문에 속도가 빠르다.
JNDI Java EE 어플리케이션 서버나 서블릿 컨테이너에서 제공하는 데이터 소스를 사용한다.
//<properties>태그에서 설정된 파일을 참조해서 사용함

<dataSource type="POOLED">
	<property name="driver" value="${driver}"/>
	<property name="url" value="${url}"/>
	<property name="username" value="${username}"/>
	<property name="password" value="${password}"/>
</dataSource>
<dataSource type="JNDI">
	<property name="data_source" value="java:comp/env/jdbc/studydb"/>
</dataSource>


<mappers> 엘리먼트

<mappers>태그는 SQL 맵퍼 파일들의 정보를 설정할 때 사용한다. 각각의 SQL 맵퍼 파일의 정보는 <mapper>태그로 정의한다.

SQL맵퍼 파일의 경로를 설정할 때, 두가지 방법이 있다. 자바 클래스 경로를 사용하는 방법과 운영체제의 파일 시스템 경로를 사용하는 방법이 있다.

//클래스 경로를 사용할 경우
<mappers>
    <mapper resource="spms/dao/MySqlProjectDao.xml"/>
    <mapper resource="spms/dao/MySqlMemberDao.xml"/>
</mappers>
//파일 시스템 경로를 사용할 경우
<mappers>
	<mapper url="file:///c:/dao/MySqlProjectDao.xml"/>
</mappers>


Reference


Comments