Bigfat

[Spring] 스프링이 지원하는 iBATIS 사용하기 (게시판 구현) 본문

Java/web

[Spring] 스프링이 지원하는 iBATIS 사용하기 (게시판 구현)

kyou 2017. 4. 19. 20:09

스프링 프레임워크가 지원하는 iBATIS를 사용해보자

  iBATIS(이하 iBatis, 아이바티스)는 SQL에 기반한 데이터베이스와 자바, 닷넷(.NET), 루비(Ruby) 등을 연결시켜주는 역할을 하는 영속성 프레임워크(Persistence Framework)이다. 이러한 연결은 프로그램의 소스코드에서 SQL 문장을 분리하여 별도의 XML 파일로 저장하고, 이 둘을 서로 연결시켜주는 방식으로 작동한다. iBatis는 사용자가 SQL 문장을 만들면 그에 적합한 객체모델을 생성하는 방식으로 작동한다(참고1).


  iBatis 설정 파일(sqlMapConfig.xml)과 Mapper 파일(bbs.xml)의 위치를 확인하기 위해 전체적인 프로젝트 구조를 먼저 살펴본다.


  iBatis는 스프링4에서 더이상 지원하지 않으므로 스프링3으로 다운그레이드해줘야 한다. 스프링3 버전 중 가장 최근에 릴리즈된 3.2.18.버전으로 변경해주었다.

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


  메이븐 저장소에서 코드를 복사해 아래와 같이 ibatis-sqlmap과 spring-orm의 의존성을 pom.xml에 추가해준다. spring-orm.jar파일을 열어 패키지들을 살펴보면 iBatis뿐만 아니라 ORM(Object-relational Mapping, 객체 관계 매핑) 표준 기술인 JPA(Java Persistence API)와 구현체인 Hibernate 클래스들이 있는 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- Ibatis -->
<!-- https://mvnrepository.com/artifact/org.apache.ibatis/ibatis-sqlmap -->
<dependency>
    <groupId>org.apache.ibatis</groupId>
    <artifactId>ibatis-sqlmap</artifactId>
    <version>2.3.4.726</version>
</dependency>
 
<!-- Spring ORM -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
cs


  root-context.xml을 열어 SqlMapClientFactoryBean과 SqlMapClientTemplate을 Bean에 등록한다. iBatis에 관한 클래스는 스프링이 제공해주므로 스프링 프레임워크에서 API를 확인할 수 있다. 현재는 스프링 3.2.18버전이므로 스프링 3.2.18. 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
<?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">
    
    <!-- DriverManagerDataSource는 커넥션(connection pool)을 지원하지 않는다. -->
    <!-- 그러므로 이후엔 아파치가 제공하는 BasicDataSource를 사용한다. -->
    <bean id="dataSource"
            class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="core.log.jdbc.driver.OracleDriver"></property>
        <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:XE"></property>
        <property name="username" value="kyou"></property>
        <property name="password" value="1234"></property>
    </bean>
    
    <!-- iBATIS는 스프링 프레임워크에서 제공해준다. -->
    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="/WEB-INF/ibatis/sqlMapConfig.xml"></property>
    </bean>
 
    <bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
        <property name="sqlMapClient" ref="sqlMapClient"></property>
    </bean>
</beans>
cs


  SqlMapClient의 설정 파일의 경로를 /WEB-INF/ibatis/sqlMapConfig.xml로 넘겼으므로, 해당 경로와 xml파일을 생성하고 아래와 같이 작성해주자. 다시 root-context.xml을 해석해보자면, SqlMapClientTemplate은 오라클 접속 정보(dataSource)와 ibatis 설정 정보(sqlMapClient)를 가지게 된다. 그리고 이전 JdbcTemplate을 사용하면서 알 수 있었듯 SqlMapClientTemplate클래스는 SqlMapClient 타입을 파라미터로 받는 setSqlMapClient메서드가 있다.

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<!-- iBATIS.com 더이상 존재하지 않음. -->
<!-- <!DOCTYPE sqlMapConfig PUBLIC 
"-//iBATIS.com//DTD SQL Map Config 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd"> -->
<!DOCTYPE sqlMapConfig PUBLIC 
"-//ibatis.apache.org//DTD SQL Map Config 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
 
<sqlMapConfig>
    <settings useStatementNamespaces="true" />
    <sqlMap resource="bbs.xml" />
</sqlMapConfig>
cs


  BBSDaoImpl클래스를 아래와 같이 변경한다. SqlMapClientTemplate의 의존성을 주입하고, 관련 메서드를 사용해 bbs.xml과 매핑된 쿼리 수행 결과를 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
package com.edu.bbs.dao;
 
import java.util.HashMap;
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
import org.springframework.stereotype.Repository;
 
import com.edu.bbs.common.LoginStatus;
import com.edu.bbs.dto.BBSDto;
 
@Repository
public class BBSDaoImpl implements BBSDao {
    HashMap<Object, Object> paramMap;
 
    @Autowired
    SqlMapClientTemplate sqlMapClientTemplate;
    
    @Override
    public List<BBSDto> selectArticles(int startRow, int endRow) {
        // parameterClass는 하나밖에 받을 수 없다.
        // 그러므로 parameter들을 HashMap에 담아서 던져준다.
        paramMap = new HashMap<>();
        paramMap.put("startRow", startRow);
        paramMap.put("endRow", endRow);
        
        // 첫 번째 파라미터인 statementName은 Mapper XML파일(bbs.xml)의 엘리먼트의 id속성 값과 매핑된다.
        return sqlMapClientTemplate.queryForList("selectArticles", paramMap);
    }
 
    @Override
    public int getArticleTotalCount() {
        return (int) sqlMapClientTemplate.queryForObject("getArticleTotalCount");
    }
 
    @Override
    public BBSDto selectArticle(String articleNumber) {
        return (BBSDto) sqlMapClientTemplate.queryForObject("selectArticle", articleNumber);
    }
 
    @Override
    public int upHit(String articleNumber) {
        return sqlMapClientTemplate.update("upHit", articleNumber);
    }
 
    @Override
    public int loginCheck(String id, String pw) {
        String result = sqlMapClientTemplate.queryForObject("loginCheck", id).toString();
        int loginStatus = 0;
        
        if(result != null && result != "") {
            if(pw.equals(result))
                loginStatus = LoginStatus.LOGIN_SUCCESS;
            else
                loginStatus = LoginStatus.PASS_FAIL;
        } else
            loginStatus = LoginStatus.NOT_MEMBER;
        
        return loginStatus;
    }
    
    @Override
    public int insertArticle(BBSDto article) {
        return sqlMapClientTemplate.update("insertArticle", article);
    }
    
    @Override
    public int replyArticle(BBSDto article) {
        sqlMapClientTemplate.update("upPos", article);
        return sqlMapClientTemplate.update("replyArticle", article);
    }
 
    @Override
    public int updateArticle(BBSDto article) {
        return sqlMapClientTemplate.update("updateArticle", article);
    }
 
    @Override
    public int deleteArticle(String articleNumber) {
        return sqlMapClientTemplate.delete("deleteArticle", articleNumber);
    }
 
}
cs


  Mapper XML 파일인 bbs.xml을 아래와 같이 작성한다. 쿼리 중간중간에 as로 별칭을 붙인 것을 볼 수 있는데, 자바측에선 변수명을 camelCase(케멀표기법)로 줬고, 테이블의 컬럼명은 Underscore(전통적인 데이터베이스 칼럼명 형태인 A_COLUMN)로 네이밍했기 때문에 발생한 문제를 해결하기 위해서였다.

  MyBatis(마이바티스)에서는 Config파일에서 mapUnderscoreToCamelCase으로 세팅하면 되는 것은 알고 있는데, iBatis는 어떻게 설정하는지 잘 모르겠다..

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
<?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">
<sqlMap>
    <typeAlias alias="article" type="com.edu.bbs.dto.BBSDto" />
    
    <select id="getArticleTotalCount" resultClass="int">
        SELECT count(*) AS total_count 
          FROM bbs
    </select>
    
    <select id="selectArticles" parameterClass="java.util.HashMap" resultClass="article">
        SELECT bbs.* 
            FROM (SELECT rownum AS row_num
                                 , bbs.* 
                        FROM (SELECT article_number AS articleNumber
                                             , id
                                             , title
                                             , depth
                                             , hit
                                             , write_date AS writeDate
                                    FROM bbs 
                                  ORDER BY group_id DESC
                                               , pos
                                 ) bbs 
                     ) bbs 
         WHERE row_num BETWEEN #startRow# AND #endRow#
    </select>
    
    <select id="selectArticle" parameterClass="String" resultClass="article">
        SELECT article_number AS articleNumber
                 , id
                 , title
                 , content
                 , group_id AS groupId
                 , depth
                 , pos
                 , hit
                 , write_date AS writeDate
                 , file_name AS fileName 
          FROM bbs 
        WHERE article_number = #articleNumber#
    </select>
    
    <update id="upHit" parameterClass="String">
        UPDATE bbs 
             SET hit = hit + 1 
         WHERE article_number = #articleNumber#
    </update>
    
    <select id="loginCheck" parameterClass="String" resultClass="String">
        SELECT pw 
          FROM users 
        WHERE id = #id#
    </select>
    
    <insert id="insertArticle" parameterClass="article">
        INSERT INTO bbs 
               VALUES (bbs_seq.nextval
                            , #id#
                            , #title#
                            , #content#
                            , bbs_seq.currval
                            , 0
                            , 0
                            , 0
                            , sysdate
                            , #fileName#)
    </insert>
    
    <update id="upPos" parameterClass="article">
        UPDATE bbs 
             SET pos = pos + 1
         WHERE group_id = #groupId#
              AND pos > #pos#
    </update>
    
    <insert id="replyArticle" parameterClass="article">
        INSERT INTO bbs 
               VALUES (bbs_seq.nextval
                            , #id#
                            , #title#
                            , #content#
                            , #groupId#
                            , #depth# + 1
                            , #pos# + 1
                            , 0
                            , sysdate
                            , #fileName#)
    </insert>
    
    <update id="updateArticle" parameterClass="article">
        UPDATE bbs 
             SET title = #title#
                  , content = #content#
         WHERE article_number = #articleNumber#
    </update>
    
    <delete id="deleteArticle" parameterClass="String">
        DELETE FROM bbs 
                 WHERE article_number = #articleNumber#
    </delete>
    
</sqlMap>
cs



[iBATIS, 위키백과 참고1]

[[JPA] JPA란 무엇인가? 참고]

[[스프링] ibatis / mybatis의 스프링4 지원 중단? 참고]

[MyBatis 문서 참고]