複数行のinsert

配列とかListを引数に渡せば、勝手にその配列数分ループして実行してくれます

Dao007.java
public interface Dao007 {
	@SqlBatch("insert into table001 (id, name) values ((select count(*)+1 from table001), :first || '-' || :last)")
	int[] insertBatch(
		@Bind("first") List<String> firstNames,
		@Bind("last") String lastName
	);

	@SqlQuery("select name from table001")
	List<String> findNames();
}
Sample013.java
public static void main(String[] args) {
	String url = "jdbc:postgresql://192.168.52.128/jdbi";
	DBI dbi = new DBI(url, "jdbi_user", "jdbi_pass");

	List<String> firstNames = Arrays.asList(new String[]{"Firstname1", "Firstname2", "Firstname3"});
	String lastName = "Lastname";

	Dao007 dao007 = dbi.onDemand(Dao007.class);
	int[] result = dao007.insertBatch(firstNames, lastName);
	System.out.println("result.length = " + result.length); // 3
	for( int i : result )
	{
		System.out.println("i = " + i); // 1
	}

	for( String name : dao007.findNames() )
	{
		System.out.println("name = " + name);
	}
}

SqlBatch アノテーションで指定したSQLを引数のサイズ分実行してくれます。
実行結果は配列にして返してくれます。この例では3回実行してますので、

  • result[0]:1回目の実行結果
  • result[1]:2回目の実行結果
  • result[2]:3回目の実行結果

となりますね。insert文はinsertした行数が返ることになってまして、それぞれのSQLでは1行だけinsertすることになりますから、全部 1 ですね。
実行結果が必要無ければ、void insertBatch と宣言すればよいです。

ObjectAPI形式でinsert文やupdate文でモデルを指定する

前回はselectの結果を任意のモデルに変換する処理をやってましたが、当然insertやupdateでもできます。

Dao006.java
public interface Dao006 {
	@SqlQuery("select name from table001 where id = :id")
	String findNameById(@Bind("id") int id);

	@SqlUpdate("insert into table001 (id, name) values (:id, :name)")
	void insert(@Bind("id") int id, @Bind("name") String name);

	@SqlUpdate("update table001 set name = :name where id = :id")
	int update(@BindBean Table001 m);
}
Sample012.java
public static void main(String[] args) {
	String url = "jdbc:postgresql://192.168.52.128/jdbi";
	DBI dbi = new DBI(url, "jdbi_user", "jdbi_pass");

	Dao006 dao006 = dbi.onDemand(Dao006.class);

	dao006.insert(100, "dao006 insert");
	System.out.println("id = 100 : " + dao006.findNameById(100));

	Table001 table001 = new Table001(100, "dao006 update");
	dao006.update(table001);
	System.out.println("id = 100 : " + dao006.findNameById(100));
}

BindBean アノテーションを付ければうまいことやってくれます。

ObjectAPI形式で戻り値を自分でハンドリングする その2

JDBIを普通に使ってると、DaoとModelが別ファイルになっちゃうのですが、一応こうやれば同じクラスで実装できます。でもあまりよろしく無さそうですね。

Table002.java
public class Table002
{
	private int id;
	private String name;

	public Table002(){}

	public Table002(int id, String name)
	{
		this.id = id;
		this.name = name;
	}

	public static class Table002Mapper implements  ResultSetMapper<Table002>
	{
		@Override
		public Table002 map(int index, ResultSet r, StatementContext ctx) throws SQLException
		{
		    return new Table002(r.getInt("id"), r.getString("name"));
		}
	}

	@SqlQuery("select * from table001")
	@RegisterMapper(Table002Mapper.class)
	public List<Table002> findAll(){return null;}

	public int getId()
	{
		return id;
	}

	public void setId(int id)
	{
		this.id = id;
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}
}
Sample011.java
public static void main(String[] args) {
	String url = "jdbc:postgresql://192.168.52.128/jdbi";
	DBI dbi = new DBI(url, "jdbi_user", "jdbi_pass");

	Table002 table002 = dbi.onDemand(Table002.class);
	List<Table002> rows = table002.findAll();
	System.out.println("rows count = " + rows.size());
	for( Table002 row : rows )
	{
		System.out.println("id = " + row.getId() + ", name = " + row.getName());
	}
}

Table002.findAll() が全てなのですが、普通の実体クラスなので実装を書かないとコンパイルエラーになります。とは言え、このメソッドのコードは全く実行されませんので、return null; とだけ書いてます。
ちなみに、Table002 を abstract にすると、今度は new できないので、やっぱり上手くいきません。やっぱり素直にDaoとModelを分割するのが良さそうです。

ObjectAPI形式で戻り値を自分でハンドリングする その1

前回の続きでObjectAPI形式の場合に自分で戻り値の型をマッピングする処理を書いてみましょう

Dao004.java
public interface Dao004 {
	public static class SimpleMapper implements ResultSetMapper<Map<String,Object>>
	{
		public Map<String,Object> map(int index, ResultSet r, StatementContext ctx) throws SQLException
		{
			Map<String,Object> result = new HashMap<>();
			result.put("id", r.getInt("id"));
			result.put("name", r.getString("name"));
			return result;
		}
	}

	@SqlQuery("select * from table001")
	@RegisterMapper(SimpleMapper.class)
	List<Map<String,Object>> findAll();
}
Sample009.java
public static void main(String[] args) {
	String url = "jdbc:postgresql://192.168.52.128/jdbi";
	DBI dbi = new DBI(url, "jdbi_user", "jdbi_pass");

	Dao004 dao004 = dbi.onDemand(Dao004.class);
	List<Map<String,Object>> rows = dao004.findAll();
	System.out.println("rows count = " + rows.size());
	for( Map<String,Object> row : rows )
	{
		System.out.println("id = " + row.get("id") + ", name = " + row.get("name"));
	}
}

いくつか説明しないといけませんね。

  • SimpleMapper
    • これが自分で戻り値の型を変換する処理のメイン部分です
    • ResultSetMapper インターフェイスの定義を見ればわかりますが、map メソッドを実装しなければなりません
    • mapメソッドはselect文の実行結果、1行毎に呼ばれる仕組みになってますので。ResultSet が1行分のデータですね。
    • ここで検索結果を取り出して適当に処理して、自分の欲しい型に変換して返してやればOKです
  • RegisterMapper アノテーション
    • このアノテーションで指定したResultSetMapperに検索結果の変換処理を委譲できるようになってます。
    • 戻り値がList(正確にはIterableなクラス)なら、SimpleMapper で変換した戻り値をJDBIが気を利かせてListにしてくれます

戻り値が Map 型だとその後取り出す側は使いにくいので、次回は戻り値を自分で定義したモデルクラスにする方法をご紹介します

ObjectAPI形式で戻り値を勝手に推測してもらう

今までもずっと使ってたんですけど、ObjectAPI形式の場合は @SqlQuery アノテーションを付けたメソッドの戻り値によって、JDBIが勝手に推測して相応の処理をしてくれます。

Dao003.java
public interface Dao003 {
	@SqlQuery("select id from table001")
	List<Integer> findIds();

	@SqlQuery("select name from table001")
	List<String> findNames();

	@SqlQuery("select * from table001")
	List<Map<String,Object>> findAll(); // 残念ながらこれはダメ
}
Sample008.java
public static void main(String[] args) {
	String url = "jdbc:postgresql://192.168.52.128/jdbi";
	DBI dbi = new DBI(url, "jdbi_user", "jdbi_pass");

	Dao003 dao003 = dbi.onDemand(Dao003.class);
	
	List<Integer> ids = dao003.findIds();
	System.out.println("ids count = " + ids.size());
	for( Integer id : ids )
	{
		System.out.println("id = " + id);
	}

	List<String> names = dao003.findNames();
	System.out.println("names count = " + names.size());
	for( String name : names )
	{
		System.out.println("name = " + name);
	}

	List<Map<String,Object>> rows = dao003.findAll();
	// ここで例外「org.skife.jdbi.v2.MappingRegistry$1: No mapper registered for java.util.Map」
	System.out.println("rows count = " + rows.size());
	for( Map<String,Object> row : rows )
	{
		System.out.println("id = " + row.get("id") + ", name = " + row.get("name"));
	}
}

List、List とも想像通り動作してくれますが、List> はダメでした。うーん、これは動いてくれてもよさそうなんですけどね・・。
じゃ、次回はどうやってこれを実装するかのご紹介です。

必要な時だけコネクションを張る

ObjectAPI形式だけを使用する場合、DBI.onDemand で勝手にopen、closeしてくれます。

Dao002.java
public interface Dao002 {
	@SqlUpdate("create table IF NOT EXISTS table002 (id int primary key, name varchar(100))")
	void createTable();

	@SqlUpdate("insert into table002 (id, name) values ((select count(*)+1 from table002), :name)")
	void insert(@Bind("name") String name);

	@SqlQuery("select name from table002 where id = :id")
	String findNameById(@Bind("id") int id);

	void close();
}
Sample007.java
public static void main(String[] args) {
	String url = "jdbc:postgresql://192.168.52.128/jdbi";
	DBI dbi = new DBI(url, "jdbi_user", "jdbi_pass");

	Dao001 dao001 = dbi.onDemand(Dao001.class);
	dao001.createTable();
	dao001.insert("dao001");

	Dao002 dao002 = dbi.onDemand(Dao002.class);
	dao002.createTable();
	dao002.insert("dao002");
}

デバッガで1行ずつ実行しながらコネクションの接続状況を確認すると、どこで止めてもコネクションが確認できません。実は、SQLを実行する直前に接続して、実行直後にすぐ切断してみるみたいです。
SqlObject.java の174行目「ding.retain(method.toString());」でコネクションを作成し、そのfinally節で「ding.release(method.toString());」として切断してます(実際のコードはまだもう少し先の処理ですが)。
トランザクションが必要無ければこれでもいいでしょうけど、エラー時は rollback するのが必須ですから、ある限定的な場合においてはコードがキレイになっていいけど、あんまり使いどころが無いような気がします

ある指定した列だけListで取得する

例えば name 列だけ一覧でほしいという場合、前回の方法だと一度全部取得してから、name列だけを再度取得する、という2段階になるので少し面倒です。そんな時に使うテクニック

Sample006.java
public static void main(String[] args) {
	String url = "jdbc:postgresql://192.168.52.128/jdbi";
	DBI dbi = new DBI(url, "jdbi_user", "jdbi_pass");

	List<String> rows = dbi.withHandle(new HandleCallback<List<String>>() {
		@Override
		public List<String> withHandle(Handle handle) throws Exception {
			return handle.createQuery("select id, name from table001") // ※1
					.map(new StringMapper(2)) // ※2
					.list(); // ※3
		}
	});

	System.out.println("total = " + rows.size());
	for( int i=0; i<rows.size(); ++i )
	{
		System.out.println("[" + i + "]: " + rows.get(i));
	}
}

これは追加で説明が必要ですね。

  • ※1
    • これはそんなに難しくないですが、「handle.createQuery」を使用してますのでそこだけ注意。前回は「handle.select」でした
  • ※2
    • ここが肝となるところです。
    • StringMapperは、結果をStringに変換するクラスで、ResultSetMapper、TypedMapper のサブクラスとなってます。クラス名から大体の動作は想像つきますね。他の変換クラスは最後に一覧にしておきます。もちろん自分で定義することも可能。
    • 引数の「2」というのは、「2列目」という意味です。select文が id, name となってますので、1列目はid、2列目がname。なので「2」を指定してます。
    • ちなみに select 文を「select name from table001」とした場合は、「new StringMapper(1)」でもいいですが、1列目は予め想定されてまして、「StringMapper.FIRST」とできます。これならnewしなくていいので、少しだけ速いです。少しだけ・・
    • map の戻り値は引数のResultSetMapper の T 型になるんですが、StringMapper は で実体化されてるので、String型になります。
  • ※3
    • map でString型になったので、その型のListが返ることになります
    • もう少し分解すると以下のようになってます
      • Query> q = handle.createQuery(...);
      • Query q2 = q.map(new StringMapper(2));
      • List list = q2.list();

※予め定義されている変換クラス

  • BigDecimalMapper
  • BooleanMapper
  • ByteArrayMapper
  • ByteMapper
  • DoubleMapper
  • FloatMapper
  • IntegerMapper
  • LongMapper
  • ShortMapper
  • StringMapper
  • TimestampMapper
  • URLMapper