Apache Commons DbUtils
Apache CommonsのDbUtilsはJDBCの補助ライブラリで、よく書くテンプレを簡略化することができます。DbUtilsを使うことでConnectionのcloseし忘れを防止することができたりもします。自分はQMACloneのデータベース部分にDbUtilsを使っています。
準備
解説では以下のMySQLのテーブルとJavaのclassを使います。
CREATE TABLE person ( id SERIAL PRIMARY KEY, name TEXT );
public class Person { public long id; public String name; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } };
初期化
DbUtilsでJDBCを扱うときはQueryRunnerを使用します。自分はApache CommonsのDBCPで作成した接続プールと一緒に使っています。
QueryRunner runner = new QueryRunner(databaseConnectionPool.getDataSoure());
INSERT文・UPDATE文・REPLACE文・DELETE文
INSERT文・UPDATE文・REPLACE文・DELETE文はQueryRunner#update()を使って実行します。update()は第一引数にPreparedStatementに渡すものと同じ書式のSQL文、第二匹数以降にSQL文の"?"に当てはめる値を順に指定していきます。
runner.update("INSERT person SET name = ?", "hoge"); runner.update("UPDATE person SET name = ? WHERE id = ?", "fuga", 1); runner.update("DELETE person WHERE id = ?", 1);
update()の内部では
- Connectionの確立
- PreparedStatementへのパラメーター指定
- SQL文の実行
- Connectionのclose
が行われています。自分でこれらの動作を書く必要がない分、コードが簡潔になっています。また、接続のcloseし忘れの心配もありません。
バッチ処理
INSERT文・UPDATE文・REPLACE文・DELETE文はQueryRunner#batch()を使ってバッチ処理することができます。batch()には第一引数にupdate()と同様にSQL文、第二引数にはSQL文の"?"に当てはめる値を順に格納したObjectの配列、Object[]を渡します。
List<Object[]> params = new ArrayList<Object[]>(); params.add(new Object[]{"hoge"}); params.add(new Object[]{"fuga"}); runner.batch("INSERT person SET name = ?", params.toArray(new Object[0][]));
batch()の内部ではupdate()と同様に
- Connectionの確立
- PreparedStatementへのパラメーター指定
- バッチ追加
- バッチ実行
- Connectionのclose
が行われています。
SELECT文
SELECT文はQueryRunner#query()を使って実行します。ResultSetHandler
Person person = runner.query("SELECT * FROM person WHERE id = ?", new ResultSetHandler<Person>() { @Override public Person handle(ResultSet rs) throws SQLException { if (rs.next()) { Person person = new Person(); person.id = rs.getLong("id"); person.name = rs.getString("name"); return person; } return null; } }, 1); List<Person> people = runner.query("SELECT * FROM person", new ResultSetHandler<List<Person>>() { @Override public List<Person> handle(ResultSet rs) throws SQLException { List<Person> people = new ArrayList<>(); while (rs.next()) { Person person = new Person(); person.id = rs.getLong("id"); person.name = rs.getString("name"); people.add(person); } return people; } });
query()の内部では
- Connectionの確立
- PreparedStatementへのパラメーター指定
- SQL文の実行
- ResultSetのclose
- Connectionのclose
が行われています。ResultSetHandlerを使用する場合、handle()には何も操作がされてない状態のResultSetが渡されます。
AbstractListHandler
query()の戻り値をList
List<Person> people = runner.query("SELECT * FROM person", new AbstractListHandler<Person>() { @Override protected Person handleRow(ResultSet rs) throws SQLException { Person person = new Person(); person.id = rs.getLong("id"); person.name = rs.getString("name"); return person; } });
AbstractListHandler
AbstractKeyedHandler
query()の戻り値をMap
Map<Long, Person> people = runner.query("SELECT * FROM person", new AbstractKeyedHandler<Long, Person>() { @Override protected Person createRow(ResultSet rs) throws SQLException { Person person = new Person(); person.id = rs.getLong("id"); person.name = rs.getString("name"); return person; } @Override protected Long createKey(ResultSet rs) throws SQLException { return rs.getLong("id"); } });
AbstractListHandler
ColumnListHandler
ある列のListが欲しい場合はColumnListHandlerを使うこともできます。ただしquery()の戻り値がList
List<Object> people = runner.query("SELECT * FROM person", new ColumnListHandler(name));
ColumnListHandlerはコンストラクタで指定した列の値のListを返してくれます。引数を省略した場合は1列目の結果のListを返してくれるので、以下のような簡潔な書き方もできます。
List<Object> people = runner.query("SELECT name FROM person", new ColumnListHandler());
SQLのint(10)型がJavaのLong型で返されてしまうため、自分は以下のようにカスタマイズしたColumnListHandlerを使っています。
public class ColumnListHandler<T> extends AbstractListHandler<T> { private final Class<T> c; public ColumnListHandler(Class<T> c) { this.c = c; } protected T handleRow(ResultSet rs) throws SQLException { Object object = rs.getObject(1); if (c == object.getClass()) { return c.cast(object); } else if (c == Integer.class && object.getClass() == Long.class) { // SQL の int(10) 型を Java の Long 型で返すため、特別処理 return c.cast((Integer) (int) (long) (Long) object); } else { throw new IllegalArgumentException(String.format("%s から %s にキャストできません", object .getClass().toString(), c.toString())); } } }
ScalarHandler
ResultSetの初めの一行の特定の列の値が欲しい場合はScalarHandlerを使うことができます。
long count = (long) runner.query("SELECT COUNT(*) FROM person", new ScalarHandler());
ScalarHandlerはResultSetの初めの行の特定の列の値を返してくれます。自分は上のようにCOUNT()やMAX()の値を取り出す時に使っています。
BeanHandler・BeanListHandler
Beansに対応したアクセサメソッドが定義されているclassであれば、自分でResultSetHandlerを書かずに値を取り出すことができます。
Person person = runner.query("SELECT * FROM person WHERE ID = ?", new BeanHandler<>( Person.class), 1); List<Person> porple = runner.query("SELECT * FROM person", new BeanListHandler<>( Person.class));
BeanHandler・BeanListHandlerはBeanProcessorを使ってResultSetを指定したクラスのオブジェクトに変換して取り出してくれます。楽です。ただし、アクセサを書かなければならないのだ若干億劫です。
リンク
- JDBC Utility Component http://commons.apache.org/dbutils/
- DbUtilsめも http://www008.upp.so-net.ne.jp/kikuta/dbu/
- @IT:Java TIPS -- DbUtilsで結果セットをJavaBeansにマッピングする http://www.atmarkit.co.jp/fjava/javatips/101java013.html
- 「薄い」JavaのO/Rマッパーの紹介 - DbUtils、Persist、Butterfly Persistence - public static void main http://d.hatena.ne.jp/Kishi/20081212/1229076102
- DbUtils でアンダバー区切りをハンガリアン記法にマッピング | もじゃの日記 http://blog.utils.jp/2008/04/dbutils.html