JNDIによるJPAアクセス

昨日のコメントだけにしておくとわすれそうなので、備忘録タグで。

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" 
xmlns="http://java.sun.com/xml/ns/persistence" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

  <persistence-unit name="VWPTestPU" transaction-type="JTA">
    <jta-data-source>jdbc/jpa_sample</jta-data-source>
    <properties/>
  </persistence-unit>
</persistence>


web.xml抜粋

<persistence-context-ref>
    <persistence-context-ref-name>persistence/em</persistence-context-ref-name>
    <persistence-unit-name>VWPTestPU</persistence-unit-name>
</persistence-context-ref>

実際に使う場合のコード

EntityManager em;
UserTransaction ut;

//キー
Integer id = 100;

InitialContext ic = new InitialContext();

em = (EntityManager) ic.lookup("java:comp/env/persistence/em");
ut = (UserTransaction) ic.lookup("java:comp/UserTransaction");
            

ut.begin();
            
em.find(id);
            
ut.commit();

TransactionManagerのJNDIはアプリケーションサーバー依存なようでglassfishだと

tm = (TransactionManager) ic.lookup("java:appserver/TransactionManager");

となる。JNDI一覧でも見れないので注意。

さらにそれっぽくするためには


トランザクション管理をユーザーの手に任せたくない場合。適当なので注意。


まずロジックを実装してもらうインターフェース。

public interface TransactionArea<T> {

    T run(EntityManager em);
}

続いてトランザクションを管理するクラス。とりあえず例外等手抜きなので要注意。まともに使うためにはかなりの部分をてこ入れ必要。

class Transaction {
    
    public static <T> T exec(TransactionArea<T> area) {

        EntityManager em;
        UserTransaction ut;
        T result = null;
        try {
            InitialContext ic = new InitialContext();

            
            em = (EntityManager) ic.lookup("java:comp/env/persistence/em");
            ut = (UserTransaction) ic.lookup("java:comp/UserTransaction");
            

            try{
                ut.begin();
                result = area.run(em );
                ut.commit();

            }catch(Exception ex){
                ut.rollback();
            }
            
            
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }
}

インターフェースを実装する。ここにJPAのロジックを書く。

    class UpdateCustomer implements TransactionArea<Customer>{
        private Integer id;
        private String name;
        private String tel;

        public UpdateCustomer(Integer id, String name, String tel) {
            this.id = id;
            this.name = name;
            this.tel = tel;
        }

        public Customer run(EntityManager em) {
            
            Customer c = em.find(Customer.class , id);
            c.set得意先名(name);
            c.set電話番号(tel);
            return c;
        }
        
    }

実行

        Customer c =  Transaction.exec(
                new UpdateCustomer(1,"新しい名前","03-hogehoge") 
                ) ;

        System.out.println(String.format("%d:Name=%s TEL=%s", 
                c.getId() , c.get得意先名() , c.get電話番号())
                );

結果

1:Name=新しい名前 TEL=030-hogehoge

予想通り。
printfを使ってないのはバッドノウハウ。printfはバッファリングしてないのでログが複数回かかれてしまうのを防ぐため。

TransactionArea#runの中で他のTransactionAreaのrunをEntityManagerをわたしてあげて呼び出せばそれもトランザクション内で実行される。REQUIREDのみで大概の場合問題ないのでこれでいいかと。


圧倒的な単体テストがやりやすいのがメリットだなぁ。依存するものがゼロだし。単体テスト時はSeasar2のシンプルEJB3で行って運用時はEJB3で実行できると楽だけど、さすがに無謀すぎるか。