DeltaSpike Data Module(使いそうなところだけ)まとめ1
DeltaSpike Data ModuleはRepositoryパターンを提供してくれるモジュール。
というのは昨日も書いた。実際に少し試してみる。
DeltaSpike 1.5.1
参考Data Module
Project Setup
依存設定(Declare Data Module Dependencies)
Maven前提。pomに「deltaspike-data-module-api」をcompileで、「deltaspike-data-module-impl」をruntimeで依存設定する。Java環境(Complete Additional Java Environment Configuration)
JPA必須(当たり前)、JavaMEじゃなければOK。CDIを使うのでJavaSEの場合はWeldなり何なり入れる。プロジェクト設定?(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、後ろはプライマリキーカラムの型を指定する。
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
JDBCドライバ配置
$CATALINA_HOME/lib にコピーしろって書いてあるけど、前エントリに書いたようにWEB-INF/libに置いている。Resource設定
ContextにResourceを設定する、のだがTomcatのcontextは数カ所配置できるので、どこに置けばいいのかよく分からなくなることがある。
Apache Tomcat 8 Configuration Reference (8.0.28) - The Context Container
context.xmlの配置について分かったこと. - 小さな星がほらひとつ
下側のページはTomcat6での情報だが、Tomcat8公式Doc(上側のページ)と照らし合わせると変わってはなさそう。
今回はwebアプリ内に/META-INF/context.xmlを作成して設定
<?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を外す。
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で動いていない?
深追いする時間は無いので動いた状態だけメモ。
JDBCのDataSource整理
アプリケーションサーバーでデータソースを構成する際、(javax.sql.)DataSource、XADataSource、ConnectionPoolDataSourceをtypeとして指定するが、違いがよく分からなかったので調べてみる。
DataSource
- たぶん最も基本的なやつ
- 普通に構成する分にはこれを選んでおけばいいはず
ConnectionPoolDataSource
- 名前からしてプール機能がありそうなやつ
- アプリサーバーやJDBCが実装していなければ使えない
- アプリサーバーやJDBCのドキュメントを見て使えそうなら指定可能(?)
- Tomcat JDBC Connection Poolでは使えない様子
XADataSource
Tomcatでは@PersistenceContextは使えない
前エントリではEntityManagerをわざわざFactoryから生成している。
JPAについて書いているブログなどでは、@PersistenceContextでEntityManagerを取ってくるサンプルがあるし、DeltaSpikeのData Moduleのページにも@PersistenceUnitでEntityManagerFactoryを使っているが、試してもNullPointerExceptionで動かなかった。
調べてみると、Tomcat(=non JavaEEサーバー)では使えない、とEclipseLinkのページに記載されている。
EclipseLink/Examples/JPA/Tomcat Web Tutorial - Eclipsepedia
そりゃただのサーブレットコンテナだもんな。。。