Seasar DI Container with AOP

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

概要

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

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

リファレンス

セットアップ

  • プロジェクトのインポート
    JDK5以上が必要です。
    あらかじめSeasar2とS2-Tigerをダウンロードして、EclipseのJavaプロジェクトとしてワークスペースにインポートしてください。
    S2TopLink-JPA-x.x.x.zipを解凍してできたs2toplink-jpaディレクトリをEclipseのJavaプロジェクトとしてワークスペースにインポートしてください。
  • 必要なjarファイル
    S2TopLink-JPAとして必要なjarファイルは、s2toplink-jpa/libにそろっています(ただし、h2-x.x.x.jarはテスト用であるため動作に必須ではありません。)。この他にSeasar2およびS2-Tigerのjarファイルが必要です。
  • クラスパス
    以下のファイルにクラスパスを通します。resourcesのファイル(特にconvention.diconとjdbc.dicon)はプロジェクトに適した設定に変更し使用してください。
    • s2toplink-jpa/lib(s2toplink-jpa-X.X.X.jar、toplink-essentials-X.X-XX.jar)
    • s2toplink-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)がS2TopLink-JPAに含まれています。

基本的な使い方

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

エンティティ

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はs2toplink-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 initializeOnCreate="true">
  <include path="s2toplink-jpa.dicon"/>

  <component name="persistenceUnitProvider"
    class="org.seasar.framework.jpa.impl.ContainerPersistenceUnitProvider">
    <property name="unitName">"persistenceUnit"</property>
    <property name="providerClassName">
      "oracle.toplink.essentials.PersistenceProvider"
    </property>
  </component>

  <!--
  <component name="localUnitProvider"
    class="org.seasar.toplink.jpa.impl.S2TopLinkPersistenceUnitProvider">
    <property name="unitName">"persistenceUnit"</property>
    <property name="javaSECMPInitializer">
      @org.seasar.toplink.jpa.impl.S2JavaSECMPInitializer@getJavaSECMPInitializer(
        "s2toplink-jpa-preload.dicon", #{})
    </property>
  </component>
  <component class="oracle.toplink.essentials.PersistenceProvider"/>
  -->
  
  <component name="entityManagerFactory" class="javax.persistence.EntityManagerFactory">
    persistenceUnitProvider.entityManagerFactory
  </component>

  <component name="entityManager" class="org.seasar.framework.jpa.impl.TxScopedEntityManagerProxy">
    <aspect pointcut="createNamedQuery">
      <component
        class="org.seasar.toplink.jpa.aop.interceptors.S2TopLinkEntityManagerInterceptor"/>
    </aspect>
  </component>

</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>

persistence.xml

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

<?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>oracle.toplink.essentials.PersistenceProvider</provider>
    <jta-data-source>jdbc/dataSource</jta-data-source>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties>      
      <property name="toplink.target-server"
        value="org.seasar.toplink.jpa.platform.server.S2ServerPlatform"/>
      <property name="toplink.target-database"
        value="oracle.toplink.essentials.platform.database.H2Platform"/>
      <property name="toplink.logging.level" value="FINE"/>
      <!-- for test -->
      <property name="toplink.cache.shared.default" value="false"/>
    </properties>
  </persistence-unit>
</persistence>

永続ユニット名にはpersistenceUnitと指定します。

データソース名は、jdbc.diconの名前空間名.dataSourceコンポーネント名と一致させてください。

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

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

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

S2TopLink-JPAとTopLink Essentialsの自動検出の違い

TODO

自動検出

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

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

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

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

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

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

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

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

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

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

自動登録の例

エンティティパッケージが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>jdbc/dataSource</jta-data-source>
        ...
        </persistence-unit>
        <persistence-unit name="fooPersistenceUnit" transaction-type="JTA">
            <jta-data-source>jdbc/fooDataSource</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">
            <aspect pointcut="createNamedQuery">
              <component
                class="org.seasar.toplink.jpa.aop.interceptors.S2TopLinkEntityManagerInterceptor"/>
            </aspect>
        </component>
  • 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">
            <aspect pointcut="createNamedQuery">
              <component
                class="org.seasar.toplink.jpa.aop.interceptors.S2TopLinkEntityManagerInterceptor"/>
            </aspect>
        </component>
  • 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をキャッシュした効率の良いテスト

TopLinkのEntityManagerを利用したテストではテストケース毎にEntitiyMnanagerFactoryが初期化されるため、テストケースが多い場合には時間がかかります。 これを避けるため、Seasar2はテストがS2JUnitやS2JUnit4で実行される場合にEntitiyMnanagerFactoryをキャッシュし、複数のテストケース間で共有する仕組みを提供します。

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

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

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

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

サンプルコード

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

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