MyBatis란?

  • MyBatis는 자바 오브젝트와 SQL 사이의 자동 매핑 기능을 지원하는 ORM(Object Relational Mapping) 프레임워크
  • SQL을 별도의 파일로 분리해서 관리하게 해준다.
  • JPA처럼 새로운 DB프로그래밍 패러다임을 익혀야 하는 부담 없이 SQL을 그대로 이용하면서 JDBC 코드 작성의 불편함을 제거해주고 도메인 객체나 VO객체를 중심으로 개발이 가능하다.

 


MyBatis 특징

  1. 쉬운 접근성과 코드의 간결함
    JDBC의 모든 기능을 MyBatis가 대부분 제공한다. 복잡한 JDBC 코드를 사용하지 않고 깔끔한 소스코드를 유지할 수 있다.
  2. SQL문과 프로그래밍 코드의 분리
    SQL에 변경이 있을 때마다 자바 코드를 수정하거나 컴파일하지 않아도 된다.
  3. 다양한 프로그래밍 언어로 구현 가능
    Java, C#, .NET, Ruby

 


Built-in type Aliases

MyBatis에서는 parameterType이나 resultType을 지정할 때 긴 이름의 클래스명 대신 짧은 이름의 alias(별칭)을 사용할 수 있다. 사용자는  <typeAlias>를 통해 이런 SQL mapper에서 사용할 alias를 설정한다.

한편, int, double, boolean 같은 기본 데이터 타입이나 wrapper 클래스에 대해서는 미리 alias가 정의되어 있다. 다음은 미리 정의된 alias 목록이다.

alias type
_byte byte
_short short
_int, _integer int
_long long
_float float
_double double
_boolean boolean
string java.lang.String
decimal java.math.BigDecimal
map java.util.Map
list java.util.list
collection java.util.Collection
object java.lang.Object

 


MyBatis 설정 파일

MyBatis 프레임워크가 참조하는 XML 파일은 Configuration 파일SQL Mapper 파일로 나뉜다. 사용자는 CRUD에 대한 각각의 SQL문을 Mapper XML 파일에 작성하고, 이 파일들을 mybatis-config.xml 파일에 등록하면 MyBatis API를 통해 자동으로 매핑된 Statement 객체들을 생성하여 이를 통해 DB에 SQL문을 실행하게 된다.

  1. Configuration 파일 (mybatis-config.xml)
    DB 설정과 트랜잭션 등 MyBatis가 동작하는 규칙을 정의
  2. SQL Mapper 파일 (MemberMapper.xml)
    SQL문을 XML에 정의한 파일

 


1. MyBatis Configuration 파일

[경로: MyBatisWeb/src/main/java/common.config/mybatis-config.xml]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 타입 별칭 설정 ================================= -->
<typeAliases> <!-- 자바 클래스 이름에 대한 alias 설정 -->
    <typeAlias type="user.model.MemberVO" alias="Member"/>
</typeAliases>
<!-- 트랜잭션 관리자 설정 ============================= -->
    <environments default="development"> 
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver" />
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" />
                <property name="username" value="scott" />
                <property name="password" value="tiger" />
            </dataSource>
        </environment>
    </environments> <!-- 프레임워크에서 사용할 DB정보(트랜잭션 관리자, 데이터 소스) 설정 -->
    <!-- 매퍼 정의 ================================ -->
    <mappers> <!-- SQL Mapper 파일 경로 -->
        <mapper resource="user/model/MemberMapper.xml" />    
    </mappers>
</configuration>
cs

 

1) XML과 DTD 선언

Configuration 파일은 XML이기 때문에 가장 먼저 XML 선언과 DTD 선언을 작성한다.

1
2
3
4
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 "https://mybatis.org/dtd/mybatis-3-config.dtd">
cs

 

2) Root Element <configuration>

MyBatis 설정의 루트 엘리먼트는 <configuration>이다.

 

<configuration>의 child elements 중 주요 엘리먼트의 용어를 정리한 표

엘리먼트 용도
properties 프로퍼티 파일 경로
<property>에 개별 프로퍼티 정의
settings 프레임워크 실행 환경 설정
typeAliases 자바 클래스 이름에 대한 alias 설정
typeHandlers 컬럼 값을 자바 객체로, 자바 객체를 컬럼 값으로 변환해주는 클래스 설정
environments 프레임워크에서 사용할 데이터베이스 정보(트랜젝션 관리자, 데이터 소스) 설정
mappers SQL mapper 파일 경로

 

<typeAliases>

SQL mapper 파일에서 parameterType이나 resultType을 지정할 때 긴 이름의 클래스명 대신 짧은 이름의 alias를 사용할 수 있다. <typeAlias>는 SQL mapper에서 사용할 alias를 설정한다.

1
2
3
<typeAliases>
    <typeAlias type="user.model.MemberVO" alias="Member"/>
</typeAliases>
cs

 

※ SQL Mapper에서 alias 사용

MyBatis 설정 파일에 설정된 alias는 SQL mapper에서 다음과 같이 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<insert id="insertMember" parameterType="Member">
    insert into java_member(name, id, pw, tel, indate)
    values(#{name}, #{id}, #{pw}, #{tel:VARCHAR}, sysdate)
</insert>
 
<select id="listMember_old" resultType="Member">
    select * from java_member order by name asc
</select>
 
<select id="listMember" resultType="Member" parameterType="map">
    SELECT * FROM(
    SELECT row_number() OVER(ORDER BY name ASC) rn, java_member.* FROM
    java_member
    ) WHERE rn BETWEEN #{start} AND #{end}
</select>
    
<delete id="deleteMember" parameterType="Member">
    delete from java_member where id = #{id}
</delete>
cs

 


2. SQL Mapper 파일

MyBatis를 사용하는 목적 중의 하나는 DAO로부터 SQL문을 분리하는 것이다.

분리된 SQL문은 SQL mapper 파일에 작성하며 DAO에서는 SqlSession 객체가 SQL mapper 파일을 참조하게 된다.

출처: https://atoz-develop.tistory.com/entry/MyBatis-%EC%84%A4%EC%A0%95-%ED%8C%8C%EC%9D%BC-SQL-Mapper-%EC%9E%91%EC%84%B1-%EB%B0%A9%EB%B2%95

 

다음은 MyBatis SQL Mapper 파일의 예이다.

[경로: MyBatisWeb/src/main/java/user.model/MemberMapper.xml]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="user.model.MemberMapper">
    <select id="userCount" resultType="_int">
        select count(*) from java_member
    </select>
 
    <insert id="insertMember" parameterType="Member">
        insert into java_member(name, id, pw, tel, indate)
        values(#{name}, #{id}, #{pw}, #{tel:VARCHAR}, sysdate)
    </insert>
 
    <select id="listMember_old" resultType="Member">
        select * from java_member order by name asc
    </select>
 
    <select id="listMember" resultType="Member" parameterType="map">
        SELECT * FROM(
        SELECT row_number() OVER(ORDER BY name ASC) rn, java_member.* FROM
        java_member
        ) WHERE rn BETWEEN #{start} AND #{end}
    </select>
    
    <delete id="deleteMember" parameterType="Member">
    delete from java_member where id = #{id}
    </delete>
</mapper>
cs

 

1) XML과 DTD 선언

SQL Mapper 파일은 XML이기 때문에 가장 먼저 XML 선언과 DTD 선언을 작성한다.

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
cs

 

2) Root Element <mapper>

SQL mapper 파일은 루트 엘리먼트 <mapper>를 작성하는 것으로 시작한다.

<mapper>의 namespace 속성은 자바의 패키지처럼 여러 개의 SQL문을 묶는 용도로 사용하며, mapper들을 구분하는 식별자 역할을 한다. 아래 코드의 user.model.MemberMapper 네임스페이스는 DAO에서 private final String NS = "user.model.MemberMapper";의 형태로 사용하게 된다.

mapper 파일에 작성하는 모든 SQL문은 <mapper> 하위에 놓여야 한다.

1
<mapper namespace="user.model.MemberMapper">
cs

 

3) <select>, <insert>, <update>, <delete>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<select id="userCount" resultType="_int">
    select count(*) from java_member
</select>
 
<insert id="insertMember" parameterType="Member">
    insert into java_member(name, id, pw, tel, indate)
    values(#{name}, #{id}, #{pw}, #{tel:VARCHAR}, sysdate)
</insert>
 
<select id="listMember_old" resultType="Member">
    select * from java_member order by name asc
</select>
 
<select id="listMember" resultType="Member" parameterType="map">
    SELECT * FROM(
    SELECT row_number() OVER(ORDER BY name ASC) rn, java_member.* FROM
    java_member
    ) WHERE rn BETWEEN #{start} AND #{end}
</select>
    
<delete id="deleteMember" parameterType="Member">
    delete from java_member where id = #{id}
</delete>
cs

 

SQL 명령어에 따라 SELECT문은 <select>, INSERT문은 <insert>, UPDATE문은 <update>, DELETE문은 <delete>에 작성한다.

속성 설명
id 각 SQL문을 구분
resultType SELECT문 실행 결과를 담을 객체
패키지 이름을 포함한 클래스 이름 또는 객체 alias 지정
resultMap SELECT문 실행 결과를 담을 객체를 resultMap으로 지정
<resultMap>을 따로 선언해줘야 한다.
resultType과 resultMap 중 하나를 택해서 설정한다.
parameterType 이 속성에 지정한 객체의 프로퍼티값이 SQL문의 입력 파라미터에 지정된다.

 

resultType 속성

SELECT문을 실행하면 결과가 생성되는데 이 결과를 담을 객체resultType 속성에 지정한다.

resultType에는 패키지 이름을 포함한 전체 클래스명을 지정하거나 객체의 alias를 지정할 수 있다. alias는 MyBatis Configuration파일에서 설정한다.

1
2
3
4
5
6
7
8
9
<!-- MyBatis Configuration 파일 -->
<typeAliases> <!-- 자바 클래스 이름에 대한 alias 설정 -->
    <typeAlias type="user.model.MemberVO" alias="Member"/>
</typeAliases>
 
<!-- SQL Mapper 파일 -->
<select id="listMember_old" resultType="Member">
    select * from java_member order by name asc
</select>
cs

 

MyBatis는 SELECT 결과를 저장하기 위해 resultType에 지정된 클래스의 인스턴스를 생성한다. 그리고 각 컬럼에 대응하는 setter를 호출한다.

컬럼명이 id, pw, name, tel, indate이면 각각 setId(), setPw(), setName(), setTel(), setIndate()를 호출한다.

 

parameterType 속성

JDBC에서 PreparedStatement 객체를 사용해서 SQL문을 실행할때 '?'로 파라미터를 표시하고 setXXX() 메소드를 호출해서 파라미터에 값을 지정한다.

1
2
3
4
5
6
7
8
9
10
11
StringBuffer buf = new StringBuffer("INSERT INTO mvc_board(")
                    .append(" num, name, passwd, title, content, fileName, fileSize, readnum, wdate)")
                    .append(" VALUES(mvc_board_seq.NEXTVAL, ?, ?, ?, ?, ?, ?, 0, SYSDATE)");
String sql = buf.toString();
ps = con.prepareStatement(sql);
ps.setString(1, vo.getName());
ps.setString(2, vo.getPasswd());
ps.setString(3, vo.getTitle());
ps.setString(4, vo.getContent());
ps.setString(5, vo.getFileName());
ps.setLong(6, vo.getFileSize());
cs

 

한편, MyBatis에서는 입력 파라미터를 '#{프로퍼티}'로 표시한다.

1
2
3
4
<insert id="insertMember" parameterType="Member">
    insert into java_member(name, id, pw, tel, indate)
    values(#{name}, #{id}, #{pw}, #{tel:VARCHAR}, sysdate)
</insert>
cs

 

#{프로퍼티}에 지정되는 값은 parameterType에 지정된 객체의 프로퍼티 값이다. 즉, #{name}에는 Member 객체(user.model.MemberVO)의 getName() 반환값이 설정된다.

 


DAO의 SqlSession 객체 얻어오기

* DAO의 SqlSession 객체: SQL mapper 파일 참조, DB CRUD 관련한 메서드를 가지고 있다.

 

SqlSession을 얻어오는 절차 - Factory Pattern
  1. 건축가(SqlSessionFactoryBuilder)를 통해서 공장(SqlSessionFactory)을 짓는다.(getSessionFactory() 메서드)
    공장을 지을 때 설계도(mybatis-config.xml)을 참조한다.
  2. 공장(SqlSessionFactory)이 지어지면 이 공장을 통해 제품(SqlSession)을 얻는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MemberDAOMyBatis {
    //Namespace: 어떤 mapper를 사용할지 네임스페이스를 보고 찾는다. (네임스페이스 지정 필수)
    private final String NS = "user.model.MemberMapper";
    
    private SqlSession ses; //공장에서 만들어진 제품
    
    //공장 짓는 메서드
    public SqlSessionFactory getSessionFactory() {
 
        String resource = "common/config/mybatis-config.xml"//설계도
        InputStream is = null;
        try {
            //설계도 파일을 읽을 수 있는 스트림 얻기
            is = Resources.getResourceAsStream(resource);
        }catch (IOException e) {
            e.printStackTrace();
        }
        //건축가
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //공장을 짓는다
        SqlSessionFactory factory = builder.build(is);
        return factory;
    }
}
 
cs

 


MyBatis- DAO 내 메서드 작성법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class MemberDAOMyBatis {
    //Namespace: 어떤 mapper를 사용할지 네임스페이스를 보고 찾는다. (네임스페이스 지정 필수)
    private final String NS = "user.model.MemberMapper";
    
    private SqlSession ses; //공장에서 만들어진 제품
    
    //공장 짓는 메서드
    public SqlSessionFactory getSessionFactory() {
 
        String resource = "common/config/mybatis-config.xml"//설계도
        InputStream is = null;
        try {
            //설계도 파일을 읽을 수 있는 스트림 얻기
            is = Resources.getResourceAsStream(resource);
        }catch (IOException e) {
            e.printStackTrace();
        }
        //건축가
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //공장을 짓는다
        SqlSessionFactory factory = builder.build(is);
        return factory;
    }
    
    public int getMemberCount() {
        try {
            //공장을 가져오고, 오픈한다. true => 자동 커밋
            ses = this.getSessionFactory().openSession(true);
            int count = ses.selectOne(NS+".userCount"); //selectOne: 단일레코드
            //namespace + .id => 해당 쿼리로 매핑
            return count;
        } finally {
            close();
        }
    }
 
    public int insertMember(MemberVO user) {
        try {
            ses = this.getSessionFactory().openSession(true); //자동 커밋(true)
            int n = ses.insert(NS + ".insertMember", user);
            //회원정보(user)를 파라미터로 전달
            return n;
        } finally {
            close();
        }
    }
    
    private void close() {
        if(ses!=null) ses.close();
    }
 
    public List<MemberVO> listMember(int start, int end) {
        try {
            Map<String, Object> map = new HashMap<>();
            map.put("start", start); //Integer객체가 저장됨
            map.put("end", end); //Integer객체가 저장됨
            
            ses = this.getSessionFactory().openSession(true);
            List<MemberVO> arr = ses.selectList(NS + ".listMember", map);
            return arr;
        } finally {
            close();
        }
    }
 
    public int deleteMember(String id) {
        try {
            ses = this.getSessionFactory().openSession(true); //자동 커밋(true)
            int n = ses.delete(NS + ".deleteMember", id);
            return n;
        } finally {
            close();
        }
    }
}
cs

 


DAO 메서드와 SQL Mapper 매칭

1) getMemberCount()

1
2
3
4
5
6
7
8
9
10
11
public int getMemberCount() {
    try {
        //공장을 가져오고, 오픈한다. true => 자동 커밋
        ses = this.getSessionFactory().openSession(true);
        int count = ses.selectOne(NS+".userCount"); //selectOne: 단일레코드
        //namespace + .id => 해당 쿼리로 매핑
        return count;
    } finally {
        close();
    }
}
cs
1
2
3
<select id="userCount" resultType="_int">
    select count(*) from java_member
</select>
cs

 

2) insertMember()

1
2
3
4
5
6
7
8
9
10
public int insertMember(MemberVO user) {
    try {
        ses = this.getSessionFactory().openSession(true); //자동 커밋(true)
        int n = ses.insert(NS + ".insertMember", user);
        //회원정보(user)를 파라미터로 전달
        return n;
    } finally {
        close();
    }
}
cs
1
2
3
4
<insert id="insertMember" parameterType="Member">
    insert into java_member(name, id, pw, tel, indate)
    values(#{name}, #{id}, #{pw}, #{tel:VARCHAR}, sysdate)
</insert>
cs

 

3) listMember()

1
2
3
4
5
6
7
8
9
10
11
12
13
public List<MemberVO> listMember(int start, int end) {
    try {
        Map<String, Object> map = new HashMap<>();
        map.put("start", start); //Integer객체가 저장됨
        map.put("end", end); //Integer객체가 저장됨
            
        ses = this.getSessionFactory().openSession(true);
        List<MemberVO> arr = ses.selectList(NS + ".listMember", map);
        return arr;
    } finally {
        close();
    }
}
cs
1
2
3
4
5
6
<select id="listMember" resultType="Member" parameterType="map">
    SELECT * FROM(
    SELECT row_number() OVER(ORDER BY name ASC) rn, java_member.* FROM
    java_member
    ) WHERE rn BETWEEN #{start} AND #{end}
</select>
cs

 

4) deleteMember()

1
2
3
4
5
6
7
8
9
public int deleteMember(String id) {
    try {
        ses = this.getSessionFactory().openSession(true); //자동 커밋(true)
        int n = ses.delete(NS + ".deleteMember", id);
        return n;
    } finally {
        close();
    }
}
cs
1
2
3
<delete id="deleteMember" parameterType="string">
   delete from java_member where id = #{value}
</delete>
cs

 

+ Recent posts