Seasar DI Container with AOP

このドキュメントはS2Hibernate-JPAのバージョン1.0.1のものです。

概要

S2Hibernate-JPAは以下の特徴を持っています。

  • Seasar2で管理されているJava Transaction API (JTA)やConnectionPoolとHibernate3 + Hibernate Annotations + Hibernate EntityManagerを連動できます。
  • アプリケーションサーバ上で実行しなくてもEJB3のJava Persistence API (JPA)を利用できます。
  • SMART deployを利用することで永続ユニットごとに永続クラスとマッピングファイルを自動的に検出し登録できます。
  • 効率的なテストをサポートします。

リファレンス

セットアップ

  • プロジェクトのインポート
    JDK5以上が必要です。
    あらかじめS2ContainerとS2Tigerをダウンロードして、EclipseのJavaプロジェクトとしてワークスペースにインポートしてください。S2Container 2.4.19 と S2Tiger 2.4.19で動作確認をしています。
    S2Hibernate-JPA-1.0.1.zipを解凍してできたs2hibernate-jpaディレクトリをEclipseのJavaプロジェクトとしてワークスペースにインポートしてください。
  • 必要なjarファイル
    S2Hibernate-JPAとして必要なjarファイルは、s2hibernate-jpa/libにそろっています(ただし、h2-1.0.20070429.jarはテスト用であるため動作に必須ではありません。)。この他にS2CntainerおよびS2Tigerのjarファイルが必要です。
    Hibernate3はコネクションプールやキャッシュの実装をいろいろ選べるようになっているので、S2Hibernate-JPAで用意していない実装が必要な場合は、Hibernateのサイトよりダウンロードしてください。
  • クラスパス
    以下のファイルにクラスパスを通します。resourcesのファイル(特にconvention.diconとjdbc.dicon)はプロジェクトに適した設定に変更し使用してください。
    • s2hibernate-jpa/libのjarファイル
    • s2hibernate-jpa/resources(jpa.dicon、META-INF/persistence.xml)
    • Seasar2のresources(convention.dicon、creator.dicon、customizer.dicon、jdbc.dicon、log4.properties)
  • データベース
    簡単に機能を試すことができるように、RDBMSであるH2 Database Engine(h2-1.0.20070429.jar)がS2Hibernate-JPAに含まれています。

基本的な使い方

S2Hibernate-JPAの機能を使用するにあたり、エンティティ、Dao(.java)、diconファイル、persistence.xmlの作成が必要になります。

エンティティ

Persistence APIの仕様に合わせてエンティティを作成します。

エンティティクラスの自動検出と永続ユニットへの自動登録を行うためにエンティティは特定のパッケージに配置します。詳細は永続クラスとマッピングファイルの自動検出/自動登録を参照してください。

Dao(Data Access Object)

Daoの実装方法

  • EntityManager型のフィールドを定義し、コンストラクタあるいはプロパティ経由で実装オブジェクトを受け取るように記述します。
    private EntityManager entityManager;
    
    public void setEntityManager(EntityManager entityManager) { 
        this.entityManager = entityManager;
    }
    または、JPAのPersistenceContextアノテーションを利用して実装オブジェクトを設定してください。
    @PersistenceContext
    private EntityManager entityManager;
  • 各メソッドでEntityManagerに対する処理を記述します。
    public Employee getEmployee(int empno) {
        return entityManager.find(Employee.class, 7788);
    }

diconファイル

  • jpa.diconを設定します。以下のjpa.diconはs2hibernate-jpa/resourcesに含まれるものと同一です(ただし改行位置やインデントの桁数は異なります)。
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" 
      "http://www.seasar.org/dtd/components24.dtd">
    <components>
      <include path="s2hibernate-jpa.dicon"/>
    
      <component name="persistenceUnitProvider" 
      class="org.seasar.framework.jpa.impl.ContainerPersistenceUnitProvider">
        <property name="unitName">"persistenceUnit"</property>
      </component>
    
      <component name="entityManagerFactory" class="javax.persistence.EntityManagerFactory">
        persistenceUnitProvider.entityManagerFactory
      </component>
    
      <component name="entityManager" 
      class="org.seasar.framework.jpa.impl.TxScopedEntityManagerProxy"/>
    </components>
    persistenceUnitProviderコンポーネントのunitNameプロパティに指定する値はpersistence.xmlで定義する永続ユニット名と一致させてください。
  • jpa.diconは他のdiconファイルやテストクラスでインクルードして利用します。 インクルードする際は、下に示すようにjpa.diconの前にjavaee5.diconを先にインクルードするようにしてください。 理由は、jpa.diconを読み込むコンテナが初期化される時点でjavaee5.diconに定義されたコンポーネントが必要になるからです。
    <components>
      <include path="javaee5.dicon"/>
      <include path="jpa.dicon"/>
      ...
    </components>
    
  • 永続クラスとマッピングファイルの自動検出/登録を有効にするにはSMART deployが必要です。SMARAT deployの設定に必要なconvention.dicon、creator.dicon、customizer.diconについてはdiconファイルの設定例を参照してください。

persistence.xml

  • persistence.xmはJPAで定められた設定ファイルです。クラスパスの通っているディレクトリにMETA-INFディレクトリを作成しpersistence.xmlを格納します。以下のpersistence.xmlはs2hibernate-jpa/resourcesに含まれるものと同一です(ただし改行位置やインデントの桁数は異なります)。
    <?xml version="1.0" encoding="UTF-8"?>
    <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="persistenceUnit" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>jdbc/dataSource</jta-data-source>
        <properties>
          <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
          <property name="hibernate.jndi.class" 
              value="org.seasar.extension.j2ee.JndiContextFactory"/>
          <property name="hibernate.transaction.manager_lookup_class" 
              value="org.seasar.hibernate.jpa.transaction.SingletonTransactionManagerProxyLookup"/>
          <property name="hibernate.show_sql" value="false"/>
          <property name="hibernate.format_sql" value="true"/>
          <property name="hibernate.use_sql_comments" value="false"/>  
        </properties>
      </persistence-unit>
    </persistence>
    永続ユニット名にはpersistenceUnitと指定します。
    データソース名は、jdbc.diconの名前空間名.dataSourceコンポーネント名と一致させてください。
    プロパティのhibernate.dialectの値は使用するデータベースに合わせて変更してください。

永続クラスとマッピングファイルの自動検出/登録

S2Hibernate-JPAには、永続クラスやマッピングファイルを自動的に検出し永続ユニットに登録する機能があります。検出/登録はSMART deployの命名規約を利用して行われます。

自動検出

永続クラスを自動検出するには次の条件を満たす必要があります。

  • SMART deployが有効である
  • クラスにEntityアノテーション、Embeddableアノテーション、MappedSuperclassアノテーションのいずれかが付与されている
  • クラスがentityパッケージまたはentityパッケージのサブパッケージに存在する
    (entityパッケージとはconvention.diconに指定したルートパッケージの直下の個別パッケージが「entity」という名前のパッケージです。例えば、ルートパッケージが「hoge.example」の場合、「hoge.example.entity」がentityパッケージです。)

マッピングファイルを自動検出するには次の条件を満たす必要があります。

  • SMART deployが有効である
  • マッピングファイルの名称がXxxOrm.xml(Xxxは任意の名称)である
  • マッピングファイルがentityパッケージまたはentityパッケージのサブパッケージに存在する、もしくはdaoパッケージまたはdaoパッケージのサブパッケージに存在する
    (daoパッケージとはconvention.diconに指定したルートパッケージの直下の個別パッケージが「dao」という名前のパッケージです。例えば、ルートパッケージが「hoge.example」の場合、「hoge.example.dao」がdaoパッケージです。)

JPA標準のマッピングファイルであるMETA-INF/orm.xmlは、SMART deployの有効/無効にかかわらずHibernate EntityManagerにより自動的に読み込まれます。

永続ユニットごとの自動登録

自動検出された永続クラスとマッピングファイルは以下の規則により特定の永続ユニットに登録されます。この規則は複数の永続ユニットを同時に利用する場合に特に重要です。複数の永続ユニットを使う場合の設定については複数の永続ユニットの使い方を参照してください。

永続クラスもしくはマッピングファイルがentityパッケージ(もしくはdaoパッケージ)直下に置かれている場合、それらはデフォルトの永続ユニット(persistenceUnit)に登録されます。

永続クラスもしくはマッピングファイルがentityパッケージ(もしくはdaoパッケージ)のサブパッケージに置かれている場合、それらはxxxPersistenceUnit(xxxはサブパッケージ名)という名前の永続ユニットに登録されます。

なお、JPA標準のマッピングファイルであるMETA-INF/orm.xmlは、Hibernate EntityManagerによりすべての永続ユニットに自動的に登録されます。

自動登録の例

entityパッケージがhoge.example.entityである場合、hoge.example.entityに属するHogeクラスとサブパッケージhoge.example.entity.fooに属するBarクラスは異なる永続ユニットに登録されます。 

登録対象である永続クラスの完全修飾名 登録先の永続ユニット名 説明
hoge.example.entity.Hoge persistenceUnit デフォルトの永続ユニットである「persistenceUnit」に登録されます。
hoge.example.entity.foo.Bar fooPeristenceUnit サブパッケージ「foo」とサフィックス「PersistenceUnit」を組み合わせた名称を持つ永続ユニット「fooPersistenceUnit」に登録されます。

Hibernate Entity Managerの自動検出機能との違い

Hibernate Entity Managerにも自動検出機能が備わっています。S2Hibernate-JPAの機能との違いを以下に示します。
S2Hibernate-JPA Hibernate Entity Manager
検出の対象 ・永続クラス
・xxxOrm.xml
・永続クラス
・hbm.xml
・META-INF/orm.xml
検出の範囲 ・entityパッケージとそのサブパッケージ
・daoパッケージとそのサブパッケージ
・persistence.xmlが属するディレクトリもしくはjarファイル
・persistence.xmのjar-file要素に指定したjarファイル
2つの機能は併用することが可能です。

複数の永続ユニットの使い方

複数のJPA実装を使う場合や、複数のJDBC DataSourceを使う場合は、複数の永続ユニットを作成します。そして、それぞれの永続ユニットに対応するjpa.diconのコピーを用意し、jpa.diconからインクルードします。

  • persistence.xmlに複数の永続ユニットを定義します。
    <persistence>
        <persistence-unit name="persistenceUnit" transaction-type="JTA">
            <jta-data-source>jdbc/dataSource</jta-data-source>
        ...
        </persistence-unit>
        <persistence-unit name="fooPersistenceUnit" transaction-type="JTA">
            <jta-data-source>jdbc/dataSource</jta-data-source>
        ...
        </persistence-unit>
  • pu.diconを定義します。jpa.diconをコピーします。
    <components>
      <include path="s2hibernate-jpa.dicon"/>
    
      <component name="persistenceUnitProvider" 
      class="org.seasar.framework.jpa.impl.ContainerPersistenceUnitProvider">
        <property name="unitName">"persistenceUnit"</property>
      </component>
    
      <component name="entityManagerFactory" class="javax.persistence.EntityManagerFactory">
        persistenceUnitProvider.entityManagerFactory
      </component>
    
      <component name="entityManager" 
      class="org.seasar.framework.jpa.impl.TxScopedEntityManagerProxy"/>
    </components>
  • foo-pu.diconを定義します。jpa.diconをコピーし、次のようにします。
    <components>
      <include path="s2hibernate-jpa.dicon"/>
    
      <component name="fooPersistenceUnitProvider" 
      class="org.seasar.framework.jpa.impl.ContainerPersistenceUnitProvider">
        <property name="unitName">"fooPersistenceUnit"</property>
      </component>
    
      <component name="fooEntityManagerFactory" class="javax.persistence.EntityManagerFactory">
        fooPersistenceUnitProvider.entityManagerFactory
      </component>
    
      <component name="fooEntityManager" 
      class="org.seasar.framework.jpa.impl.TxScopedEntityManagerProxy"/>
    </components>
  • jpa.diconを書き換えpu.diconfoo-pu.diconをインクルードします。
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" 
        "http://www.seasar.org/dtd/components24.dtd">
    <components>
      <include path="pu.dicon"/>
      <include path="foo-pu.dicon"/>
    </components>
  • エンティティマネジャーを使用するDaoのクラスはプロパティでオブジェクトを受け取ります。プロパティ名には使用したいエンティティマネージャーのコンポーネント名を使用してください。
    public void setFooEntityManager(EntityManager fooEntityManager) { 
        this.fooEntityManager = fooEntityManager;
    }
    または、JPAのPersistenceContextアノテーションを使用してエンティティマネージャーを設定します。unitName要素には使用したい永続コンテキストの名称を指定してください。
    @PersistenceContext(unitName="fooPersistenceUnit")
    private EntityManager em

テストのサポート

EntitiyMnanagerFactoryをキャッシュした効率の良いテスト

Hibernate EntityManagerの初期化は比較的重たい処理です。通常、JUnitでHibernate EntityManagerを使用するとテストケース毎にEntitiyMnanagerFactoryが初期化されるため時間がかかります。Seasar2では、これを避けるためEntitiyMnanagerFactoryをキャッシュして複数のテストケース間で共有する仕組みを提供します。

S2UnitやS2JUnit4またはそれらの派生クラスでテストが実行される限り、この仕組みはデフォルトで有効です。

キャッシュ機能を無効にしたい場合には、env_ut.txtというファイルをクラスパスに通し、ファイルに「ut」以外の文字で始まる環境名を記述してください。

S2JUnit4を使った複雑なマッピングのテスト

JPAではさまざまなマッピング方法がサポートされていますが、継承を使ったマッピングなどは意図通りにマッピングできたかテストするのが難しいことがあります。S2JUnit4を利用すると、エンティティを表形式(Seasar2のDataSet)に変換でき、Excelで用意した期待値と比較可能なのでテストが容易です。

サンプルコード

以下のコードはS2Hibernate-JPAに含まれるサンプルコード(examples.entityreader.FileTest.java)からの抜粋です。

import static org.seasar.framework.unit.S2Assert.*;

@RunWith(Seasar2.class)
public class FileTest {
    
    private TestContext context;
    private EntityManager em;
    ...
    public void polymorphicQuery() throws Exception {
        Folder root = new Folder();
        root.setName("root");
        Folder folder = new Folder();
        folder.setName("folder");
        folder.setParent(root);
        Document document = new Document();
        document.setName("document");
        document.setSize(100);
        document.setParent(folder);
        folder.getChildren().add(document);

        em.persist(root);
        em.persist(folder);
        em.persist(document);

        Query query = em.createQuery("SELECT f FROM File f ORDER BY f.name");
        assertEntityEquals(context.getExpected(), query.getResultList());
    }
}

このコードにはFolderクラスとDocumentクラスが登場しますが、これらはともにFileクラスを継承したエンティティです。さらにFileクラスもエンティティであり、継承戦略にはJoined Subclassが使用されています。したがって、このテストで実行されるJPQLは3つのテーブルにまたがるデータを返します。org.seasar.framework.unit.S2AssertクラスのassertEntityEqualsを使用することで、3つのテーブルにまたがるデータを含んだ期待値(context.getExpected()で取得)とJPQLの戻り値(query.getResultList()で取得)を比較検証することができます。

ここでは、継承のマッピングを例に挙げましたが、その他のマッピングに対しても同様のテストが可能です。

S2AssertクラスにはオーバーロードされたstaticなassertEntityEqualsメソッドが4つあります。期待値にはDataSetを、実際値には次のいずれかの値を渡すことができます。

  • 単一のエンティティ
  • エンティティを要素とするコレクション
  • エンティティのオブジェクト配列を要素とするコレクション