Spring+HibernateEntityManager(宣言的トランザクション編)


今回は、前回のサンプルを、プログラム的なランザクションから、宣言的なトランザクションに変更してみます。

主な変更点

  • aspectjweaverの追加。
    • aspectjAOP 文法を利用するため、aspectjweaver のライブラリを追加。追加しないと、以下のような例外が出ます。
java.lang.ClassNotFoundException: org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException
  • JpaTransactionManager の使用
  • SharedEntityManagerBean の使用
    • EntityManagerFactory から直接 EntityManager を取得すると、スレッドに紐付けたトランザクション情報を参照できないため、SharedEntityManagerBean を使用して、TransactionSynchronizationManager から EntityManager を使用するようにします。SharedEntityManager とでも言うべきでしょうか。

ソース

sample.A

サンプルのエンティティです。このファイルは特に変更点はありません。

package sample;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class A {
    @Id
    @GeneratedValue
    private int id;
}
sample.Service

宣言的トランザクション用に設定された EntityManager を使用するように変更。

package sample;

import javax.persistence.EntityManager;

import org.springframework.orm.jpa.JpaDialect;
import org.springframework.orm.jpa.support.JpaDaoSupport;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

public class Service {
    
    /**
     * DI される EntityManager
     */
    private EntityManager em; // Shared EntityManager proxy for target factory [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@1b4fe4d]
    
    /**
     * @param em
     */
    public void setEm(EntityManager em) {
        this.em = em;
    }
    
    public void persistA() {
        
        // em は Shared EntityManager proxy なので、
        // TransactionSynchronizationManager から EntityManagerの実体を取得し、
        // それを用いて永続化処理を行います。
        em.persist(new A());
        
    }
    
}
sample.SampleMain

このソース内に書かれた処理そのものは変わりませんが、AOPにより行われる処理についてコメント。

package sample;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SampleMain {
    
    // A を新規作成し、DB に保存
    public static void main(String[] args) throws Exception {
        
        // ApplicationContext の取得
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        
        // Service の取得
        // beans-xmlの以下の adviser 設定により、宣言的トランザクション用の AOP がかかったクラスが返されます。
        // <aop:advisor advice-ref="allOperationAdvice"
        // pointcut-ref="allOperation" />
        // 例:Service$$EnhancerByCGLIB$$c3b5bf5b
        Service service = (Service) ctx.getBean("service");
        
        // A を新規作成し、DB に保存。
        // 
        // このメソッドの開始前に、AOPでトランザクションの開始および終了処理が入ります。
        // 
        // ■ 開始処理(細部は省略)
        // org.springframework.transaction.interceptor.TransactionInterceptor 経由で
        // org.springframework.orm.jpa.JpaTransactionManager の doBegin メソッドが呼ばれ、
        // EntityManagerHolder が存在しなければ、createEntityManagerForTransaction
        // メソッドにてトランザクション用の EntityManager を作成し、
        // それを用いて EntityManagerHolder を新規作成し、
        // org.springframework.transaction.support.TransactionSynchronizationManager#bindResource
        // にて、スレッドとEntityManagerFactoryの組み合わせにユニークなEntityManagerがバインドされます。
        // ・バインドのキーは、org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean(の proxy)
        // ・バインドの値は、org.springframework.orm.jpa.EntityManagerHolder
        // その後、トランザクションが開始。
        service.persistA();
        // ■ 終了処理(細部は省略)
        // スレッドにバインドしたトランザクション情報を削除します。
        
    }
}
beans.xml

宣言的トランザクションに関する情報がいろいろ増えました。

<?xml version="1.0" encoding="UTF-8"?>
<beans
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
>

  <!-- Transaction Manager の設定 -->
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="emf" />
  </bean>

  <!-- ベンダ依存の情報を取得するためのアダプタ  -->
  <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />

  <!-- persistence.xml の位置指定や LTW 等の設定も可能な FactoryBean -->
  <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceXmlLocation" value="persistence.xml" />
    <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
  </bean>

  <!-- 
    TransactionSynchronizationManager にバインドされたエンティティマネージャを取得可能な bean。
        普通に EntityManagerFactory#getEntityManager() すると、宣言的トランザクションによって
        スレッドとemfの組み合わせに関連付けられた EntityManager を取得できません。
  -->
  <bean id="em" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
    <property name="entityManagerFactory" ref="emf" />
  </bean>
  
  <!-- トランザクションのアドバイスです。 -->
  <tx:advice id="allOperationAdvice" transaction-manager="txManager">
    <tx:attributes>
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>
  
  <!-- Service -->
  <bean id="service" class="sample.Service">
    <property name="em" ref="em" />
  </bean>

  <!-- aop -->
  <aop:config>
    <aop:pointcut id="allOperation" expression="execution(* sample.Service.persistA())" />

    <!-- advisor を設定すると、指定された pointcut に対して、指定された advice が設定される。 -->
    <aop:advisor advice-ref="allOperationAdvice" pointcut-ref="allOperation" />
  </aop:config>
   
</beans>

persistence.xml

このファイルは特に変更ありません。

<persistence
  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"
  version="1.0"
>
  <persistence-unit name="manager" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
    
      <!-- データソースの設定 -->
      <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
      <property name="hibernate.connection.username" value="root" />
      <property name="hibernate.connection.password" value="" />
      <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/test" />
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
      
      <!-- テスト用プログラムなので、毎回DBのテーブルをを新規作成するように設定。 -->
      <property name="hibernate.hbm2ddl.auto" value="create-drop" />
      
    </properties>
  </persistence-unit>
</persistence>

参考

サンプル