Mybatis

2019-02-18

MyBatis

MyBatis는 자바오브젝트와 SQL 문 사이의 자동매핑 기능을 지원하는 ORM 프레임워크 입니다. MyBatis는 코드 내에서 자바오브젝트만을 이용해 데이터 로직을 작성할 수 있게 해주고, SQL을 별도의 파일로 분리해서 관리하게 해주며, 오브젝트-SQL사이의 파라미터 매핑 작업을 자동으로 해주기 때문에 많은 인기를 얻고 있는 기술입니다. MyBatis는 본격적인 ORM인 JPA나 하이버네이트처럼 새로운 DB 프로그래밍 패러다임을 익혀야 하는 부담이 없습니다. 대부분의 개발자가 이미 익숙한 SQL을 그대로 이용할 수 있으면서도 JDBC 코드 작성의 불편함을 제거해주고, 도메인 오브젝트나 DTO를 중심으로 개발이 가능한다는 장점이 있습니다. MyBatis의 가장 큰 특징은 SQL을 자바 코드에서 분리해서 별도의 XML 파일 안에 작성하고 관리할 수 있다는 점입니다. 따라서 SQL에 변경이 있을 때 마다 자바 코드를 수정하고 컴파일하지 않아도 됩니다. 또 SQL의 작성과 관리 또는 검토를 DBA와 같은 개발자가 아닌 사람에게 손쉽게 맡길 수도 있습니다. XML에 담긴 SQL과 자바오브젝트 사이의 매핑은 이름치환자와 빈 프로퍼티 사이의 매핑을 이용합니다. 이런 매핑 기능은 스프링 JDBC에서도 지원합니다. SQL을 외부 리소스 파일에서 가져올 수 있다는 점을 제외하면 MyBatis가 제공해주는 많은 SQL-오브젝트 매핑 기능은 스프링 JDBC에서도 지원됩니다. 단순히 SQL-오브젝트 자동매핑 기능을 이용하고 번거로운 JDBC API의 사용을 피하고 싶을 뿐이라면 굳이 MyBatis를 새롭게 학습하고 적용하는 대신 스프링 JDBC를 이용하는 것으로 충분합니다. 하지만 MyBatis는 단순한 SQL 분리와 자동 매핑 이상의 많은 기능을 제공해주는 고급 프레임워크입니다. 따라서 본격적인 SQL 매핑 기법을 활용하고 MyBatis가 제공하는 고급 기능을 사용하고 싶다면 MyBatis를 사용하면 됩니다.

다음은 JDBC를 이용한 DAO 클래스의 일부분이다.


public int delete(String email){

    try {
    conn = getConnection();
    pstmt = conn.prepareStatement("delete from member_csb where email=?");
    pstmt.setString(1, email);
    return pstmt.executeUpdate();
    } catch (Exception e) {
        // TODO: handle exception
    } finally {
         closeDBResources(rs, stmt, pstmt, conn);
    }
    return 0;
}

public MemberDTO info(String email) {

    try {
    conn = getConnection();
    MemberDTO member = new MemberDTO();
    stmt = conn.createStatement();
    rs = stmt.executeQuery("select * from member_csb " + 
            "where email='" + email + "'");

    if(rs.next()) {
        member = new MemberDTO();
        member.setEmail(rs.getString(1));
        //member.setPw(rs.getString(2));
        member.setName(rs.getString(3));
        member.setPhone(rs.getString(4));
    }
    return member;	
    } catch (Exception e) {
        // TODO: handle exception
    }finally {
         closeDBResources(rs, stmt, pstmt, conn);
    }
    return null;
}

public int update(MemberDTO member){

    try {
    conn = getConnection();
    pstmt = conn.prepareStatement("update member_csb set name=?, phone=? where email=?");
    pstmt.setString(1, member.getName());
    pstmt.setString(2, member.getPhone());
    pstmt.setString(3, member.getEmail());
    return  pstmt.executeUpdate();
    } catch (Exception e) {
        // TODO: handle exception
    } finally {
         closeDBResources(rs, stmt, pstmt, conn);
    }
    return 0;
}

딱 봐도 반복적인 코드가 많아서 시간이 낭비되거나 , 간혹 간단한 한 줄을 빼먹어서 오류가 발생되는 것을 볼 수 있습니다. 이러한 반복적인 코드를 줄이기 위해서 SQL을 맵핑해주는 프레임워크를 사용합니다.

준비하기

환경

  • Spring version : 4.3.2
  • JDK : 1.8
  • Junit4
  • 데이터베이스 : mysql
  • 기본패키지 : org.mybatis.tutorial

데이터베이스

member 테이블을 하나 만들어서 no , id , pw 3개의 컬럼으로 이루어진 간단한 데이터베이스를 만들어 연습 해볼것입니다. 그럼 member 테이블을 다음과 같이 만들어주세요.

CREATE TABLE `member` (
  `no` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `id` varchar(255) DEFAULT NULL,
  `pw` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`no`)
) 

의존 라이브러리

MyBatis에서 JDBC를 이용하므로 spring-jdbc 라이브러리가 사전에 미리 있어야 합니다.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${org.springframework-version}</version>
</dependency>

mysql 연결을 위한 mysql-connector 도 필요합니다.

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>

Junit test를 하기 위해 spring-test가 필요합니다.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${org.springframework-version}</version>
    <scope>test</scope>
</dependency>

시작하기

먼저 MyBatis를 사용하기 위해서는 jar 파일이 존재해야 하는데, Maven을 이용한다면 pom.xml에

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

다음 라이브러리를 추가해주세요. 저는 3.4.0 버전을 사용했습니다.

먼저 데이터베이스 연결을 위한 dataSource를 XML 파일에 빈으로 등록해보겠습니다.. 스프링 프로젝트 생성시 기본적으로 생성되는 root-context.xml에 선언을 해주면 됩니다.

root-context.xml

<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName"
        value="com.mysql.jdbc.Driver" />
    <property name="url"
        value="jdbc:mysql://localhost:3306/mybatis"/>
    <property name="username" value="root" />
    <property name="password" value="1234" />
</bean>

데이터베이스는 여러분이 원하는 이름을 사용하면 됩니다. 저는 mybatis라는 이름을 사용하도록 하겠습니다.

이제 mybatis를 사용하기 위한 Bean을 생성해 줄거예요. 똑같은 파일 root-context.xml에 다음과 같이 작성하면 됩니다.

root-context.xml

<bean id="sqlSession"
    class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="mapperLocations"
        value="mapper위치는 추후에 작성예정" />
</bean>

<bean id="sqlSessionTemplate"
    class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSession" />
</bean>

mybatis를 사용하기 위해서는 데이터베이스에 접근을 위해 데이터베이스의 내용을 알고 있어야하는데, 우리는 방금 위에서 dataSource로 선언해 줬습니다. mapperLocations는 이제 우리가 SQL을 작성할 XML 파일들의 위치를 작성해주면 됩니다. XML위치는 아직 XML을 작성하지 않았으므로 추후에 작성해주도록 하겠습니다.

지금까지의 파일들은 https://github.com/DaeAkin/Mybatis-tutorial/tree/setUp 여기에 있습니다.

이제 MVC패턴을 이용해 클래스를 작성해 나갈 것인데요, 여기서 V와 C는 사용하지 않고, M만 작성해서 테스트를 해보겠습니다.

MemberDao와 MemberDto를 작성해보겠습니다.

MemberDto

package com.mybatis.tutorial;

public class MemberDto {

}

MemberDao

package com.mybatis.tutorial;

public class MemberDao {

}

Dto 객체는 보통 데이터베이스 컬럼 이름과 동일하게 작성을 해줍니다. 여기서도 동일하게 작성해보독 하겠습니다. 데이터베이스에는 회원들의 고유한 번호를 알려주는 no , 회원들의 아이디인 id 그리고 회원들의 패스워드인 pw 세개로 이루어질 것입니다.

package com.mybatis.tutorial;

public class MemberDto {
	int no;
	String id;
	String pw;
}

그런 다음 getter 와 setter를 작성해줍시다.


public class MemberDto {
	
	int no;
	String id;
	String pw;
	
	public int getNo() {
		return no;
	}
	public void setNo(int no) {
		this.no = no;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPw() {
		return pw;
	}
	public void setPw(String pw) {
		this.pw = pw;
	}
	
}

마지막으로 MemberDto의 생성자를 만들어줘야하는데요. 생성자를 두개 만들도록 하겠습니다.

public MemberDto(int no,String id, String pw) {
    this.no = no;
    this.id = id;
    this.pw = pw;
}

public MemberDto(String id, String pw) {
    this.id = id;
    this.pw = pw;
}

왜 생성자를 두개 만드나요?

생성자를 두개 만드는 이유가 궁금하신분들이 있을겁니다. 생성자를 두개 만드는 이유는 처음 우리가 테이블을 생성했을 때, no는 auto_increment로 만들었던걸 기억하실 겁니다. 회원의 데이터를 넣을 때는 보통 Dto로 객체를 만들어서 넣기 마련인데, no의 값을 같이 만들어서 넣을 필요가 없습니다. 왜냐하면 no는 auto_increment 이기 때문에 데이터베이스가 자동적으로 만들어 주기 때문에 전혀 필요가 없습니다. 하지만 데이터를 가져올때는 no값을 같이 가져와야 하기때문에 no도 생성자에 같이 넣어줘야 온전한 데이터를 불러올 수 있게 됩니다.

이제 우리의 MemberDto는 이렇게 생겼습니다.

MemberDto

package com.mybatis.tutorial;

public class MemberDto {
	
	int no;
	String id;
	String pw;
	
	public MemberDto(int no,String id, String pw) {
		this.no = no;
		this.id = id;
		this.pw = pw;
	}
	
	public MemberDto(String id, String pw) {
		this.id = id;
		this.pw = pw;
	}
	
	public int getNo() {
		return no;
	}
	public void setNo(int no) {
		this.no = no;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPw() {
		return pw;
	}
	public void setPw(String pw) {
		this.pw = pw;
	}
	
}

이제 MemberDao를 마저 완성해보겠습니다

먼저 이 강좌의 주제인 MyBatis를 가져와서 사용할 차례입니다. MemberDao에 다음과 같이 적어주세요.

package com.mybatis.tutorial;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;

public class MemberDao {
	@Autowired
	SqlSession sqlSession;
}

위에 코드는 우리가 root-context.xml에 작성해줬던 sqlSession을 DI 해주는 과정입니다. @Autowired 를 이용함으로써 객체가 자동으로 root-context.xml에 의해 주입되서 sqlSession을 사용할 수 있게 되었습니다.

이제 함수를 만들어보도록 하겠습니다.

	public int insertMember(MemberDto memberDto) {
		return sqlSession.insert("memberInsert", memberDto);
	}
	
	public int deleteMember(int no) {
		return sqlSession.delete("deleteMember", no);
	}

insertMember 는 회원을 삽입해주는 함수이고, deleteMember 는 회원을 삭제해주는 함수입니다. sqlSession.insert() 는 데이터를 삽입해줄 때 사용되는 함수인데요, 첫번째 파라미터는 이따가 XML 파일에 SQL문을 작성해줄 것인데, 해당되는 SQL문의 id를 작성해주면 됩니다. 두번째 파라미터는 그 SQL문을 실행할 때 들어갈 파라미터들을 같이 전달해주는 역할을 합니다. 지금 이해가 안돼도 괜찮습니다. XML을 작성할 때 이해가 되실거예요.

sqlSession.delete() 는 데이터를 삭제할 때 사용되는 함수인데요, 두개의 파라미터도 위에 insert() 와 같은 역할을 합니다.

완성된 MemberDao는 다음과 같습니다.

package com.mybatis.tutorial;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;

public class MemberDao {

	@Autowired
	SqlSession sqlSession;
	
	public int insertMember(MemberDto memberDto) {
		return sqlSession.insert("memberInsert", memberDto);
	}
	
	public int deleteMember(int no) {
		return sqlSession.delete("deleteMember", no);
	}
}

좋습니다! 이제 SQL문을 작성할 XML파일을 생성해보도록 하겠습니다.

org.mybatis.tutorial.tutorial.mapper 패키지안에 member.xml 이름을 가진 XML 파일을 만들어주세요.

아참. 그전에 이제 mapper 위치를 지정해줬으니 root-context.xml로 돌아가서 위치를 정해주도록 해보겠습니다.

root-context.xml

<bean id="sqlSession"
    class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="mapperLocations"
        value="com/mybatis/tutorial/mapper/*.xml" />
</bean>

mapperLocations에 값을 적어주면 됩니다. *.xml을 붙임으로써 추후에 다른 mapper 파일도 함께 읽을 수 있게 되었습니다.

member.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="org.mybatis.tutorial.MemberDao">
	
</mapper>

namespace 의 값이 sqlSession이 선언된 MemberDao 클래스인걸 확인할 수 있습니다. 이 말은 이 XML 파일이 MemberDao를 계속 쳐다보고 있다고(?) 생각하시면 됩니다. 이 XML 파일은 MemberDao만을 위한 파일이 됩니다.

이제 본격적으로 SQL 을 작성해보도록 할께요.

member.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="org.mybatis.tutorial.MemberDao">

	<insert id="memberInsert" 
		parameterType="com.mybatis.tutorial.MemberDto">
		insert into member (
			id,
			pw
		) values(
			#{id},
			#{pw}
		)
	</insert>
	
</mapper>

#{} 으로 둘러싸인 부분이 이름 치환자라고 하는데요, Dao에서 memberDto의 형태로 파라미터를 전달을 해줬습니다. #{} 을 사용하게 되면, 자동적으로 해당 이름에 맞는 값들로 변경이 됩니다! 여기서의 id는 MemberDao 클래스에서 적어주었던 sqlSession.insert("memberInsert", memberDto); 의 첫번째 파라미터 이름과 동일해야합니다. 그리고 parameterType은 sqlSession.insert("memberInsert", memberDto); 의 두번째 파라미터의 클래스를 적어주면 됩니다.

delete도 마저 적어주도록 할께요

완성된 member.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="org.mybatis.tutorial.MemberDao">
	<insert id="memberInsert" 
		parameterType="com.mybatis.tutorial.MemberDto">
		insert into member (
			id,
			pw
		) values(
			#{id},
			#{pw}
		)
	</insert>
	
	<delete id="deleteMember"
		parameterType="int">
		delete 
		from
			member
		where
			no = #{no}
		
	</delete>
</mapper>

테스트하기

이제 MyBatis를 사용하기 위한 준비가 완료되었습니다. 테스트할 차례만 남았는데요. 우리는 컨트롤러와 뷰 단이 없으니 다른 방법으로 테스트를 할건데, Junit을 이용해서 테스트를 해보겠습니다.

Junit을 잘 모르시는 분은 밑에 링크를 보시면 더 자세히 알 수 있습니다.

Junit에 대하여 알아보기

Junit로 테스트하기 위해 준비를 해보겠습니다. 먼저 root-context.xml을 복사한 다음 com.mybatis.tutorial 패키지 안에 붙여 넣어주세요. 그런 다음 test-context.xml 으로 이름을 변경해주세요,

왜 번거롭게 패키지안에 복사를 해주나요?

패키지안에 다시 복사를 해주는 이유는 테스트용 XML 파일을 만들어줘야 하기 때문입니다. Junit 테스트는 톰캣을 이용하지 않기 때문에 톰캣이 실행 될때 스프링 라이브러리들이 자동적으로 배치되고 동작하게 되는데, 이 부분이 빠져있어 직접 bean으로 설정해줘야하고, 테스트용으로만 사용하는 bean과 실제 서비스할때 사용되는 bean이 있어 따로 분리하기위함입니다.

test-context.xml으로 이름을 변경해주셨으면 MemberDao클래스 빈을 추가하도록 하겠습니다.

test-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- Root Context: defines shared resources visible to all other web components -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName"
			value="com.mysql.jdbc.Driver" />
		<property name="url"
			value="jdbc:mysql://localhost:3306/mybatis" />
		<property name="username" value="root" />
		<property name="password" value="1234" />
	</bean>

	<bean id="memberDao" class="com.mybatis.tutorial.MemberDao" />

	<bean id="sqlSession"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="mapperLocations"
			value="com/mybatis/tutorial/mapper/*.xml" />
	</bean>

	<bean id="sqlSessionTemplate"
		class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSession" />
	</bean>
</beans>

테스트용 클래스 MemberTest를 만들겠습니다.

public class MemberTest {

}

MemberTest에 이제 테스트 코드를 작성할 것입니다. 다음과 같이 작성해주세요.

package com.mybatis.tutorial;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "test-context.xml")
public class MemberTest {

	@Autowired
	MemberDao memberDao;

	@Test
	public void insertTest() {
		MemberDto memberDto = new MemberDto("test", "1234");
		memberDao.insertMember(memberDto);
	}
}

이 테스트 코드는 test-context.xml 설정파일을 처음에 로딩시킵니다. 그런 다음 MemberDto를 하나 생성하여 memberDao.insertMember(memberDto); 를 실행하여 결과적으로 데이터베이스에 데이터를 넣어주는 역할을 하게 됩니다.

테스트코드를 한번 실행해보세요. 초록불이 뜨면 정상적으로 실행이 되었다는 뜻입니다. 한번 확인해 보겠습니다.

터미널에 다음의 sql문을 입력해보세요.

select * from member; 
+----+------+------+
| no | id   | pw   |
+----+------+------+
|  1 | test | 1234 |
+----+------+------+
1 row in set (0.00 sec)

정상적으로 코드가 실행된 것을 알 수 있습니다.

삭제도 테스트 해볼까요?

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "test-context.xml")
public class MemberTest {

	@Autowired
	MemberDao memberDao;

//	@Test
//	public void insertTest() {
//		MemberDto memberDto = new MemberDto("test", "1234");
//		memberDao.insertMember(memberDto);
//	}
	
	@Test
	public void deleteTest() {
		memberDao.deleteMember(1);
	}
}

insertTest() 함수를 주석처리하고 deleteTest() 함수를 만드세요, 그런 다음 똑같이 테스트코드를 실행하면 됩니다.

초록불이 뜨는지 확인해보세요.

자 이제 정상적으로 삭제가 됐는지 확인해보겠습니다.

select * from member;
Empty set (0.00 sec)

아까 나왔던 데이터가 나오질 않는걸 확인할 수 있습니다.

root-context.xml 파일은 쓸모가 없는건가요?

아닙니다. 실제 톰캣을 실행할때는 기본적으로 root-context.xml을 로딩하게 되어있습니다. 그러므로 컨트롤러와 뷰를 만들어 나머지 부분을 완성하여 톰캣을 실행하게되면 root-context를 사용하게 됩니다.

마치며

지금 까지 작성한건 일부분에 불과하고, 더 많은 내용들이 담겨져 있습니다. 더욱 더 자세한 내용을 알고 싶다면 다음의 링크에 들어가서 공부하시면 되겠습니다. 그럼 즐거운 코딩하세요!

Mybatis 공식사이트

완성된 프로젝트