try-with-resourcesを試してみる


少し時間ができたので、↓の記事を参考にJava7のtry-with-resourcesを試してみました。
Java7 体当たり/try-with-resources Statement - 日々常々

まあ他の言語からするとアレですが、Java6以前から比較すると地味にありがたい拡張ですね。特にcloseのチェック例外を無視できるのが嬉しい。なぜかリソース系クラスのcloseメソッドはチェック例外持ちが多いので、closeの例外処理が不要でも、finallyブロック内にtryをネストしなきゃいかんというのは気分の悪いものでした。

実行順が気になる

ところで、tryブロック内で例外がthrowされた場合、リソースがクローズされた後にcatchブロックが実行されます。従来であれば、closeはたいていfinallyブロックに記述されたので、catchブロックが実行されたあとにリソースがクローズされていました。

JDBCを使ったDB更新処理では、例外が発生するとcatchブロックの中でrollbackする実装をよく見かけますが、java.sql.Connectionをtry-with-resourcesに管理させた場合は。catchブロックでConnection#rollback()を呼んでも、すでにそのConnectionはクローズされている、ということになります。

try (Connection conn = dataSource.getConnection()) {
  
  // DBを読んだり書いたり〜

  conn.commit();

} catch (Exception e) {
  conn.rollback(); // ここでは、connはクローズ済みなので例外がthrowされる!
} 

まあ、JDBCの仕様上、close時に内部でrollbackされる約束になっているので、rollbackだけなら書かなければいいだけですが、システムによっては、DB上のログテーブルにエラー情報を書け、なんてケースもあるので、そういう場合は注意が必要です。そもそもDB処理で例外が発生した先でDBへ書き込めなんて、そんな実装は窓から投げ捨てるべき

まあ、大した話ではありませんが、実装パターンが若干変わってくるので、コピペテンプレ実装に慣れきっている向きは、気に留めておいたほうがいいかも知れません。

おまけ

try() の中に書けるリソースオブジェクトは、今回新設されたAutoCloseable型でなければいけません*1が、古いライブラリでも、void close() メソッドさえ持っていれば使えるようにラッパクラスを書いてみました。需要があるか微妙ですが。

import java.lang.reflect.*;

public class ACWrapper<T> implements AutoCloseable {

    private final T resource;
    private final Method closeMethod;
    
    public ACWrapper(T resource) {
        
        try {
            closeMethod = resource.getClass().getMethod("close");
        } catch (Exception e) {
            throw new IllegalArgumentException("close-method not found.", e);
        }
        this.resource = resource;
    }

    @Override
    public void close() throws Exception {
        closeMethod.invoke(resource);
    }
    
    public T getResource() {
        return resource;
    }

}

こんな感じで使います。

try (ACWrapper<OldResource> acw = new ACWrapper<OldResource>(new OldResource())) {
    OldResource res = acw.getResource();
    
    // OldResourceを使った処理
}

やっぱ型推論ほしいな…


(8/2 追記)

Java7では型引数を取るクラスのインスタンス生成・初期化の際、右辺の型引数を省略できることを忘れていました。上記の使用例のtry()の行は以下のように書けます。

try (ACWrapper<OldResource> acw = new ACWrapper<>(new OldResource())) {

ちなみにACWrapperのあとの「<>」をつけないとWarnが出ます。…それくらい補完してよ

*1:標準APIのリソース系クラスはほとんどAutoCloseableを実装しています