トランザクションインターセプタ
Google App Engineのデータストア用にトランザクションインターセプタを作成。もはやお約束ですなー。
package test; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.jdo.JDOHelper; import javax.jdo.PersistenceManager; import javax.jdo.PersistenceManagerFactory; import javax.jdo.Transaction; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * トランザクション */ public class Tx { /** * {@link PersistenceManager}ホルダ */ private static ThreadLocal<PersistenceManager> pmHolder = new ThreadLocal<PersistenceManager>(); /** * {@link PersistenceManager}を取得する。 * @return {@link PersistenceManager} */ public static PersistenceManager getPersistenceManager() { return pmHolder.get(); } /** * {@link PersistenceManager}を取得する。 * @return {@link PersistenceManager} */ private static PersistenceManager createPersistenceManager() { return pmfInstance.getPersistenceManager(); } /** * {@link PersistenceManagerFactory} */ private static final PersistenceManagerFactory pmfInstance = JDOHelper.getPersistenceManagerFactory("transactions-optional"); /** * 永続化処理を行うメソッドを示すアノテーション */ @Retention( RetentionPolicy.RUNTIME ) @Target({ElementType.METHOD }) public static @interface Persistence {}; /** * トランザクションインターセプタ */ public static class TxInterceptor implements MethodInterceptor { @Override public Object invoke ( MethodInvocation mi ) throws Throwable { PersistenceManager pm = getPersistenceManager(); if ( pm != null ) return mi.proceed(); try { // PersistenceManager を取得 pm = createPersistenceManager(); pmHolder.set( pm ); // トランザクション Transaction tx = pm.currentTransaction(); try { tx.begin(); // トランザクション開始 Object res = mi.proceed(); tx.commit(); // コミット return res; } finally { if (tx.isActive()) tx.rollback(); } } finally { // PersistenceManager はクローズ必須。 try { if ( pm != null ) pm.close(); } finally { pmHolder.set( null ); } } } } }
利用例は以下。
- 前に作ったCounterServletをトランザクションインターセプタを使う形に修正したものです。
- Guiceと組み合わせて、特定のアノテーションが付与されているメソッドに、インターセプタを適用するようにしています。
package test; import static com.google.inject.matcher.Matchers.annotatedWith; import static com.google.inject.matcher.Matchers.any; import java.io.IOException; import javax.jdo.JDOObjectNotFoundException; import javax.jdo.PersistenceManager; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; @SuppressWarnings("serial") public class CounterServlet2 extends HttpServlet { /** * GETメソッドを処理する。 */ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/plain"); // CounterServiceのインスタンスを取得 int count = HOME.getInstance(CounterService.class).count(); resp.getWriter().println("count :" + String.valueOf( count )); } /**コンテナ*/ static final Injector HOME = Guice.createInjector(new AbstractModule() { @Override protected void configure() { // カウンターサービス bind( CounterService.class ).to( CounterServiceImpl.class ); // Tx.Persistence のアノテーションが付与されているメソッドに // トランザクションインターセプタを適用 bindInterceptor( any(), annotatedWith( Tx.Persistence.class ), new Tx.TxInterceptor()); } }); /**カウンターサービス*/ static interface CounterService { /** * カウンタの値を取得する。 * @return カウンタの値 */ int count(); } /** * {@link CounterService}の実装 */ static class CounterServiceImpl implements CounterService { @Tx.Persistence public int count() { PersistenceManager pm = Tx.getPersistenceManager(); Count c = null; try { // 値クラスのインスタンスを取り出す。 c = pm.getObjectById(Count.class, "count"); c.setValue(c.getValue()+1); // 値があればインクリメント } catch ( JDOObjectNotFoundException e ) { // 初回のアクセスで値がない場合、ここに来る。 c = new Count(); } // 更新 pm.makePersistent(c); // 値を返す return c.getValue(); } } }