Spring Framework 入門記 JDBCその4 JdbcTemplate 残り

GW中ということもあり,ちょっと中だるみ気味の入門記です.
ところで,この入門記は一応「Reference Documentation」に沿って学習しています.そのつもりでした.しかし...
Chapter 8. DAO support」を完全に見落としていたことに気づいてしまいました.無念だ.別にたいした内容じゃないからすっ飛ばしたとかいう訳ではなく,なぜか見落としていたのです.心より恥じる.
Sprint Padへ持っていくときにこっそり追加することにしようかな...


さぁ,気を取り直していきましょう! っていうか,気合を入れましょう!
... ごめん,気合が抜けちゃってます.無念だ.
本来さくっと終わらせるはずだったJdbcTemplateなのですが,油断してquery()の仲間たちをチェックしてしまったものですから,勢いでupdate()の仲間たちも見てしまったのが痛い.これでexecute()の仲間たちを無視したら,いかにも手抜きではないですか.
そんなわけで,今回はJdbcTemplateの残りのメソッドを見ることにします.


まず,本来なら前回見ておくべきだったのが,

    • int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss)

です.一応update系なので.いわゆるバッチ更新をやってくれるメソッドですね.
JDBCドライバがバッチ更新をサポートしていない場合は,普通にPreparedStatement#execute()を呼び出してくれます.こういうのは気が利いていていいかも.
BatchPreparedStatementSetterは,次のメソッドを持っています.

    • int getBatchSize()
    • void setValues(PreparedStatement ps, int i)

最初のメソッドが返した回数だけsetValues(PreparedStatement, int)が呼び出されるわけですね.
setValues(PreparedStatement, int)の第二引数のiは,バッチアップデート中の何件目かを示す,0から始まる値です.
簡単に使えそうですね.


次はexecute()の仲間たちを見ていきます.
実はこれまでのお試しコードでも,create tableするために

    • void execute(String sql)

をこっそりと使っていたのですが,これはもう,そのまんまSQLを実行してくれるというだけのメソッドです.
で,いつものように,このSQL文字列からPreparedStatementを作って実行してくれるわけですが,その部分をカスタマイズできるように,

    • Object execute(final String sql, PreparedStatementCallback action)
    • Object execute(PreparedStatementCreator psc, PreparedStatementCallback action)

というメソッドが用意されています.
PreparedStatementCallbackというinterfaceは,次のメソッドを持っています.

    • Object doInPreparedStatement(PreparedStatement ps)

JdbcTemplateが用意したPreparedStatementexecute()が呼び出される前に,好きなように細工できるというわけです.PreparedStatementSetter#setValues(PreparedStatement)と同じように使えるわけですね.っていうか,どちらも引数がPreparedStatementで戻り値がObjectになっていたりして,なぜ異なったinterfaceを用意しなければならなかったのか,いまひとつ分かりません.
しかもですね,これまで見てきたJdbcTemplate#update(String, PreparedStatementSetter)などですが,実はPreparedStatementSetterPreparedStatementCallbackにラップされて呼び出されていたみたいです.うーみゅ.相変わらず太った凝ったつくりになってるなぁ.ま,GW中だからいっか.


JdbcTemplate#execute()の仲間はこれだけではありません.ストアドプロシジャ/ファンクションを呼び出すために,次のメソッドが用意されています.

    • Object execute(final String callString, CallableStatementCallback action)
    • Object execute(CallableStatementCreator csc, CallableStatementCallback action)

CallableStatementCallbackCallableStatementCreatorの役割はそれぞれ,PreparedStatementCallbackPreparedStatementCreatorと同じで,PreparedStatementではなくCallableStatementを扱うのが違うくらいです.ということで詳細は省略.心より恥じる.


最後に,

    • Map call(CallableStatementCreator csc, List declaredParameters)

というメソッドがあります.これもストアドを呼び出すためのメソッドなのですが,ResultSetを戻すようなストアドを扱うためのものみたい.やべぇ,そんなの使ったことないよっ! ということでスキップです.無念だ.


これでJdbcTemplatepublicなメソッドは一通り見たことになります.いや,文句なしに見たんです! 見たことにしてくださいっ!
ということでお試しコーナーです.あまりやる気しない感じなのですが,OFF会などやむにやまれぬ事情ががある場合を除いてお試しするのがこの日記のお約束なのです.
ということで,今回はバッチ更新を使ってみましょう.
Propertiesネーム・バリュー・ペアをまるっとinsertするように,いつものFooを修正します.

package study;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;

public class Foo {
    private DataSource dataSource;
    private Properties properties;
    public DataSource getDataSource() {
        return dataSource;
    }
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    public Properties getProperties() {
        return properties;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
    /**
     * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()
     */
    public void createTable() {
        JdbcTemplate jt = new JdbcTemplate(dataSource);
        jt.execute("create table pair (key varchar, value varchar, primary key(key))");
    }
    /**
     * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()
     */
    public void insert() {
        final Iterator it = properties.entrySet().iterator();
        JdbcTemplate jt = new JdbcTemplate(dataSource);
        jt.batchUpdate("insert into pair (key, value) values(?, ?)", new BatchPreparedStatementSetter() {
            public int getBatchSize() {
                return properties.size();
            }
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                Map.Entry entry = (Map.Entry) it.next();
                ps.setString(1, (String) entry.getKey());
                ps.setString(2, (String) entry.getValue());
            }
        });
    }
    /**
     * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute(readOnly=true)
     */
    public Properties query() {
        JdbcTemplate jt = new JdbcTemplate(dataSource);
        return (Properties) jt.query("select * from pair", new ResultSetExtractor() {
            public Object extractData(ResultSet rs) throws SQLException {
                Properties props = new Properties();
                while (rs.next()) {
                    props.put(rs.getString("key"), rs.getString("value"));
                }
                return props;
            }
        });
    }
}

忘れずにAttribute Compilerでコンパイルします.
次に定義ファイル.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd"
>
<beans>
    <bean id="autoProxyCreator" 
        class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames">
            <value>foo</value>
        </property>
        <property name="interceptorNames">
            <value>transactionInterceptor,trace</value>
        </property>
    </bean>

    <bean id="foo" class="study.Foo">
        <property name="dataSource">
            <ref bean="dataSource"/>
        </property>
        <property name="properties">
            <props>
                <prop key="Yuri">Ebihara</prop>
                <prop key="Akiko">Yada</prop>
            </props>
        </property>
    </bean>
</beans>

foopropertiesというプロパティにinsertするデータを設定しています.
最後に実行クラス.いつもとあまり変わりません.

package study;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.BeanFactoryReference;
import org.springframework.context.ApplicationContext;
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;

public class Main {
    public static void main(String[] args) {
        try {
            BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance();
            BeanFactoryReference ref = locator.useBeanFactory("context");
            ApplicationContext context = (ApplicationContext) ref.getFactory();

            Foo foo = (Foo) context.getBean("foo");
            foo.createTable();
            foo.insert();
            System.out.println(foo.query());

            ref.release();
        }
        catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

こいつを実行!!!!

 BEGIN study.Foo#createTable()
 - Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
 - Creating shared instance of singleton bean 'DB2'
 - Creating shared instance of singleton bean 'HSQL'
 - Creating shared instance of singleton bean 'MS-SQL'
 - Creating shared instance of singleton bean 'MySQL'
 - Creating shared instance of singleton bean 'Oracle'
 - Creating shared instance of singleton bean 'Informix'
 - Creating shared instance of singleton bean 'PostgreSQL'
 - SQLErrorCodes loaded: [HSQL Database Engine, Oracle, Microsoft SQL Server, Informix Dynamic Server, PostgreSQL, MySQL, DB2]
 - Looking up default SQLErrorCodes for DataSource
 - Database Product Name is HSQL Database Engine
 - Driver Version is 1.7.1
 END study.Foo#createTable() : null
 - Initiating transaction commit
 BEGIN study.Foo#insert()
 - Looking up default SQLErrorCodes for DataSource
 - Database product name found in cache {17131806}. Name is HSQL Database Engine
 END study.Foo#insert() : null
 - Initiating transaction commit
 BEGIN study.Foo#query()
 - Looking up default SQLErrorCodes for DataSource
 - Database product name found in cache {17131806}. Name is HSQL Database Engine
 END study.Foo#query() : {Yuri=Ebihara, Akiko=Yada}
 - Initiating transaction commit
 {Yuri=Ebihara, Akiko=Yada}

ということで,うまく動いたように見えます.これで,だらだらと続いてしまった退屈なJdbcTemplateを終わりにします.
最後に,バッチ更新のサンプルでありながらHSQLDBを使っているために,実際にはバッチ更新が行われていないことを... 心より恥じる.