LoginSignup
0
0

More than 5 years have passed since last update.

Java JPA:J2SE環境でトランザクション制御とロジックを分離する

Posted at

始めに

J2SE環境でトランザクション制御とロジックを分離してみます
J2EEのアプリケーションサーバのコンテナやSpringの機能は一切使いません

前提

実行環境は以下の通りです

  • J2SE1.8
  • EclipseLink

やりたいこと

Jouhousyori.javaに「xmlファイルをpostgresqlに登録する」ロジックを作っていました。
教科書的な例だと以下の通りになと思います

Jouhousyori.java
    public void execute() throws Exception{
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("myUnitInPersistenceXML");
        EntityManager em = factory.createEntityManager();       
        EntityTransaction entityTransaction = em.getTransaction();
        JouhousyoriEntity entity = new JouhousyoriEntity();
        String xml = new String(Files.readAllBytes(Paths.get(getPath())));
        entity .setText(xml);
        em.persist(entity);
        entityTransaction.commit();
    }

この実装の問題点

 EMの取得処理が冗長

まず、EMの取得の処理がうざすぎます。
書き捨てのプログラムならまだしも、DBの処理を行うクラスやメソッドが増やすたびにEMの取得の処理を書くとか面倒くさくてやってられません

 transactionの開始、終了、ロールバックの制御を外だししたい

次にロジッククラスにtransactionの開始、終了、ロールバックの制御を入れるとロジッククラスの可読性が著しく低くなるように見えます。
ここは人によって好みが分かれると思うけれど、私はロジックとトランザクションの制御は分けたい派です

上記実装の問題点を解決するために

Interceptorを使う

Javaで共通的なロジックを処理したい場合は、Interceptorという機能を使えばいいようです
Interceptorについては
https://blogs.yahoo.co.jp/dk521123/33226901.html
に具体的なJavaによる実装例があります
また、上記サイトのクラス図と簡単な解説については
https://afternoon-garden-70542.herokuapp.com/diarydetails/show/details/?id=76
に載せてみたので併せてどうぞ

具多的な実装イメージ

クラス図

image.png

クラス図の解説

TransactionInterceptorで以下の処理を行っています

  • EMによるトランザクションの開始と終了
  • JouhousyoriクラスにEMのインスタンスをセットする

TransactionInterceptor側にトランザクションやEMの操作の処理を書くことでJouhousyoriクラスは本来自分がやりたい処理である、「xmlファイルを読み込みemに対して永続化処理行う」に専念できます
ついでに、何かDIみたいななにかも実現できているようです

問題解決後のソース

Jouhousyori.java
package logic;

import java.nio.file.Files;
import java.nio.file.Paths;

import javax.persistence.EntityManager;

import entity.JouhousyoriEntity;

public class Jouhousyori implements Logic{

    private EntityManager em;
    private String path;

    @Override
    public void execute() throws Exception{
        JouhousyoriEntity entity = new JouhousyoriEntity();
        String xml = new String(Files.readAllBytes(Paths.get(getPath())));
        entity .setText(xml);
        em.persist(entity);
    }
    public EntityManager getEm() {
        return em;
    }
    public void setEm(EntityManager em) {
        this.em = em;
    }
    public String getPath() {
        return path;
    }
    public void setPath(String path) {
        this.path = path;
    }
}
TransactionIntercepter.java
package proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class TransactionIntercepter  implements InvocationHandler {
       Object target;

       private TransactionIntercepter(Object target) {
          this.target = target;
       }

       @SuppressWarnings({ "rawtypes", "unchecked" })
       public static <T> T getProxyInstance(T instance) {
           Class<? extends Object> clazz = instance.getClass();
          // 対象クラスが実装するインターフェースのリスト
           Class[] classes = clazz.getInterfaces();
           TransactionIntercepter intercepter = new TransactionIntercepter(instance);
           T proxyInstance = (T) Proxy.newProxyInstance(clazz.getClassLoader(), classes, intercepter);
           return proxyInstance;
       }

       @Override
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           EntityManagerFactory factory = Persistence.createEntityManagerFactory("myUnitInPersistenceXML");
           EntityManager em = factory.createEntityManager();        
           EntityTransaction entityTransaction = em.getTransaction();
           entityTransaction.begin();

           Object result=null;
           try {
              //EntityManaterのインスタンスをセットする
              target.getClass().getDeclaredMethod("setEm",EntityManager.class).invoke(target,em);
              // 実際のコードを呼び出し
              result = method.invoke(this.target, args);
           }catch(Exception e) {
               e.printStackTrace();
              entityTransaction.rollback();;
              throw e;
           }
           entityTransaction.commit();
           return result;
       }
}
LogicTest.java
import org.junit.jupiter.api.Test;

import logic.Jouhousyori;
import logic.Logic;
import proxy.TransactionIntercepter;

class LogicTest {

    @Test
    void testParsePDF() throws Exception{
        Jouhousyori target = new Jouhousyori();
        target.setPath("C:\\java\\workspace\\jyouhousyori2\\pdf\\xml\\2018h30h_sc_am2_qs.pdf.xml");
        Logic targetClass = TransactionIntercepter.getProxyInstance(target);
        targetClass.execute();
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0