Spring Framework 入門記 JDBCその5 SqlQuery
かなーり間が空いてしまって久々の入門記です.
前回までは,JdbcTemplate
という随分と低水準のユーティリティクラスを必要以上に眺め続けてしまったのですが,今回からはその上位に構築されている,もう少しコンポーネントらしいものを見ていきます.
まずは問合せ用のコンポーネントである,
SqlQuery
です.これは抽象クラスなのですが,次のプロパティを持っています.
-
dataSource
sql
本当は他にもjdbcTemplate
とかあるのですが,使いそうもないので無視しましょう.
上記のプロパティを設定すると,問合せを実行することが出来ます.そのためのメソッドがこれまた豊富にあるんですが,大雑把に言うと,結果をList
で受け取るexecute()
の仲間が12個,結果をObject
で受け取るfindObject()
の仲間が10個,用意されています.なんていうか,そのぉ,ダイエットしろよ...
とりあえず,execute()
の仲間を見ていきましょう.
一番基本的なのは,
-
List execute()
です.これは,問合せSQLにパラメータが含まれていない場合に使うことが出来ます.
それから,パラメータとして1〜2個のint
を持つ問合せSQLを実行するために,
-
List execute(int)
List execute(int, int)
が用意されています.
さらに,パラメータとして1個のlong
やString
を持つ問合せSQLを実行するために,
-
List execute(long)
List execute(String)
も用意されています.
そして,任意のパラメータを持つ問合せSQLを実行するために,
-
List execute(Object[])
が用意されています.ふーっ.
あって困るものではないものの,最初と最後のだけでも十分な気もするんですが...
これで6個,約半分です.残りの半分はというと,追加のコンテキストとしてMap
を渡せるもので,
-
List execute(Map)
List execute(int, Map)
List execute(int, int, Map)
List execute(long, Map)
List execute(String, Map)
List execute(Object[], Map)
が用意されています.このコンテキストがどういうものかというと... よく分かりません.心より恥じる.
特に明確な使い方が想定されているわけではなくて,もしSqlQuery
の派生クラスで必要な情報があれば,このコンテキストを使って渡せるよ,ということだと思います.
findObject()
の仲間もほとんど同様です.
さて,SqlQuery
がexecute()
で返すList
の要素,あるいはfindObject()
が返すObject
がどのようなものかというと,それは実装されていません.抽象クラスなので.
ということで,
MappingSqlQuery
MappingSqlQueryWithParameters
という派生クラスが用意されています.
用意されているのですが,これもまた抽象クラスです.とほほ... こんなんばっかだなぁ.無念だ.
こいつらはそれぞれ,
-
Object mapRow(ResultSet rs, int rowNum)
Object mapRow(ResultSet rs, int rowNum, Object[] parameters, Map context)
という抽象メソッドを持っていて,派生クラスでResultSet
を適当なオブジェクトにして返せということみたいです.
うーむ.そこが一番面倒なんだけどなぁ.そこをやってくれるクラスは用意されていないみたい...
さすがSpring,太っていても人に優しくはないのですね.S2だったら本人自らDependency Injectorのひがさんが用意してくれているのに...
ということで,ちょっとは使いやすいSqlQuery
の派生クラスを作ってみることにしましょう.
ResultSet
のカラムを対応するBeanのプロパティに設定するというのは頻繁に行われていることだと思います.そんなわけで,Apache Jakarta Commons DbUtilsではBeanProcessor
というクラスが用意されています.ただしこれ,1.1からのものなので,現在リリース済みの1.0には含まれていません.Nightly BuildsもしくはCVSから持ってくる必要があります.
そのBeanProcessor
を使って,プロパティで指定されたBeanクラスのインスタンスを返すようなSqlQuery
の派生クラス,BeanSqlQuery
です.
package study; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.apache.commons.dbutils.BeanProcessor; import org.springframework.jdbc.core.ResultReader; import org.springframework.jdbc.object.SqlQuery; public class BeanSqlQuery extends SqlQuery { private Class beanClass; public BeanSqlQuery() { } public BeanSqlQuery(DataSource ds, String sql) { super(ds, sql); } public Class getBeanClass() { return beanClass; } public void setBeanClass(Class beanClass) { this.beanClass = beanClass; } protected ResultReader newResultReader(int rowsExpected, Object[] parameters, Map context) { return new ResultReaderImpl(rowsExpected); } protected class ResultReaderImpl implements ResultReader { private List results; private BeanProcessor beanProcessor = new BeanProcessor(); public ResultReaderImpl(int rowsExpected) { this.results = (rowsExpected > 0) ? (List) new ArrayList(rowsExpected) : (List) new LinkedList(); } public void processRow(ResultSet rs) throws SQLException { this.results.add(beanProcessor.toBean(rs, beanClass)); } public List getResults() { return this.results; } } }
これは,beanClass
プロパティで指定されたクラスのインスタンスを作成し,それが持つプロパティと一致するカラムの値を設定します.実際にその作業を行うのは,ResultReader
をimplements
した内部クラスです.このつくりはMappingSqlQuery
のパクリです.てへっ.
ということで,Beanを用意しなくてはなりません.
かなーり手抜きして,プロパティ1つだけのクラスを用意.
package study; public class Model { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { return name; } }
このModel
はMVCのModel
ではなくて,雑誌モデルのModel
です.心より恥じる.
ということで,おなじみのFoo
をBeanSqlQuery
およびModel
を使うように修正.ついでにテーブルもカラム1つだけにしちゃいました.
package study; import java.util.List; import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; public class Foo { private DataSource dataSource; private BeanSqlQuery beanSqlQuery; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public BeanSqlQuery getBeanSqlQuery() { return beanSqlQuery; } public void setBeanSqlQuery(BeanSqlQuery beanSqlQuery) { this.beanSqlQuery = beanSqlQuery; } /** * @@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() { JdbcTemplate jt = new JdbcTemplate(dataSource); jt.update("insert into model (name) values('Yuri Ebihara')"); jt.update("insert into model (name) values('Yu Yamada')"); jt.update("insert into model (name) values('Moe Oshikiri')"); } /** * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute(readOnly=true) */ public List getModels() { return beanSqlQuery.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="beanSqlQuery" 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="foo" class="study.Foo" autowire="byName"> </bean> </beans>
実行用のクラスを作って,
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.getModels()); ref.release(); } catch (Throwable e) { e.printStackTrace(); } } }
これを実行!
BEGIN study.Foo#createTable() - Looking up default SQLErrorCodes for DataSource - Database product name found in cache {31578843}. Name is HSQL Database Engine END study.Foo#createTable() : null - Initiating transaction commit BEGIN study.Foo#insert() - Looking up default SQLErrorCodes for DataSource - Database product name found in cache {31578843}. Name is HSQL Database Engine 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]
うんうん,いい感じ.
ということで,SqlQuery
を使えるようになりました.次は更新用のSqlUpdate
なのですが,明日は「arton&いがぴょん合コン」,その後週末は「ゴールデンウィークの誓い」消化のため,次回の入門記は週明けになってしまうと思います.心より恥じる.