JavaEE7のトランザクション制御

【前提条件】

[環境]
[その他]

コンテキストパスは「Sample」、ApplicationPathは「/service」。

GlassFishJDBCリソース登録済み。
リソース名は「jdbc/blogDb」

【概要】

JavaEE6まではトランザクション制御するためには
EJBの管理Beanとして登録する必要がありました。
(登録すると言ってもアノテーションをつけるだけですが)

JavaEE7からは管理Beanへの登録なしでできるようになりました。

【DBのテーブル】

テーブルには↓のようなものを用意します。

CREATE TABLE access_log
(
  id serial NOT NULL,
  content text,
  CONSTRAINT log_pk PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);
CREATE SEQUENCE access_log_id_seq
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 1
  CACHE 1;

【persistence.xml

persistence.xmlの設定は↓のようにします。

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
  xmlns="http://xmlns.jcp.org/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="blogDb" transaction-type="JTA">
      
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/blogDb</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
    </properties>

  </persistence-unit>
</persistence>

transaction-type属性には「JTA」を指定します。
name属性は「blogDB」です。

【beans.xml

今回は独自のクラスをインジェクションさせるため、
bean-discovery-modeを「all」に変更します。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       bean-discovery-mode="all">
</beans>

【クラス】

パラメータを受け取り、テーブルにレコードを登録するクラスを作成します。

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import jp.glory.sample01.entity.AccessLog;

public class TransactionSampleService {

    private static int count = 0;

    @PersistenceContext(unitName = "blogDb")
    private EntityManager manager = null;

    @Transactional
    public void insert(final String content) {

        final AccessLog log = new AccessLog();
        log.setContent(content);

        manager.persist(log);

        executeError();
    }

    // エラー発生用のメソッド
    private void executeError() {

        count++;
        if ( (count % 2) == 0) {

            throw new RuntimeException("回数エラー");
        }
    }
}

トランザクション制御したいメソッドに
javax.transaction.Transactionalアノテーションを付与するだけです。


それ以外にはEntityManagerをインジェクションする設定を行っています。
また、サンプルではinsert後、2回に1回例外をスローしています。

【リソースクラス】

先ほど作成したクラスを実行するための
リソースクラスを作成します。

import javax.inject.Inject;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import jp.glory.sample01.service.TransactionSampleService;

@Path("/tran01")
public class TransactionSampleResource {

    private static int count = 0;

    @Inject
    private TransactionSampleService service = null;

    @POST
    public Response writeLog(@FormParam("content") final String content) {

        try {
            service.insert(content);
        } catch(final Exception ex) {

            ex.printStackTrace();
            return Response.ok("insert is failed.").build();
        }
        executeError();

        return Response.ok("insert is complete").build();
    }
    
    private void executeError() {

        count++;
        if ( (count % 3) == 0) {

            throw new RuntimeException("回数エラー");
        }
    }
}

先ほど作成したクラスをインジェクションします。
insertメソッドから例外が投げられた場合はキャッチして、
正常にレスポンスを返却するようにしています。

サンプルの動作を確認するため、
3回に1回例外をスローしています。

【実行結果】

適当なフォームからアクセスすると
回数ごとに↓のような結果になると思います。

回数 画面 テーブルの状態
1回目 insert is complete 登録される
2回目 insert is failed 登録されない
3回目 500エラー画面 登録される
4回目 insert is failed 登録されない
5回目 insert is complete 登録される
6回目 insert is failed 登録されない

結果を見るとinsertメソッドが正常に終了した場合はコミット、
insertメソッド内で冷害が発生した場合はロールバックされているのがわかります。