Seasar DI Container with AOP

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

概要

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

  • Seasar2で管理されているJava Transaction API (JTA)やConnectionPoolとHibernate3 + Hibernate Annotations + Hibernate EntityManagerが簡単に連動するようになります。
  • アプリケーションサーバが無くても、簡単にEJB3のJava Persistence API (JPA)を利用ができます。
  • SMART deployを利用することで永続ユニットごとに永続クラスとマッピングファイルを自動登録できます。
  • Hibernate EntityManagerを使ったテストの時間を短縮できます。
  • S2JUnit4を使って複雑なマッピングのテストができます。

リファレンス

セットアップ

  • プロジェクトのインポート
    JDK5以上が必要です。
    あらかじめSeasar2とS2-Tigerをダウンロードして、EclipseのJavaプロジェクトとしてワークスペースにインポートしてください。
    S2Hibernate-JPA-x.x.x.zipを解凍してできたs2hibernate-jpaディレクトリをEclipseのJavaプロジェクトとしてワークスペースにインポートしてください。
  • 必要なjarファイル
    S2Hibernate-JPAとして必要なjarファイルは、s2hibernate-jpa/libにそろっています(ただし、h2-x.x.x.jarはテスト用であるため動作に必須ではありません。)。この他にSeasar2およびS2-Tigerの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-x.x.x.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ファイル

SMART deployに必要なconvention.dicon、creator.dicon、customizer.diconを設定します。設定方法はdiconファイルの設定例を参照してください。

jpa.diconを設定します(以下のjpa.diconはs2hibernate-jpa/resourcesに含まれるものと同一です)。jpa.diconは他の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="s2hibernate-jpa.dicon"/>

  <component name="entityManagerFactory" class="javax.persistence.EntityManagerFactory">
    jpa.persistenceUnitManager.getEntityManagerFactory("persistenceUnit")
  </component>

  <component name="entityManager" 
      class="org.seasar.framework.jpa.impl.TxScopedEntityManagerProxy"/>

  <component class="org.seasar.hibernate.jpa.metadata.HibernateEntityDescProvider"/>

  <component name="dataSourceProxy" 
      class="org.seasar.extension.datasource.impl.SingletonDataSourceProxy"/>
</components>

jpa.persistenceUnitManager.getEntityManagerFactory()に渡す永続ユニット名はpersistence.xmlの定義と一致させてください。

データソースのコンポーネント名はpersistence.xmlのデータソース名の定義と一致させてください。

persistence.xml

クラスパスの通っているディレクトリにMETA-INFディレクトリを作成します。META-INFには、次のようなpersistence.xmlを格納します。

<?xml version="1.0" encoding="UTF-8"?>
<persistence>
    <persistence-unit name="persistenceUnit" transaction-type="JTA">
        <jta-data-source>dataSourceProxy</jta-data-source>
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <property name="hibernate.dialect" 
                value="org.hibernate.dialect.HSQLDialect"/>
            <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と指定します。

データソース名には、使用するデータソースのコンポーネント名を指定します。

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

S2Hibernate-JPAは、SMART deployを利用することでエンティティパッケージに置かれた永続クラスやマッピングファイルを自動検出し永続ユニットに自動登録できます。

「エンティティパッケージ」とはconvention.diconに指定したルートパッケージの直下の個別パッケージ名が「entity」であるパッケージのことです。たとえば、ルートパッケージ名が「example」の場合、エンティティパッケージは「example.entity」となります。 「永続クラス」とはEntityアノテーション、Embeddableアノテーション、MappedSuperclassアノテーションのいずれかが付与されたクラスを指します。「マッピングファイル」とはJPAのマッピングファイルを意味します。

自動検出

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

  • SMART deployが有効である
  • エンティティクラスがエンティティパッケージまたはエンティティパッケージのサブパッケージに存在する

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

  • SMART deployが有効である
  • マッピングファイルがエンティティパッケージまたはエンティティパッケージのサブパッケージに存在する
  • マッピングファイルの名称がXxxOrm.xml(Xxxは任意の名称)である

SMART deployが有効である場合、S2Hibernate-JPAはS2Hibernate-JPAによる自動検出を優先し、Hibernateの自動検出機能を無効にします。したがって、Hibernateの独自マッピングファイル(.hbm.xml)は自動検出されません。

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

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

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

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

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

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

自動登録の例

エンティティパッケージがexample.entityである場合、エンティティパッケージに属するHogeクラスとサブパッケージに属するBarクラスはそれぞれ特定の永続ユニットに登録されます。 

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

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

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

  • persistence.xmlに複数の永続ユニットを定義します。
    <?xml version="1.0" encoding="UTF-8"?>
    <persistence>
        <persistence-unit name="persistenceUnit" transaction-type="JTA">
            <jta-data-source>dataSourceProxy</jta-data-source>
        ...
        </persistence-unit>
        <persistence-unit name="fooPersistenceUnit" transaction-type="JTA">
            <jta-data-source>dataSourceProxy</jta-data-source>
        ...
        </persistence-unit>
  • pu.diconを定義します。jpa.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>
        ...
        <component name="entityManagerFactory" class="javax.persistence.EntityManagerFactory">
            jpa.persistenceUnitManager.getEntityManagerFactory("persistenceUnit")
        </component>
    
        <component name="entityManager" 
            class="org.seasar.framework.jpa.TxScopedEntityManagerProxy"/>
  • foo-pu.diconを定義します。jpa.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>
        ...
        <component name="fooEntityManagerFactory" class="javax.persistence.EntityManagerFactory">
            jpa.persistenceUnitManager.getEntityManagerFactory("fooPersistenceUnit")
        </component>
    
        <component name="fooEntityManager" 
            class="org.seasar.framework.jpa.TxScopedEntityManagerProxy"/>
  • 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を利用したテストではテストケース毎にEntitiyMnanagerFactoryが初期化されるため、テストケースが多い場合には時間がかかります。これを避けるため、Seasar2はテストがS2JUnitやS2JUnit4で実行される場合にEntitiyMnanagerFactoryをキャッシュし、複数のテストケース間で共有する仕組みを提供します。

S2JUnitや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を、実際値には次のいずれかの値を渡すことができます。

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