Spring Framework 入門記 JDBCその2 JdbcTemplate#query
まずは落ち穂拾いから.
前回の内容に間違いがありました.無念だ.
JdbcTemplate
のお手軽問い合わせメソッドについてですが,これらは戻り値がObject
のものとそれ以外でメソッドのシグネチャが異なります.まずObject
以外については,
-
queryForXxx(String sql)
queryForXxx(String sql, Object[] args)
の2種類があり,Xxx
の部分はInt
・Long
・List
の3種類があります.
一方Object
を返すメソッドとしては,
-
queryForObject(String sql, Class requiredType)
queryForObject(String sql, Object[] args, Class requiredType)
の2種類があります.
ちょっと混乱していました.心より恥じる.
気を取り直して,ここから今日の学習です.
今回もJdbcTemplate
の学習を続けます.まだ見ていないことが結構あるようなので.
前回書いたように,JdbcTemplate
は豊富なメソッドを持っています.publicな問い合わせメソッドだけでも16個もあります.これらをもう少し詳しく見ていくことにします.
前回は,queryForInt(String)
のような使いやすそうなメソッドを見たわけですが,それではすまない場合も多いでしょう.そのような場合に備えて,JdbcTemplate
の問い合わせメソッドは様々にカスタマイズできるようになっています.
まずは,ResultSet
の扱いをカスタマイズする方法を見てみましょう.それには,
RowCallbackHandler
というinterface
をimplements
したclass
を用意して,JdbcTemplate
の
-
List query(String sql, RowCallbackHandler rch)
などに渡します.
このRowCallbackHandler
はResultSet
を扱うためのもので,結果セットの行ごとにJdbcTemplate
からコールバックされる,次のメソッドを持っています.
-
void processRow(ResultSet rs)
引数でResultSet
を渡されますが,そのnext()
を呼び出すのはJdbcTemplate
の役割です.
ところで,このメソッドはvoidなんですね.行を処理した結果はどうするの?
このあたり,ちょっとイマイチな感じなのですが,実はRowCallbackHandler
の実装クラスが
ResultReader
というinterface
もimplements
していれば,そのメソッドである
-
List getResults()
を通じてList
を返すことができます.ということは,自分でList
を用意するのですね.てっきり,行ごとのインスタンスを返せば,それをJdbcTemplate
がList
に加えてくれるのだと思っちゃいました.無念だ.
なお,RowCallbackHandler
の実装クラスがResultReader
をimplements
していなければ,query(String, RowCallbackHandler)
などのメソッドはnull
を返します.
ということで,事実上RowCallbackHandler
とResultReader
は同時にimplements
する必要がありますね.
これで十分とは限りません.結果をList
以外で取得したい場合もあるでしょう.そんな場合でも,JdbcTemplate
を利用することができます.それには,
ResultSetExtractor
というinterface
をimplements
したclass
を用意して,JdbcTemplate
の
-
Object query(String sql, ResultSetExtractor rse)
などに渡します.
このResultSetExtractor
は,文字通りResultSet
を展開するもので,次のメソッドを持っています.
-
Object extractData(ResultSet rs)
このメソッドは,RowCallbackHandler#processRow(ResultSet)
と異なり,問い合わせごとに一回だけ呼び出されます.ResultSet#next()
を呼び出すのもこのメソッドの役割になります.
ということで,ResultSet
をどう扱うかで,JdbcTemplate
のメソッドを次のように使い分ければいいようです.
List queryForList(String sql)
- 問い合わせ結果を
Map
のList
で欲しい場合に使う. List query(String sql, RowCallbackHandler)
- 問い合わせ結果を任意の型の
List
で欲しい場合に使う. Object query(String sql, ResultSetExtractor
- 問い合わせ結果を任意の型で欲しい場合に使う.
ということになります.
さて,カスタマイズしたくなるのはResultSet
の扱いだけとは限りません.もしかすると,PreparedStatement
の扱い方もカスタマイズしたくなるかもしれません.
例えばObject
の配列を作りたくない場合.そんな場合は,
PreparedStatementSetter
というinterface
をimplements
したclass
を用意して,JdbcTemplate
の
-
Object query(String sql, PreparedStatementSetter pss, RowCallbackHandler)
などに渡します.
このinterface
は次のメソッドを持っています.
-
void setValues(PreparedStatement ps)
ここで,好きなようにPreparedStatement
にパラメータを設定できます.
それでも十分ではない場合もあるかもしれません.例えばPreparedStatement
をキャッシュしたりとか.そんな場合は,
PreparedStatementCreator
というinterface
をimplements
したclass
を用意して,JdbcTemplate
の
-
Object query(PreparedStatementCreator psc, RowCallbackHandler)
などに渡します.
このinterface
は次のメソッドを持っています.
-
PreparedStatement createPreparedStatement(Connection conn)
ここで,好きなようにPreparedStatement
を用意して返せばいいわけですね.
ということで,PreparedStatement
をどう扱うかで,JdbcTemplate
のメソッドを次のように使い分ければいいようです.
List query(String sql, Object[] args, RowCallbackHandler rch)
- パラメータを
Object
の配列で渡せる場合に使う. List query(String sql, PreparedStatementSetter pss, RowCallbackHandler rch)
- パラメータを
Object
の配列で渡したくない場合に使う. List query(PreparedStatementCreator psc, RowCallbackHandler rch)
PreparedStatement
の取得方法をカスタマイズしたい場合に使う.
なお,これらのメソッドの第3引数は,ResultSetExtractor
を受け取るものも用意されています.
ということで,最強(?)の問い合わせメソッドは
-
Object query(PreparedStatementCreator psc, ResultSetExtractor rse)
です.自分でPreparedStatement
を用意して,自分でResultSet
を処理します.
うーむ,それでもJdbcTemplate
を使うメリットって?
おぉ,そうだ! 例外がSQLException
じゃなくなるんだ! 後は... そうだ! ResultSet#close()
を自分で呼び出さなくてもいいんだ!
さて,こんな感じでいろいろなメソッドがあるわけですから,一通り使うべきだという気持ちもないわけではないのですが,あまりに細かいのばかりなので,メソッド一つだけ使うことで済ませたいと思います.心より恥じる.
前回のサンプルで使っているテーブルは,key
とvalue
という文字列型のカラムを持っています.ということで,このテーブルの問い合わせ結果をProperties
にして返すということをやってみましょう.
まずはいつものFoo
.例によってメタデータによる宣言的トランザクションを使います.
package study; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; public class Foo { private DataSource dataSource; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * @@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(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; } } ); } /** * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute(readOnly=true) */ public void insert(String key, String value) { JdbcTemplate jt = new JdbcTemplate(dataSource); jt.update("insert into pair (key, value) values(?, ?)", new Object[] { key, value }); } }
これをAttribute Compilerで処理します.私はいつもこれを忘れて痛い目に遭います.無念だ.
定義ファイルは前回と同じです.
後は実行用クラス.
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("Yuri", "Ebihara"); foo.insert("Akiko", "Yada"); 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(Yuri, Ebihara) - Looking up default SQLErrorCodes for DataSource - Database product name found in cache {534353}. Name is HSQL Database Engine END study.Foo#insert(Yuri, Ebihara) : null - Initiating transaction commit BEGIN study.Foo#insert(Akiko, Yada) - Looking up default SQLErrorCodes for DataSource - Database product name found in cache {534353}. Name is HSQL Database Engine END study.Foo#insert(Akiko, Yada) : null - Initiating transaction commit BEGIN study.Foo#query() - Looking up default SQLErrorCodes for DataSource - Database product name found in cache {534353}. Name is HSQL Database Engine END study.Foo#query() : {Yuri=Ebihara, Akiko=Yada} - Initiating transaction commit {Yuri=Ebihara, Akiko=Yada}
こんな感じということで.
次はJdbcTemplate#execute()
シリーズかなぁ.