Spring Framework 入門記 JDBCその6 SqlUpdate

もはや惰性で続いている感じの入門記,今日はRDBの更新に使用する

  • SqlUpdate

です.
これは前回学習したSqlQueryと異なり,抽象クラスではないので,そのまま使うこともできるっぽい.ですが,「Reference Documentation」の「9.4.3. SqlUpdate」では,やっぱり派生クラスを作っています.ま,気にしない気にしない.
このSqlUpdateですが,SqlQueryと同じくSqlOperationの派生クラスで,SqlOperationRdbmsOperationの派生クラスで,そんなわけでSqlQueryと同じく次のプロパティを持っています.

    • dataSource
    • sql

まぁ,いいでしょう.と思いきや,このsqlというプロパティ,SqlOperationではなくてRdbmsOperationで定義されているのですね.不思議.
どうやらSqlOpeartionというのはJdbcOperationとでもいうべき存在みたい.RdbmsOperationjava.sqlパッケージを全く使ってなくて(javax.sqlDataSourceのみ使ってますが),JDBCを使った実装がSqlOperationという役割になっているようです.
そんなことはともかく,上記のプロパティを設定すると,RDBを更新することができます.そのためのメソッドが6個,用意されています.よかった,二桁じゃなくて.
まずは,更新用のSQLにパラメータが含まれていない場合に使う,

    • update()

ですね.
それから,intのパラメータを1〜2個持つSQLを実行するために,

    • update(int)
    • update(int, int)

があり,さらにStringのパラメータを1〜2個持つSQLを実行するために,

    • update(String)
    • update(String, String)

が用意されています.
最後に,任意のパラメータを持つSQLを実行するために,

    • update(Object[])

が用意されています.
でも,SqlQueryと異なり,コンテキストを渡すメソッドは用意されていません.うーむ,一貫性がないなぁ.


ということで,お試しタぁイム(あゆの北陽タぁイム風)
全然面白くありませんが,前回のサンプルでレコードをinsertしているところをSqlUpdateを使うようにしてみます(マジでつまらんなぁ).
...
ぐはぁっ!
はまってもーた.そうか,SQLにパラメータが含まれている場合はそうなるのか... 無念だ.
実はSqlUpdateには(SqlQueryにも),

    • types

というint配列のプロパティがあって,SQLが含むパラメータの型を明示的に指定しなくてはいけないみたい.
そうすると,afterPropertiesSet()が呼び出されたところで

    • compile()

というメソッド呼び出されて,SQL文字列に含まれるパラメータの数とtypes配列の要素数が一致しているかチェックされるようです.一致していないと,例外が吹っ飛んできます.吹っ飛ばされました.心より恥じる.
しかし,intJDBC型を指定するのはいやだなぁ.定義ファイル中にTypes.VARCHARとか書けるわけないので,"12"とか書くはめに.ちなみに"12"はVARCHARです.わっかんないよ,そんなの.
そうか,だから「9.4.3. SqlUpdate」では派生クラスを作っているのか... なんかイマイチだなぁ.
JdbcTypeとかいうクラスとJdbcTypeEditorとかいうPropertyEditorを用意して,その配列プロパティを持つようなSqlUpdateの派生クラスを作ればいいかなぁ.
ということで,作ってみました.

package study;
import java.lang.reflect.Field;
import java.sql.Types;

public class JdbcType {
    private String typeName;
    private int type;
    public JdbcType(String typeName) {
        this.typeName = typeName;
        try {
            Field field = Types.class.getField(typeName);
            type = field.getInt(null);
        }
        catch (Exception e) {
            throw (IllegalArgumentException) 
                new IllegalArgumentException("illegal jdbc type name : " + typeName).initCause(e);
        }
    }
    public int getType() {
        return type;
    }
    public String toString() {
        return typeName;
    }
}

と,

package study;
import java.beans.PropertyEditorSupport;

public class JdbcTypeEditor extends PropertyEditorSupport {
    public void setAsText(String text) throws IllegalArgumentException {
        setValue(new JdbcType(text));
    }
    public String getAsText() {
        return getValue().toString();
    }
}

このPropertyEditorfactory.xmlに組み込みます.このあたりの話は「Property Editorだよ」を参照.
ということで,このJdbcTypeを使うSqlUpdateの派生クラスを作成.

package study;
import javax.sql.DataSource;
import org.springframework.jdbc.object.SqlUpdate;

public class MySqlUpdate extends SqlUpdate {
    public MySqlUpdate() {
        super();
    }
    public MySqlUpdate(DataSource ds, String sql) {
        super(ds, sql);
    }
    public MySqlUpdate(DataSource ds, String sql, JdbcType jdbcTypes) {
        super(ds, sql, toIntArray(jdbcTypes));
    }
    public MySqlUpdate(DataSource ds, String sql, JdbcType jdbcTypes, int maxRowsAffected) {
        super(ds, sql, toIntArray(jdbcTypes), maxRowsAffected);
    }
    public void setJdbcTypes(JdbcType jdbcTypes) {
        setTypes(toIntArray(jdbcTypes));
    }
    protected static int toIntArray(JdbcType jdbcTypes) {
        int types = new int[jdbcTypes.length];
        for (int i = 0; i < jdbcTypes.length; ++i) {
            types[i] = jdbcTypes[i].getType();
        }
        return types;
    }
}

うーみゅ,My〜っていうクラス名は使いたくなかったなぁ.でも適当な名前が思いつかない... 心より恥じる.
そして,例のFoo(まぁこの名前もMy〜と同じくらい心より恥じるですが)を修正.

package study;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.object.SqlQuery;
import org.springframework.jdbc.object.SqlUpdate;

public class Foo {
    private DataSource dataSource;
    private SqlQuery sqlQuery;
    private SqlUpdate sqlUpdate;
    public DataSource getDataSource() {
        return dataSource;
    }
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    public SqlQuery getSqlQuery() {
        return sqlQuery;
    }
    public void setSqlQuery(SqlQuery sqlQuery) {
        this.sqlQuery = sqlQuery;
    }
    public SqlUpdate getSqlUpdate() {
        return sqlUpdate;
    }
    public void setSqlUpdate(SqlUpdate sqlUpdate) {
        this.sqlUpdate = sqlUpdate;
    }
    /**
     * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()
     */
    public void createTable() {
        JdbcTemplate jt = new JdbcTemplate(dataSource);
        jt.execute("create table model (name varchar, primary key(name))");
    }
    /**
     * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()
     */
    public void insert() {
        sqlUpdate.update("Yuri Ebihara");
        sqlUpdate.update("Yu Yamada");
        sqlUpdate.update("Moe Oshikiri");
    }
    /**
     * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute(readOnly=true)
     */
    public List getModels() {
        return sqlQuery.execute();
    }
}

例によってAttributes 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="sqlQuery" class="study.BeanSqlQuery">
        <property name="dataSource">
            <ref bean="dataSource"/>
        </property>
        <property name="sql">
            <value>select name from model</value>
        </property>
        <property name="beanClass">
            <value>study.Model</value>
        </property>
    </bean>

    <bean id="sqlUpdate" class="study.MySqlUpdate">
        <property name="dataSource">
            <ref bean="dataSource"/>
        </property>
        <property name="sql">
            <value>insert into model (name) values(?)</value>
        </property>
        <property name="jdbcTypes">
            <list>
                <value>VARCHAR</value>
            </list>
        </property>
    </bean>

    <bean id="foo" class="study.Foo" autowire="byName">
    </bean>
</beans>

実行用のクラスは前回と同じです.
それを実行!!

 BEGIN study.Foo#createTable()
 - Looking up default SQLErrorCodes for DataSource
 - Database product name found in cache {12241337}. Name is HSQL Database Engine
 END study.Foo#createTable() : null
 - Initiating transaction commit
 BEGIN study.Foo#insert()
 END study.Foo#insert() : null
 - Initiating transaction commit
 BEGIN study.Foo#getModels()
 END study.Foo#getModels() : [Moe Oshikiri, Yu Yamada, Yuri Ebihara]
 - Initiating transaction commit
 [Moe Oshikiri, Yu Yamada, Yuri Ebihara]

こんなもんですかね.