Bigfat

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

Java/web

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

kyou 2017. 4. 18. 20:03

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

  스프링이 제공하는 JdbcTemplate은 데이터베이스 접근 정보를 클래스에 담지 않는다. 그러므로 더이상 기존 프로젝트의 OracleDBConnector클래스는 필요가 없으며, **DaoImpl클래스에서 메서드마다 커넥션을 얻지 않아도 된다. 스프링은 이러한 **Template클래스와 같이 다양한 API를 제공하는데, 이를 PSA(Portable Service Abstractions, 쉬운 서비스 추상화) 기술이라고 한다(참고1).

  우선 pom.xml에 아래와 같이 spring-jdbc에 대한 의존성을 추가한다. 이때 주의할 점은 스프링에 관련된 jar파일들의 버전은 항상 일치해야 한다. 그렇지 않으면 에러가 발생하니 유의하도록 하자. 아래와 같이 작성하면 <version>값을 pom.xml 상단부에 <org.springframework-version>엘리먼트의 값인 '4.3.7.RELEASE'와 동일하게 적용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
<!-- Spring JDBC -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<!-- <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.7.RELEASE</version>
</dependency> -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
cs


  이제 /WEB-INF/spring/root-context.xml을 아래와 같이 작성한다. 개발자가 직접 작성한 클래스들은 애노테이션(@Component, @Controller, @Service, @Repository)으로 Beans에 등록시켜줄 수 있지만, 스프링이 제공하는 클래스들을 사용하기 위해서는 xml에 직접 등록하여 사용하여야 한다. 물론 @Configuration을 사용하여 xml이 아닌 Java코드로 컨테이너를 설정할 수도 있고(참고2), 이는 스프링 4.0부터 지향하는 방법이라고 한다.

  DriverManagerDataSource클래스를 id가 dataSource인 Bean객체로 Beans에 등록한다. <property>의 name, value속성을 사용해 데이터베이스 접근 정보를 설정해준다. 스프링 4.3.7 API 문서에서 DriverManagerDataSource클래스를 검색해보면 url, username, password를 파라미터로 하는 생성자와 setDriverClassName메서드가 있는 것을 확인할 수 있다. <property>의 name속성은 set메서드와 연관되어 있고, value속성은 set메서드의 파라미터가 되는 것이다. 예를 들면, <property name="driverClassName" value="core.log.jdbc.driver.OracleDriver">은 DriverManagerDataSource.setDriverClassName("core.log.jdbc.driver.OracleDriver")와 같은 기능을 하는 것이다. url, username, password의 set메서드는 DriverManagerDataSource의 부모클래스인 AbstractDriverBasedDataSource에 선언되어 있다.

  DriverManagerDataSource는 DataSource인터페이스를 구현한다. API문서에서 JdbcTemplate클래스를 찾아보면 DataSource를 파라미터로 받는 생성자가 있는 것을 확인할 수 있다. 아래 코드와 비교해보면 JdbcTemplate클래스를 id가 jdbcTemplate인 Bean객체를 등록하고 <constructor-arg>엘리먼트로 JdbcTemplate의 생성자의 파라미터로 dataSource를 전달한다. 이런 방법으로 의존성을 주입하게 된다. <property>의 name속성으로 dataSource를 줬다. 위의 설명에 비추어봤을 때 JdbcTemplate은 setDataSource메서드를 가지고 있어야 한다. 역시나 JdbcTemplate의 부모클래스인 JdbcAccessor클래스에 setDataSource메서드가 선언되어 있다. 토할 것 같다. 다시 정리해야겠다...

  참고로 Bean을 구분하는 방법으로는 id속성, name속성, class속성이 있고, Bean을 찾는 순서도 나열한 순서대로다.

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
<?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 -->
    
    <!-- DriverManagerDataSource는 커넥션(connection pool)을 지원하지 않는다. -->
    <!-- 그러므로 이후엔 Apache가 제공하는 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>
    
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg>
            <ref bean="dataSource"/>
        </constructor-arg>
        
        <!-- 첫 번째 방법 -->
        <property name="dataSource" ref="dataSource"></property>
        
        <!-- 두 번째 방법 -->
        <!-- <property name="dataSource">
            <ref bean="dataSource"/>
        </property> -->
    </bean>
</beans>
cs


  스프링 MVC 구조로 게시판 구현하기 프로젝트에서 BBSDaoImpl클래스만 아래와 같이 변경해준다. @Autowired 애노테이션으로 root-context.xml에서 bean객체에 등록해준 JdbcTemplate의 의존성을 주입해준다. 메서드마다 Connection을 얻어줄 필요도, 예외 처리를 해줄 필요도 없고, 코드가 이전보다 간결해진 것을 알 수 있다.

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
package com.edu.bbs.dao;
 
import java.util.ArrayList;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
 
import com.edu.bbs.common.LoginStatus;
import com.edu.bbs.dto.BBSDto;
 
@Repository
public class BBSDaoImpl implements BBSDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    StringBuffer query;
    
    @Override
    public ArrayList<BBSDto> selectArticles(int startRow, int endRow) {
        query = new StringBuffer();    
        query.append("SELECT bbs.* ");
        query.append("  FROM (SELECT rownum AS row_num, bbs.* ");
        query.append("            FROM (SELECT article_number, id, title, depth, hit, write_date ");
        query.append("                        FROM bbs ");
        query.append("                       ORDER BY group_id DESC, pos ");
        query.append("                    ) bbs ");
        query.append("          ) bbs ");
        query.append(" WHERE row_num BETWEEN ? AND ?");
    
        return (ArrayList<BBSDto>) jdbcTemplate.query(query.toString(), 
                new Object[]{startRow, endRow}, new BeanPropertyRowMapper<BBSDto>(BBSDto.class));
    }
    
    @Override
    public int getArticleTotalCount() {
        // Spring이 알아서 예외 처리해준다.
        return jdbcTemplate.queryForObject("SELECT count(*) AS total_count FROM bbs", Integer.class);
    }
    
    @Override
    public BBSDto selectArticle(String articleNumber) {
        return (BBSDto) jdbcTemplate.queryForObject("SELECT * FROM bbs WHERE article_number = ?",
                new Object[] {articleNumber}, new BeanPropertyRowMapper<>(BBSDto.class));
    }
    
    @Override
    public int upHit(String articleNumber) {
        return jdbcTemplate.update("UPDATE bbs SET hit = hit + 1 WHERE article_number = ?", articleNumber);
    }
    
    @Override
    public int loginCheck(String id, String pw) {
        String result = jdbcTemplate.queryForObject("SELECT pw FROM users WHERE id = ?"new Object[]{id}, String.class);
        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 signUpIdCheck(String inputId) {
        // TODO Auto-generated method stub
        return 0;
    }
    
    @Override
    public int insertArticle(BBSDto article) {
        return jdbcTemplate.update("INSERT INTO bbs VALUES(bbs_seq.nextval, ?, ?, ?, bbs_seq.currval, 0, 0, 0, sysdate, ?)",
                article.getId(), article.getTitle(), article.getContent(), article.getFileName());
    }
    
    public int upPos(int groupId, int pos) {
        return jdbcTemplate.update("UPDATE bbs SET pos = pos + 1 WHERE group_id = ? AND pos > ?"
                groupId, pos);
    }
    
    @Override
    public int replyArticle(BBSDto article) {
        this.upPos(article.getGroupId(), article.getPos());
        
        return jdbcTemplate.update("INSERT INTO bbs VALUES(bbs_seq.nextval, ?, ?, ?, ?, ?, ?, 0, sysdate, ?)",
                article.getId(), article.getTitle(), article.getContent(), 
                article.getGroupId(), article.getDepth() + 1, article.getPos() + 1, article.getFileName());
    }
    
    @Override
    public int updateArticle(BBSDto article) {
        return jdbcTemplate.update("UPDATE bbs SET title=?, content=? WHERE article_number=?",
                article.getTitle(), article.getContent(), article.getArticleNumber());
    }
    
    @Override
    public int deleteArticle(String articleNumber) {
        return jdbcTemplate.update("DELETE FROM bbs WHERE article_number = ?", articleNumber);
    }
    
}
cs


  JdbcTemplate보다는 MyBatis를 훨씬 더 많이 사용하겠지만(뿐만아니라 Hibernate, JPA도 있고), 스프링 클래스를 Bean객체에 등록하고 의존성을 주입하는 것을 더욱 이해하기 위해 이 글을 올린다. 완벽히 이해된 것은 아닌 것 같다.



[Spring 의 시작, 프레임워크의 구성요소와 동작원리 참고1]

[Spring java Code 기반 설정 참고2]

[[Spring 레퍼런스] 13장 JDBC를 사용한 데이터 접근 #1 참고]

[Getting List<String> using JdbcTemplate, Stack Overflow 참고]