JavaEE7のトランザクション制御
【前提条件】
[環境]
- JDK 1.7.0_25
- GlassFish 4.0
- PostgreSQL 9.3
【概要】
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メソッド内で冷害が発生した場合はロールバックされているのがわかります。