DeltaSpike Data Module(使いそうなところだけ)まとめ1

DeltaSpike Data ModuleはRepositoryパターンを提供してくれるモジュール。
というのは昨日も書いた。実際に少し試してみる。
DeltaSpike 1.5.1
参考Data Module

Project Setup

  1. 依存設定(Declare Data Module Dependencies)
    Maven前提。pomに「deltaspike-data-module-api」をcompileで、「deltaspike-data-module-impl」をruntimeで依存設定する。

  2. Java環境(Complete Additional Java Environment Configuration)
    JPA必須(当たり前)、JavaMEじゃなければOK。CDIを使うのでJavaSEの場合はWeldなり何なり入れる。

  3. プロジェクト設定?(Complete Additional Project Configuration)
    EntityManagerのProducerを作っておく。Data Moduleのページにあるサンプルは@PersistenceUnitを使ったJavaEE用。今回はJavaSE(Tomcat)で動かすので、Factoryを自前で取得して返すメソッドにする。

public class EntityManagerProducer {
    @Produces
    @RequestScoped
    protected EntityManager createEntityManager() {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("testProvider");
        return factory.createEntityManager();
    }
    protected void closeEntityManager(@Disposes EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }
}

JTAデータソースを使うならbeans.xmlに追記する必要があるらしいけどスキップ。

Core Concepts

  • Repositoryについて
    Repositoryを使うには@Repositoryを指定したinterfaceかabstrace classを作る。forEntityで対象Entityを指定する。
@Repository(forEntity = Person.class)
public abstract class PersonRepository {}

@Repository(forEntity = Person.class)
public interface PersonRepository {}
  • EntityRepositoryインターフェース、AbstractEntityRepositoryクラス
    EntityRepositoryにはよく使いそうなメソッドが定義されている。
    findBy(プライマリキーで検索)、findAll、save、remove、etc...
    AbstractEntityRepositoryはEntityRepositoryをimplementした抽象クラスなので、同じく同様のメソッドが定義済み。
    なのでこれらを継承したインターフェース or 抽象クラスを作る方がオススメ。
    更に言うと、ドキュメントの最後の方まで読むと、インターフェースよりも抽象クラスにしておく方が、後々拡張したいと思ったときに楽な気がする。
@Repository
public interface PersonRepository extends EntityRepository<Person, Long> {}

@Repository
public abstract class PersonRepository extends AbstractEntityRepository<Person, Long> {}

ジェネリクスの<Person, Long>は、前はEntity、後ろはプライマリキーカラムの型を指定する。

  • Deactivating Repositories?
    DeltaSpike Coreの機能でDeactivateできるらしい。非アクティブ化? とりあえず保留。

  • Using Multiple EntityManagers
    これは昨日書いたのでスキップ。

  • Other EntityManager Methods
    EntityRepositoryは一般的なメソッドだけ用意されているので、EntityMangerの他メソッドを使いたければEntityManagerDelegateを使えばいいみたい。必要になったら再調査。

DeltaSpike Data Moduleで複数EntityManagerを呼び分ける

DeltaSpike Data ModuleはRepositoryパターンを提供してくれるモジュール。
・・・リポジトリ パターンは不勉強、とりあえす典型コード(boilerplate code)をすっきりさせてくれるらしい、と理解しておく。
DeltaSpike 1.5.1
参考 Data Module

本題

複数のEntityManagerを使うにはEntityManagerConfig アノテーションをRepositoryに付ける」とある。

EntityMangerのProducer

public class EntityManagerProducer {
    @Produces
    @TestDS
    @RequestScoped
    protected EntityManager createEntityManager() {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("testProvider");
        return factory.createEntityManager();
    }

    @Produces
    @CommonDS
    @RequestScoped
    protected EntityManager createCommonEntityManager() {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("commonProvider");
        return factory.createEntityManager();
    }

    protected void closeEntityManager(@Disposes @TestDS EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }

    protected void closeCommonEntityManager(@Disposes @CommonDS EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }
}

前に使った物そのまま。独自アノテーションでEntityManagerを複数提供できるようにしている。

EntityManagerResolver

public class TestEntityManagerResolver implements EntityManagerResolver {
    @Inject
    @TestDS
    @RequestScoped
    private EntityManager entityManager;

    @Override
    public EntityManager resolveEntityManager() {
        return this.entityManager;
    }
}
public class CommonEntityManagerResolver implements EntityManagerResolver {
    @Inject
    @CommonDS
    @RequestScoped
    private EntityManager entityManager;

    @Override
    public EntityManager resolveEntityManager() {
        return this.entityManager;
    }
}

EntityManagerResolverを実装してresolveEntityManager()でEntityManagerを返す。
独自アノテーション付きでEntityMangerをInjectして、それを返しているだけ。

Repository

@Repository
@EntityManagerConfig(entityManagerResolver = TestEntityManagerResolver.class)
public interface UserRepository extends EntityRepository<User, Integer>{}
@Repository
@EntityManagerConfig(entityManagerResolver = CommonEntityManagerResolver.class)
public interface GroupRepository extends EntityRepository<Group, Integer>{}

@EntityManagerConfigのentityManagerResolverに、欲しいEntityManagerを返してくれるResolverを指定する。
以上。


難しくないがちょっと嬉しくない仕組み。独自アノテーション作ってるのに、さらにResolverクラスを作らないといけない。
@Repositoryで独自アノテーションを指定できればいいんだけど、まあ大したコード量でもないし。

複数のEntityManagerを切り替える(on CDI)

データベースが複数あるなどで、接続先を選びたい場合。
ついでにあやふやにしたままだったアノテーションも整理。
参考 JPA Module

環境

  • Java8SE
  • Tomcat8
    ※なので@PersistenceContextが使えません。使えるともう少し楽?
  • Weld 2.3.1

単一EntityManager(変更前)

public class EntityManagerProducer {
    @Produces
    @RequestScoped
    protected EntityManager createEntityManager() {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("testProvider");
        return factory.createEntityManager();
    }
    protected void closeEntityManager(@Disposes EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }
}

※@Producesはインジェクションを行うメソッド or フィールドに指定する
※@Disposesはオブジェクトの破棄を行うメソッドのパラメータに指定する

限定アノテーション

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface TestDS {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface CommonDS {}

※今回は2つを切り替えるのでアノテーションを2つ作成
※@Qualifierはインジェクションするimplを指定する時に使う
※@Retentionはアノテーションの有効範囲指定、指定しなければRetentionPolicy.CLASSになる
※ここでは実行時に切り替えが必要なのでRetentionPolicy.RUNTIMEを指定
※@Targetはアノテーションを指定できる対象指定

persistence.xml

<persistence-unit name="testProvider" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <non-jta-data-source>java:comp/env/jdbc/testSource</non-jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
</persistence-unit>

<persistence-unit name="commonProvider" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <non-jta-data-source>java:comp/env/jdbc/commonSource</non-jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
</persistence-unit>

Tomcatのデータソースにはjdbc/testSource、jdbc/commonSourceがある状態で、persistenceにそれぞれのデータソースを参照する設定を書く

EntityManger(変更後)

public class EntityManagerProducer {
    @Produces
    @TestDS
    @RequestScoped
    protected EntityManager createEntityManager() {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("testProvider");
        return factory.createEntityManager();
    }

    @Produces
    @CommonDS
    @RequestScoped
    protected EntityManager createCommonEntityManager() {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("commonProvider");
        return factory.createEntityManager();
    }

    protected void closeEntityManager(@Disposes @TestDS EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }

    protected void closeCommonEntityManager(@Disposes @CommonDS EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }
}

※限定アノテーション毎に@Producesのメソッドを作成
※同じく@Disposesのメソッドもそれぞれ用意し、パラメータに限定アノテーションを指定する

Inject

public class UserService {
    @Inject
    @TestDS
    private EntityManager entityManager;
}
public class GroupService {
    @Inject
    @CommonDS
    private EntityManager entityManager;
}

※Injectと共に限定アノテーションも指定すると、対象のEntityManagerをInjectできる

余談

@Disposesを横着して

   protected void closeCommonEntityManager(@Disposes @CommonDS @TestDS EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }

とか限定アノテーション両方とも指定したらエラーで怒られたのは内緒の話。

WELD-001424: The following disposal methods were declared but did not resolve to a producer method

closeするだけだから纏めさせて欲しい。。。

JPAでデータソースからコネクションを取得

環境

  • Tomcat8
  • PostgreSQL9.4

データソース設定

参考
Apache Tomcat 8 (8.0.28) - JNDI Datasource HOW-TO

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resource
        name="jdbc/testSource"
        auth="Container"
        type="javax.sql.DataSource"
        factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
        maxActive="20"
        maxIdle="10"
        maxWait="-1"
        username="・・・"
        password="・・・"
        driverClassName="org.postgresql.Driver"
        url="jdbc:postgresql://127.0.0.1:5432/test" />
</Context>

前エントリの通りTomcat JDBCを使用。Commons DBCPを使うならfactoryを外す。

  • web.xml
    これは設定していない。persistence.xmlでlookupするから不要だと思われる。

JPA(persistence.xml)設定

<persistence-unit name="testProvider" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <non-jta-data-source>java:comp/env/jdbc/testSource</non-jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
<!--
       <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />
       <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://127.0.0.1:5432/test" />
       <property name="javax.persistence.jdbc.user" value="・・・" />
       <property name="javax.persistence.jdbc.password" value="・・・" />
-->
    </properties>
</persistence-unit>

直接接続の設定(property)をコメントアウトして、代わりにnon-jta-data-sourceでデータソース名を指定。
TomcatでJNDIをlookupする際は「java:comp/env/」を付ける必要がある。
transaction-type="RESOURCE_LOCAL"なのでnon-jta-data-sourceタグを使用しているが、transaction-type="JTA"にする場合はjta-data-sourceタグを使う。

Tomcat JDBC Connection Pool

Tomcat7最新版ではデータソースのタイプが2種類提供されている。

  • Apache Commons DBCP
    • 以前から使用されていたデータソース
  • Tomcat JDBC Connection Pool
    • Apache Tomcat 7 (7.0.65) - The Tomcat JDBC Connection Pool
    • マルチコアに対応
    • 高パフォーマンス
    • シンプルな実装(DBCPのクラス数は60以上、こちらのコア部分は8)
    • interceptorを挟める、独自intercepterも作成可能
    • JMXとの連携強化
    • XA connectionサポート
    • etc...

DBCPとの切替

特に指定をしなければDBCPが使用される。Tomcat JDBCを使用する場合は、JNDI設定のResourceタグにfactory属性を追加する。

<Resource ・・・(略)
    factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
/>

属性名などはほとんど変わっていないので、特殊な設定をしていない限りfactoryを追加するだけでOK。

JDBCドライバの配置場所

こちら
Tomcat 7の新機能で何ができるようになるのか?(番外編):Commons DBCPを超えるTomcat JDBC Poolとは (1/2) - @IT
にはTomcat共通のlibに置かないとダメ、と書いてあるが、Tomcatのドキュメントにはそういう記載は見当たらなかった。
試しに共通libにドライバを入れずに、webアプリの方のWEB-INF/libに置いてみたが動くみたい。
Resourceの記述場所によって違う? ドライバ(DB)によって違う? 実はTomcat JDBCで動いていない?
深追いする時間は無いので動いた状態だけメモ。

  • Tomcat 8.0.28
  • PostgreSQL9.4
  • ドライバ(9.4-1205-jdbc41)をmavenの依存に入れて取ってきているだけ
  • Resource設定はwebアプリ内にMETA-INF/context.xmlを作って設定
  • JPA(persistence.xml)のnon-jta-data-sourceで名称設定

JDBCのDataSource整理

アプリケーションサーバーでデータソースを構成する際、(javax.sql.)DataSource、XADataSource、ConnectionPoolDataSourceをtypeとして指定するが、違いがよく分からなかったので調べてみる。

  • DataSource

    • たぶん最も基本的なやつ
    • 普通に構成する分にはこれを選んでおけばいいはず
  • ConnectionPoolDataSource

  • XADataSource

Tomcatでは@PersistenceContextは使えない

前エントリではEntityManagerをわざわざFactoryから生成している。
JPAについて書いているブログなどでは、@PersistenceContextでEntityManagerを取ってくるサンプルがあるし、DeltaSpikeのData Moduleのページにも@PersistenceUnitでEntityManagerFactoryを使っているが、試してもNullPointerExceptionで動かなかった。
調べてみると、Tomcat(=non JavaEEサーバー)では使えない、とEclipseLinkのページに記載されている。

jsf 2 - Using JPA2 in Tomcat 6: @PersitenceContext doesn't work, EntityManager is null - Stack Overflow

EclipseLink/Examples/JPA/Tomcat Web Tutorial - Eclipsepedia

そりゃただのサーブレットコンテナだもんな。。。