始めに
J2SE環境でトランザクション制御とロジックを分離してみます
J2EEのアプリケーションサーバのコンテナやSpringの機能は一切使いません
前提
実行環境は以下の通りです
- J2SE1.8
- EclipseLink
#やりたいこと
Jouhousyori.javaに「xmlファイルをpostgresqlに登録する」ロジックを作っていました。
教科書的な例だと以下の通りになと思います
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
に載せてみたので併せてどうぞ
具多的な実装イメージ
クラス図
クラス図の解説
TransactionInterceptorで以下の処理を行っています
- EMによるトランザクションの開始と終了
- JouhousyoriクラスにEMのインスタンスをセットする
TransactionInterceptor側にトランザクションやEMの操作の処理を書くことでJouhousyoriクラスは本来自分がやりたい処理である、「xmlファイルを読み込みemに対して永続化処理行う」に専念できます
ついでに、何かDIみたいななにかも実現できているようです
問題解決後のソース
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;
}
}
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;
}
}
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();
}
}