insertした時のidを取得する その2

一応こんなこともできるというご紹介で、前回は1レコードだけinsertしたIDを取得する処理でしたが、今回は複数レコード対応です。
insert select で複数レコード登録した際に、それらのIDがListで返ってきます。

Dao017.java
public interface Dao018 {
	@SqlUpdate("CREATE TABLE IF NOT EXISTS table004 (name text)")
	void createTable();

	@SqlUpdate("insert into table004 (name) values (:name)")
	int insert(@Bind("name") String name);

	@SqlUpdate("insert into table003(name) select name from table004")
	@GetGeneratedKeys
	List<Integer> insertSelect();
}
Sample027.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");
	Dao018 dao = dbi.onDemand(Dao018.class);

	dao.createTable();

	dao.insert("name1");
	dao.insert("name2");

	// Daoに指定するのはダメみたい
	try {
		List<Integer> ids = dao.insertSelect(); // JDBI 2.59 ではここで例外
		for( Integer id : ids )
		{
			System.out.println("id = " + id);
		}
	} catch(Throwable e) {
		e.printStackTrace();
	}

	// でも、Update オブジェクトから executeAndReturnGeneratedKeys を呼べばOK
	dbi.withHandle(new HandleCallback<Void>() {
		@Override
		public Void withHandle(Handle h) throws Exception
		{
			String sql = "insert into table003(name) select name from table004";
			Update updateStatement = h.createStatement(sql);
			List<Integer> ids = updateStatement.executeAndReturnGeneratedKeys(IntegerMapper.FIRST).list();
			for( Integer id : ids )
			{
				System.out.println("id = " + id);
			}

			return null;
		}
	});
}

ソースを見てもらえればわかる通り、ObjectAPI形式の GetGeneratedKeys アノテーションではダメみたいです。
でも、JDBIのソース(V 2.59)を見ると、

@Override
public <ContainerType> ContainerType list(Class<ContainerType> containerType)
{
//    return containerFactoryRegistry.lookup(containerType).create(Arrays.asList(list()));
    throw new UnsupportedOperationException("Not Yet Implemented!");
}

となってまして、対応する気はあるみたいです。

insertした時のidを取得する その1

よくあるRESTのパターンとして、IDはシーケンスを設定しておき、APIから指定された内容を登録したらIDを返す、というもの。JDBIではどうやればいいのかというサンプルです。

Dao017.java
public interface Dao017 {
	@SqlUpdate("CREATE SEQUENCE seq")
	void createSequence();

	@SqlUpdate("CREATE TABLE IF NOT EXISTS table003 (id int DEFAULT nextval('seq'), name text, CONSTRAINT id_pkey PRIMARY KEY ( id ))")
	void createTable();

	@SqlUpdate("insert into table003 (name) values (:name)")
	@GetGeneratedKeys
	int insert(@Bind("name") String name);

	@SqlQuery("select name from table003 where id = :id")
	String findNameById(@Bind("id") int id);
}
Sample026.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");
	Dao017 dao = dbi.onDemand(Dao017.class);

	try {
		dao.createSequence(); // 2回目以降既に存在するエラーが起きるけど無視する
		dao.createTable();
	} catch(Throwable e) {
	}

	int id1 = dao.insert("name1");
	int id2 = dao.insert("name2");

	System.out.println("id1 = " + id1);
	System.out.println("id2 = " + id2);

	System.out.println("name of id1 = " + dao.findNameById(id1));
	System.out.println("name of id2 = " + dao.findNameById(id2));
}

ソースを見てもらえればわかる通り、GetGeneratedKeys アノテーションを付けるとinsert後、insertした時に割り振られたシーケンス値が返ってきます

トランザクション その10 DaoからHandleを取得する with Mixin

今まで、DBIオブジェクト or HandleオブジェクトからDaoを取得してきましたが、今度は逆にDaoからHandleオブジェクトを取得してみましょう。取得するには、「getHandle」メソッドを宣言すればいいのですが、今回は予めJDBI側で用意されているMixin用のinterfaceをextendsしてみます。別に使いはしませんが、一応今のとこ用意されている他のMixinも全部extendsしてみました。org.skife.jdbi.v2.sqlobject.mixins パッケージ配下にあります。

Dao016.java
public interface Dao016
	extends CloseMe, GetHandle, Transactional<Dao016>
{
	@SqlUpdate("insert into table001 (id, name) values (:id, :name)")
	int insert(@Bind("id") int id, @Bind("name") String name);
}
Sample025.java
private static Handle otherHandler = null;
private static Dao016 dao;

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

	otherHandler = dbi.open();
	otherHandler.insert("truncate table table001");

	txSuccess();
	txError();

	otherHandler.close();
}

public static void txSuccess()
{
	System.out.println("[0] : count = " + countTable001()); // 0
	try {
		dao.begin();

		dao.insert(1, "name1");
		System.out.println("[1] : count = " + countTable001()); // 0
		dao.insert(2, "name2");

		dao.getHandle().commit();
	} catch(Throwable e) {
		dao.getHandle().rollback();
		e.printStackTrace();
	}
	System.out.println("[2] : count = " + countTable001()); // 2
}

public static void txError()
{
	System.out.println("[0] : count = " + countTable001()); // 2
	try {
		dao.begin();

		dao.insert(3, "name3");
		System.out.println("[1] : count = " + countTable001()); // 2
		dao.insert(3, "name4"); // エラー

		dao.getHandle().commit();
	} catch(Throwable e) {
		dao.getHandle().rollback();
		e.printStackTrace();
	}
	System.out.println("[2] : count = " + countTable001()); // 2
}

public static int countTable001()
{
	return otherHandler.createQuery("select count(*) from table001").map(IntegerMapper.FIRST).first();
}

トランザクション その9 Daoからbegin〜commit〜rollbackを呼べるようにする

Handleから自前でトランザクション制御するのは以前やりましたが、Daoでもできますのでそのサンプルです。敢えてこの方法でトランザクション処理を書く必要は無さそうですけど、一応こんなこともできますよーという意味で。

Dao015.java
public interface Dao015 extends Transactional<Dao015> {
	@SqlUpdate("insert into table001 (id, name) values (:id, :name)")
	int insert(@Bind("id") int id, @Bind("name") String name);
}
Sample024.java
private static Handle otherHandler = null;
private static Dao015 dao;

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

	otherHandler = dbi.open();
	otherHandler.insert("truncate table table001");

	txSuccess();
	txError();

	otherHandler.close();
}

public static void txSuccess()
{
	System.out.println("[0] : count = " + countTable001()); // 0
	try {
		dao.begin();

		dao.insert(1, "name1");
		System.out.println("[1] : count = " + countTable001()); // 0
		dao.insert(2, "name2");

		dao.commit();
	} catch(Throwable e) {
		dao.rollback();
		e.printStackTrace();
	}
	System.out.println("[2] : count = " + countTable001()); // 2
}

public static void txError()
{
	System.out.println("[0] : count = " + countTable001()); // 2
	try {
		dao.begin();

		dao.insert(3, "name3");
		System.out.println("[1] : count = " + countTable001()); // 2
		dao.insert(3, "name4"); // エラー

		dao.commit();
	} catch(Throwable e) {
		dao.rollback();
		e.printStackTrace();
	}
	System.out.println("[2] : count = " + countTable001()); // 2
}

public static int countTable001()
{
	return otherHandler.createQuery("select count(*) from table001").map(IntegerMapper.FIRST).first();
}

特筆すべきは、Transactional をextendsしてるところだけですね。これにより、

  • begin
  • commit
  • rollback

を呼べるようになります。他にもいくつか便利メソッドがインジェクションされますので、org.skife.jdbi.v2.sqlobject.mixins.Transactional を見てください。
1つ注意点として、close メソッドは単にDao内に close を定義すればよかったですが、トランザクション関連のメソッドは Transactional インターフェイスを extends しないといけません。

トランザクション その8 例外を発生させずにRollbackしたい

今までトランザクション中に例外が発生すれば自動的にRollbackされることを確認してきましたが、例外が起きなくてもRollbackさせたい場合があるので、そのサンプルです。

Sample023.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");
	final Handle otherHandler = dbi.open();
	final Handle h = dbi.open();

	h.insert("truncate table table001");

	System.out.println("before table001 rows count = " + countTable001(h)); // 0

	try {
		h.inTransaction(new TransactionCallback<Void>()
		{
			@Override
			public Void inTransaction(Handle handle, TransactionStatus status) throws Exception
			{
				System.out.println("[0] : count = " + countTable001(otherHandler)); // 0

				handle.insert("insert into table001(id,name) values(101, 'name101')");

				System.out.println("[1] : count = " + countTable001(otherHandler)); // 0

				handle.attach(Dao010.class).noTxSuccessTest();

				System.out.println("[2] : count = " + countTable001(otherHandler)); // 0

				status.setRollbackOnly();

				return null;
			}
		});
	} catch(TransactionFailedException e) {
		e.printStackTrace(); // 例外発生
	}

	System.out.println("after table001 rows count = " + countTable001(h)); // 0

	h.close();
	otherHandler.close();
}

public static int countTable001(final Handle h)
{
	return h.createQuery("select count(*) from table001").map(IntegerMapper.FIRST).first();
}

前回までとほとんど同じですが、「status.setRollbackOnly();」を呼ぶことにより、Rollbackしてくれるようになります。
ただ1点注意が必要で、トランザクション中に例外が発生したわけではないですけど、トランザクションが失敗した例外(TransactionFailedException)が発生するので、catchしないとアボーンしちゃいます。

トランザクション その7

前回の続きです。ObjectAPI形式とFluent形式を融合させてトランザクション処理をしてみますが、やってはいけないサンプルです。
Daoのメソッドに Transaction アノテーションを付けるとどうなるかやってみます。

Sample022.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");
	final Handle otherHandler = dbi.open();
	final Handle h = dbi.open();

	h.insert("truncate table table001");

	System.out.println("before table001 rows count = " + countTable001(h)); // 0

	try {
		h.inTransaction(new TransactionCallback<Void>()
		{
			@Override
			public Void inTransaction(Handle handle, TransactionStatus status) throws Exception
			{
				System.out.println("[0] : count = " + countTable001(otherHandler)); // 0

				handle.insert("insert into table001(id,name) values(101, 'name101')");

				System.out.println("[1] : count = " + countTable001(otherHandler)); // 0

				handle.attach(Dao010.class).txSuccessTest();

				System.out.println("[2] : count = " + countTable001(otherHandler)); // 4!!

				return null;
			}
		});
	} catch(Throwable e) {
		e.printStackTrace(); // 例外発生
	}

	System.out.println("after table001 rows count = " + countTable001(h)); // 4

	h.close();
	otherHandler.close();
}

public static int countTable001(final Handle h)
{
	return h.createQuery("select count(*) from table001").map(IntegerMapper.FIRST).first();
}

ソース中にコメントで書いてますが、[2] の所で既にコミットされてしまっているので行数が4となってしまってます。
そしてその後、h.inTransaction で例外が発生してcatchします。

というわけで、Handle.inTransaction 内でDaoを呼び出す場合は、Transaction アノテーションを付けたメソッドを呼び出してはいけません。

トランザクション その6

今回からObjectAPI形式とFluent形式を融合させてトランザクション処理をしてみましょう。まずは簡単なサンプルを。
2つ Handle を作って、1つは inTransaction メソッドでトランザクション処理を書きます。こいつは勝手にbegin〜commit〜rollbackをしてくれます。
そしてもう一つは別トランザクションからテーブルの行数を取得してます。
このサンプルは想定通りの挙動です。

Sample021.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");
	final Handle otherHandler = dbi.open();
	final Handle h = dbi.open();

	h.insert("truncate table table001");

	System.out.println("before table001 rows count = " + countTable001(h)); // 0

	try {
		h.inTransaction(new TransactionCallback<Void>()
		{
			@Override
			public Void inTransaction(Handle handle, TransactionStatus status) throws Exception
			{
				System.out.println("[0] : count = " + countTable001(otherHandler)); // 0

				handle.insert("insert into table001(id,name) values(101, 'name101')");

				System.out.println("[1] : count = " + countTable001(otherHandler)); // 0

				handle.attach(Dao010.class).noTxSuccessTest();

				System.out.println("[2] : count = " + countTable001(otherHandler)); // 0

				return null;
			}
		});
	} catch(Throwable e) {
		e.printStackTrace();
	}

	System.out.println("after table001 rows count = " + countTable001(h)); // 4

	h.close();
	otherHandler.close();
}

public static int countTable001(final Handle h)
{
	return h.createQuery("select count(*) from table001").map(IntegerMapper.FIRST).first();
}

※Dao010.javaは以前出てきたやつを使いまわしてます