Bigfat

[Spring] MyBatis 사용하기, MapperFactoryBean (게시판 구현) 본문

Java/web

[Spring] MyBatis 사용하기, MapperFactoryBean (게시판 구현)

kyou 2017. 4. 24. 14:27

iBATIS 버전3인 MyBatis를 설정하고 사용해보자

  과거 iBATIS인 MyBatis(이하 마이바티스)는 버전3부터 자바와 닷넷 주요 개발자들을 포함한 팀 전원이 아파치 소프트웨어 재단에서 구글 코드로 이전하면서 프레임워크의 이름이 변경되었다. 스프링은 MyBatis의 기본 지원을 중단하고 스프링 연동 프로젝트(mybatis-spring)를 시작했다(참고1).


  데이터베이스 접속 정보를 담은 db.properties, 마이바티스 설정 파일인 mybatisConfig.xml, 쿼리가 작성되어있는 Mapper XML인 bbs.xml이 어느 경로에 위치해있는지 확인하기 위해 프로젝트의 전체적인 구조를 확인하자.


  pom.xml을 열어 iBATIS를 사용하기 위해 다운그레이드했던 스프링의 버전을 최신으로 변경해주자. 기존에 4.3.7버전을 사용하였었는데, 3일 전(2017.04.18.)에 4.3.8버전이 릴리즈되었다.

1
2
3
4
5
6
<properties>
    <java-version>1.6</java-version>
    <org.springframework-version>4.3.8.RELEASE</org.springframework-version>
    <org.aspectj-version>1.6.10</org.aspectj-version>
    <org.slf4j-version>1.6.6</org.slf4j-version>
</properties>
cs


  마이바티스 프레임워크 사용을 위해 mybatis를, 스프링과의 연동을 위해 mybatis-spring 의존성을 pom.xml에 추가해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- MyBatis -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
</dependency>
cs


  DB 접근 정보를 작성해놓은 db.properties파일을 아래와 같이 작성한다. driverClassName을 이전과 같이 log4sql을 사용하기 위한 클래스 경로로 작성하면 서버 구동 시엔 에러가 없지만, DB에 접근하려할 때 에러가 발생한다. 이는 dbcp2를 사용하기 때문이라 판단되며 자세한 건 찾아보고 있다...

1
2
3
4
5
6
#bbs.properties
#driverClassName=core.log.jdbc.driver.OracleDriver
driverClassName=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:XE
username=kyou
password=1234
cs


  root-context.xml을 아래와 같이 작성한다. PropertyPlaceholderConfigurer를 이용하면 ${...}로 db.properties파일 내에 값들을 넘겨받을 수 있다.

  스프링이 지원하는 DriverManagerDataSource를 사용했던 아이바티스 프로젝트와는 달리 BasicDataSource를 사용한다. BasicDataSource는 톰캣이 지원하므로 Tomcat 8.5 API 문서에서 확인할 수 있다. 만약 8.0버전이나 7.0버전을 사용한다면 URL에 명시된 버전을 바꿔 해당 버전에 대한 API 문서를 볼 수 있다. 톰캣 7.0은 DBCP 1.x버전을 사용하므로 패키지명을 다르게 지정해줘야 한다. BasicDataSource는 커넥션 풀(DataBase Connection Pool)을 지원하며, DBCP에 관해서는 Apache commons DBCP overviewCommons DBCP 이해하기를 읽어보도록 하자.

  mybatis-spring를 이용하면 마이바티스 API를 직접 사용할 수 있다. sqlSessionFactory를 SqlSessionFactoryBean을 이용해 생성한다. 데이터베이스 정보가 담긴 dataSource를 참조하여, sqlSessionFactory 인스턴스가 트랜잭션을 관리해야하는 JDBC DataSource를 설정한다. 마이바티스 설정 정보를 담은 mybatisConfig.xml의 위치를 configLocation의 value로 작성한다.

  Thread safe(여러 스레드에 의해 동시에 사용되어도 안전함)하고, 스프링이 관리하는 SqlSession을 생성하는 템플릿인 SqlSessionTemplate은 SqlSession 인스턴스를 생성하기 위해 sqlSessionFactory를 필요로 한다.

  마이바티스 Mapper 인터페이스의 주입(injection)을 가능하게 하는 BeanFactory인 MapperFactoryBean을 SqlSessionTemplate로 설정한다.

  SqlSessionFactoryBean클래스와 SqlSessionTemplate클래스, MapperFactoryBean클래스는 Mybatis-Spring API 문서를 통해 확인할 수 있다.

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
<?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="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:db.properties"></property>
    </bean>
    
    <!-- BasicDataSource는 Connection Pool 지원 -->
    <!-- Tomcat7 사용 시 패키지명 org.apache.tomcat.dbcp.dbcp.BasicDataSource -->
    <bean id="dataSource"
            class="org.apache.tomcat.dbcp.dbcp2.BasicDataSource">
        <property name="driverClassName" value="${driverClassName}"></property>
        <property name="url" value="${url}"></property>
        <property name="username" value="${username}"></property>
        <property name="password" value="${password}"></property>
    </bean>
    
    <bean id="sqlSessionFactory"
            class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:/mybatisConfig/mybatisConfig.xml"></property>
 
        <!-- config.xml에 mappers로 작성하지 않는다면 mapperLocations로 bean에 경로를 알려줄 수 있다. -->
        <!-- <property name="mapperLocations" value="classpath:/com/edu/mapper/bbs/bbs.xml"></property> -->
    </bean>
    
    <bean id="sqlSessionTemplate"
            class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg ref="sqlSessionFactory"></constructor-arg>
    </bean>
    
    <bean id="bbsDao"
            class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="com.edu.bbs.dao.BBSDao"></property>
        <property name="sqlSessionTemplate" ref="sqlSessionTemplate"></property>
    </bean>
</beans>
cs


  mybatisConfig.xml은 마이바티스 설정 파일이다. <settings>에 런타임시 마이바티스의 행위를 조정하는 값들을 설정한다. 주석 처리되어있는 마이바티스 문서(한글)에서 각각의 설정에 대한 설명들을 읽어보자. 

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
<?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>
        <!-- http://www.mybatis.org/mybatis-3/ko/configuration.html -->
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="false"/>
        <setting name="multipleResultSetsEnabled" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <setting name="defaultStatementTimeout" value="25000"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="jdbcTypeForNull" value="VARCHAR"/>
        <!-- <setting name="callSettersOnNulls" value="true" /> -->
    </settings>
    
    <!-- iBATIS와 다르게 config.xml로 빼낸다. -->
    <typeAliases>
        <typeAlias alias="article" type="com.edu.bbs.dto.BBSDto"/>
        <!-- <typeAlias alias="comment" type="com.edu.comment.dto.CommentDto"/> -->
    </typeAliases>
    
    <!-- iBATIS는 <sqlMapConfig><sqlMap resource="bbs.xml" /> 방식으로 매핑한다. -->
    <mappers>
        <!-- resource속성은 resources 아래에서 찾으므로 다음 경로와 같이 작성한다. -->
        <mapper resource="com/edu/mapper/bbs/bbs.xml"/>
    </mappers>
    
</configuration>
cs


  BBSDao 인터페이스를 구현하는 BBSDaoImpl클래스를 사용하지 않으므로, BBSDaoImpl클래스의 메서드의 기능을 BBSServiceImpl가 모두 처리하게 된다.

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package com.edu.bbs.service;
 
import java.util.ArrayList;
import java.util.HashMap;
 
import javax.servlet.http.HttpSession;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
 
import com.edu.bbs.common.Page;
import com.edu.bbs.dao.BBSDao;
import com.edu.bbs.dto.BBSDto;
 
@Service
public class BBSServiceImpl implements BBSService {
    
    @Autowired
    private BBSDao bbsDao;
    
    @Autowired
    private Page page;
    
    HashMap<String, Object> paramMap;
    
    @Override
    public Model list(int pageNum, Model model) {        // Spring은 원하는 타입으로 받아온다, pageNum을 String으로 받아와도 상관없다.
        ArrayList<BBSDto> articles = null;
 
        int totalCount = 0;
        int pageSize = 10;
        int pageBlock = 10;
        
        totalCount = bbsDao.getArticleTotalCount();
        page.paging(pageNum, totalCount, pageSize, pageBlock);
        
        // mapper.xml은 파라미터를 하나밖에 받을 수 없으므로 HashMap에 담아서 던져준다.
        paramMap =  new HashMap<>();
        paramMap.put("startRow", page.getStartRow());
        paramMap.put("endRow", page.getEndRow());
//        articles = (ArrayList<BBSDto>) bbsDao.selectArticles(page.getStartRow(), page.getEndRow());
        articles = (ArrayList<BBSDto>) bbsDao.selectArticles(paramMap);
        
        model.addAttribute("totalCount", totalCount);
        model.addAttribute("articles", articles);
        model.addAttribute("pageNum", pageNum);
        model.addAttribute("pageCode", page.getSb().toString());
        
        return model;
    }
    
    @Override
    public BBSDto content(String articleNumber) {
        BBSDto article = bbsDao.selectArticle(articleNumber);
        bbsDao.upHit(articleNumber);
        
        return article;
    }
    
    @Override
    public String login(HttpSession session, String id, String pw) {
        String view = null;
        String result = bbsDao.loginCheck(id);
        
        if(result != null && result != "") {
            if(pw.equals(result)) {
                session.setAttribute("id", id);
                view = "redirect:/list.bbs?pageNum=1";
            }
            else
                view = "passFail";
        } else
            view = "joinMember";
            
        return view;
    }
 
    @Override
    public int write(BBSDto article) {
        return bbsDao.insertArticle(article);
    }
 
    @Override
    public int reply(BBSDto article) {
        bbsDao.upPos(article);
        return bbsDao.replyArticle(article);
    }
 
    @Override
    public BBSDto updateForm(String articleNumber) {
        return bbsDao.selectArticle(articleNumber);
    }
 
    @Override
    public int update(BBSDto article) {
        return bbsDao.updateArticle(article);
    }
 
    @Override
    public int delete(String articleNumber) {
        return bbsDao.deleteArticle(articleNumber);
    }
    
}
cs


  BBSDao 인터페이스는 Mapper 인터페이스다. 이 매퍼 인터페이스 내에 선언된 mapper클래스들의 메서드 이름이 마이바티스 설정 파일에 <mapper>의 resource 속성 값으로 작성해둔 bbs.xml의 엘리먼트 id와 같은 쿼리를 수행해 결과를 return 해준다.

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
package com.edu.bbs.dao;
 
import java.util.HashMap;
import java.util.List;
 
import com.edu.bbs.dto.BBSDto;
 
public interface BBSDao {
    
//    public List<BBSDto> selectArticles(int startRow, int endRow);
    public List<BBSDto> selectArticles(HashMap<String, Object> paramMap);
    
    public int getArticleTotalCount();
    
    public BBSDto selectArticle(String articleNumber);
    
    public int upHit(String articleNumber);
    
    public String loginCheck(String id);
    
    public int insertArticle(BBSDto article);
    
    public int upPos(BBSDto article);
    
    public int replyArticle(BBSDto article);
    
    public int updateArticle(BBSDto article);
    
    public int deleteArticle(String articleNumber);
    
}
cs


  Mapper 인터페이스와 Mapper XML은 namespace 속성으로 매핑된다. 위에서 언급했듯이 매퍼 인터페이스(BBSDao)의 매퍼 클래스의 메서드명과 일치하는 id의 엘리먼트(<select>, <insert>, <update>, <delete>)의 parameterType으로 매퍼 클래스의 파라미터를 넘겨받고, resultType으로 결과를 return 해준다.

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<?xml version="1.0" encoding="UTF-8"?>
<!-- <!DOCTYPE sqlMap PUBLIC 
"-//http://ibatis.apache.org//DTD SQL Map 2.0//EN" 
"http://ibatis.apache.org/dtd/sql-map-2.dtd"> -->
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<!-- <sqlMap> -->
    <!-- <typeAlias alias="article" type="com.edu.bbs.dto.BBSDto" /> -->
<mapper namespace="com.edu.bbs.dao.BBSDao">
 
    <select id="getArticleTotalCount" resultType="int">
        SELECT count(*) AS total_count 
          FROM bbs
    </select>
    
    <!-- <select id="selectArticles" parameterClass="java.util.HashMap" resultClass="article"> -->
    <select id="selectArticles" parameterType="HashMap" resultType="article">
        SELECT bbs.* 
            FROM (SELECT rownum AS row_num
                                 , bbs.* 
                        FROM (SELECT article_number
                                             , id
                                             , title
                                             , depth
                                             , hit
                                             , write_date
                                    FROM bbs 
                                  ORDER BY group_id DESC
                                               , pos
                                 ) bbs 
                     ) bbs 
         WHERE row_num BETWEEN #{startRow} AND #{endRow}
    </select>
    
    <select id="selectArticle" parameterType="String" resultType="article">
        SELECT article_number
                 , id
                 , title
                 , content
                 , group_id
                 , depth
                 , pos
                 , hit
                 , write_date
                 , file_name
          FROM bbs 
        WHERE article_number = #{articleNumber}
    </select>
    
    <update id="upHit" parameterType="String">
        UPDATE bbs 
             SET hit = hit + 1 
         WHERE article_number = #{articleNumber}
    </update>
 
    <select id="loginCheck" parameterType="String" resultType="String">
        SELECT pw 
          FROM users 
        WHERE id = #{id}
    </select>
    
    <insert id="insertArticle" parameterType="article">
        INSERT INTO bbs 
               VALUES (bbs_seq.nextval
                            , #{id}
                            , #{title}
                            , #{content}
                            , bbs_seq.currval
                            , 0
                            , 0
                            , 0
                            , sysdate
                            , #{fileName})
    </insert>
    
    <update id="upPos" parameterType="article">
        UPDATE bbs 
             SET pos = pos + 1
         WHERE group_id = #{groupId}
              AND pos > #{pos}
    </update>
    
    <insert id="replyArticle" parameterType="article">
        INSERT INTO bbs 
               VALUES (bbs_seq.nextval
                            , #{id}
                            , #{title}
                            , #{content}
                            , #{groupId}
                            , #{depth} + 1
                            , #{pos} + 1
                            , 0
                            , sysdate
                            , #{fileName})
    </insert>
    
    <update id="updateArticle" parameterType="article">
        UPDATE bbs 
             SET title = #{title}
                  , content = #{content}
         WHERE article_number = #{articleNumber}
    </update>
    
    <delete id="deleteArticle" parameterType="String">
        DELETE FROM bbs 
                 WHERE article_number = #{articleNumber}
    </delete>
    
</mapper>
cs



[MyBatis SQL Mapper(마이바티스란) 참고1]