2007-11-18
2007-09-02
OpenJPAは永続クラスをエンハンスしなくても動作する
OpenJPA 0.9.7までは、エンティティクラスに対して-javaagent の指定によるランタイムエンハンス又はantタスクやコマンドラインによるビルドタイムエンハンスをしないと永続APIが使えなかったが、OpenJPA 1.0.0からはエンハンスをしなくても大丈夫になった。
但し、よく読んでいないが制約はあるらしい。また、エンハンスなしで OpenJPAを使う場合、永続クラスには引数無しのコンストラクタが必要で、それがないとエラーになる。
但し、よく読んでいないが制約はあるらしい。また、エンハンスなしで OpenJPAを使う場合、永続クラスには引数無しのコンストラクタが必要で、それがないとエラーになる。
ラベル: OpenJPA
2007-09-01
すぐに始める Spring + OpenJPA 1.0.0
クラスパスに追加したもの
commons-collections-3.2.jarcommons-lang-2.2.jar
commons-logging-1.0.4.jar
openjpa-1.0.0.jar
geronimo-jpa_3.0_spec-1.0.jar
geronimo-jta_1.1_spec-1.1.jar
serp-1.13.1.jar
他にも要るかもしれないが、なんにせよ OpenJPAの配布物に含まれている。
あれ、0.9.7の時はgeronimo-j2ee-connectorも必要だったような気が・・・まあいいか。
Java VMに与えたオプション
-javaagent:path/to/openjpa-1.0.0.jarEclipseの場合は Run.. から Arguments → VM arguments: に書く。
このオプションを与えることによって、永続クラスをOpenJPA用にエンハンスする処理が自動で行われる。
(Hibernate-JPAの時は何もしなくても勝手にやってくれていた気がする)
他にも、classファイルを静的にエンハンスする方法(ビルドタイムエンハンス)などがある。
Springの Bean定義ファイルに追加したもの
(dataSourceの定義は既にあるものとする)
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"/>
</property>
</bean>
(0.9.7の時にはつけていたかもしれない)loadTimeWeaverプロパティは、1.0.0ではつけないこと。さもないとランタイムエンハンスに失敗する?
クラスパス以下に作成したファイル
META-INF/persistence.xml
<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="適当な名前" transaction-type="RESOURCE_LOCAL"/>
</persistence>
JPAの入門記事などでは、persistence.xml内にデータソースなど様々な設定を書き込むように解説されているのが普通だが、Spring LocalContainerEntityManagerFactoryBeanを使って EntityManagerFactoryを定義する場合にはそうする必要がない。
但し、永続クラスのリストをpersistence-unit要素内に持たないと OpenJPAのランタイムエンハンサが全てのクラスをエンハンス対象かどうか調べなければならないためクラスのロードがやたらと遅くなるので、上記の例では省略しているものの必要な永続クラスの数分だけ class 要素を並べるのだけはやっておいた方がよい。
JPAを使用するコードの例
EntityManager em = entityManagerFactory.createEntityManager();MyEntityはDBのテーブルに対応づけられるべくアノテーション漬けにされた永続クラス。
// 主キー指定でDBから行をフェッチし、MyEntityのインスタンスとして得る
MyEntity e = em.find(MyEntity.class, 123);
// ...など、いろいろ処理
em.close(); // 使い終わったら片付ける
その他
永続クラスのキーになるクラスは、下記の条件を満たしていないとOpenJPAでは使えないことに注意。equals()が実装されている
hashCode()が実装されている
永続クラス本体側では、キーとなるフィールドに@Idアノテーションが付いていること。
RDBMSの種類は設定で教えてあげなくてもドライバの種類を見て勝手に判断するようだ(少なくとも PostgreSQLではそうだった)
2007-06-30
OpenJPA: JPQLでパラメータプレースホルダに使ってはいけない言葉
日付範囲を指定するUIを作って、開始日を from 終了日を to という名前にした。
で、日付範囲で請求書(Invoice)を抽出する
select i from Invoice i where i.date between :from and :to order by i.date'
みたいなJPQLを発行しようとしたら
org.apache.openjpa.kernel.jpql.ParseException: Encountered "i . date between : from" at character 31, but expected: ...
というようなエラーになった。
パラメータのプレースホルダに予約語を使うとこうなるらしい。というわけで、:frmとか:fro にして回避する。
JPQLの仕様なのか OpenJPAの仕様なのかは不明。
で、日付範囲で請求書(Invoice)を抽出する
select i from Invoice i where i.date between :from and :to order by i.date'
みたいなJPQLを発行しようとしたら
org.apache.openjpa.kernel.jpql.ParseException: Encountered "i . date between : from" at character 31, but expected: ...
というようなエラーになった。
パラメータのプレースホルダに予約語を使うとこうなるらしい。というわけで、:frmとか:fro にして回避する。
JPQLの仕様なのか OpenJPAの仕様なのかは不明。
ラベル: OpenJPA
2007-06-12
OpenJPAのビルドタイムエンハンサをantからコールする
JPAのエンティティクラスはエンハンスされていないと使えない。
コンパイル後(実行前)に classファイルを書き換えてしまうのがビルドタイムエンハンス
実行中にリアルタイムでクラスをエンハンスするのがランタイムエンハンス
Eclipseで開発作業をしているときにはランタイムエンハンスが便利。OpenJPAの場合、Java VMに -javaagent:/path/to/openjpa.jar オプションを与えてやることでランタイムエンハンスが有効になる。
しかし warファイルを作って Tomcatにデプロイしようとしたとき、どうしてもランタイムエンハンスをうまく適用させることが出来なかった(JAVA_OPTS環境変数に -javaagentをセットするとTomcatが起動しない・・・)
仕方ないので antのタスクでビルドタイムエンハンスをすることにした。OpenJPAでは、ビルドタイムエンハンス用のAntタスクが提供されている。
コンパイルの後、パッケージングの前に
エンハンス対象となるクラスを特定するため、persistence.xmlの persistence-unit内に class要素を羅列しておく必要がある(これはランタイムエンハンスの場合でも実行時間を短縮するために有用)。
これでアプリケーションサーバに特別な設定をすることなく OpenJPAを使った Webアプリケーションがデプロイできるようになった。
コンパイル後(実行前)に classファイルを書き換えてしまうのがビルドタイムエンハンス
実行中にリアルタイムでクラスをエンハンスするのがランタイムエンハンス
Eclipseで開発作業をしているときにはランタイムエンハンスが便利。OpenJPAの場合、Java VMに -javaagent:/path/to/openjpa.jar オプションを与えてやることでランタイムエンハンスが有効になる。
しかし warファイルを作って Tomcatにデプロイしようとしたとき、どうしてもランタイムエンハンスをうまく適用させることが出来なかった(JAVA_OPTS環境変数に -javaagentをセットするとTomcatが起動しない・・・)
仕方ないので antのタスクでビルドタイムエンハンスをすることにした。OpenJPAでは、ビルドタイムエンハンス用のAntタスクが提供されている。
build.xmlに加えた内容
コンパイルの後、パッケージングの前に
<taskdef name="openjpac" classname="org.apache.openjpa.ant.PCEnhancerTask">
<classpath>
<fileset dir="OpenJPAや依存jarが入っているディレクトリ" includes="*.jar"/>
<pathelement location="対象クラス及びMETA-INF/persistence.xmlを含むクラスパス"/>
</classpath>
</taskdef>
<openjpac />
エンハンス対象となるクラスを特定するため、persistence.xmlの persistence-unit内に class要素を羅列しておく必要がある(これはランタイムエンハンスの場合でも実行時間を短縮するために有用)。
これでアプリケーションサーバに特別な設定をすることなく OpenJPAを使った Webアプリケーションがデプロイできるようになった。
ラベル: OpenJPA
2007-06-11
OpenJPA がエンティティクラスを認識しない問題
JPAでは、JPQLという SQLによく似た問い合わせ言語を使ってデータベースを利用できる。SQLとの違いは、SQLが「行」や「列」を扱うものであるのに対して(当然)、JPQLは行や列の代わりに「オブジェクト」「プロパティ」を扱うようになっていることだ。
下記の例では、hogeプロパティが honyaと等価な MyEntityクラスオブジェクトのリストが返ってくる。(むろん、実際にはRDBのテーブル・行・列が検索される)
ところが、このコードでOpenJPAが下記のような例外を投げてくるという現象が発生した。
org.apache.openjpa.persistence.ArgumentException: Could not locate metadata for the class using alias "MyEntity". Registered alias mappings: "{MyEntity=null}"
要するに MyEntityなんてクラスは知らないよと。
EntityManager#find()を使った場合などはこのエラーが出なかったのだが・・・
どうやら、OpenJPAでは createQuery(など)が行われる前に対象のエンティティクラスが最低いちどは VMにロードされていなくてはいけないらしい。(利用のしかたにもよるのかも)
というわけで、格好悪いけど JPAを利用する Beanに下記のような staticコンストラクタを書き足した。余計な依存性は発生しないし、ひとまずこれで良いとする。
下記の例では、hogeプロパティが honyaと等価な MyEntityクラスオブジェクトのリストが返ってくる。(むろん、実際にはRDBのテーブル・行・列が検索される)
Collection col =
entityManager.createQuery(
"select m from MyEntity m where m.hoge=?1")
.setParameter(1, honya)
.getResultList();
ところが、このコードでOpenJPAが下記のような例外を投げてくるという現象が発生した。
org.apache.openjpa.persistence.ArgumentException: Could not locate metadata for the class using alias "MyEntity". Registered alias mappings: "{MyEntity=null}"
要するに MyEntityなんてクラスは知らないよと。
EntityManager#find()を使った場合などはこのエラーが出なかったのだが・・・
どうやら、OpenJPAでは createQuery(など)が行われる前に対象のエンティティクラスが最低いちどは VMにロードされていなくてはいけないらしい。(利用のしかたにもよるのかも)
というわけで、格好悪いけど JPAを利用する Beanに下記のような staticコンストラクタを書き足した。余計な依存性は発生しないし、ひとまずこれで良いとする。
static {
try {
MyEntity.class.newInstance();
} catch (Exception e) {
// ここにはこないで・・・
}
}
ラベル: OpenJPA
Springで JPAの共有EntityManagerを利用する
EntityManagerFactoryにさえアクセスできるようセットアップされていれば、いちおう JPAが利用出来ることになるのだが、毎度ファクトリから EntityManagerを取り出して使い終わったらクローズするといった処理の記述を省略できればさらに良い。
Java EEでは @PersistenceContext アノテーション (javax.persistenceパッケージにある) を用いることによって、コンテナから Beanに対して自動的に EntityManager (Factoryじゃなく、さっそく使える方)をインジェクトしてもらえるらしい(ちゃんと調べていないが多分そう)。これならファクトリから EntityManagerのインスタンスをもらったり、使った後に閉じたりする処理を記述しなくて済む分プログラムが少し短くできる(どれほどのもんよ?という疑問はあるが)。
実はこの機能、SpringでもSharedEntityManagerBeanとPersistenceAnnotationBeanPostProcessorの組み合わせで実現できるらしいので試してみた。
以上で EntityManagerがインジェクトされてくるようになるのを確認できた。
それって本当にスレッドセーフなのか?という疑問が当然のごとく湧いてくるかと思うが、そのあたりは SharedEntityManagerBeanや AbstractEntityManagerFactoryBeanがうまくやっているに違いないと信じている。
とりあえず JPAのAPIを通じて進行中のトランザクションに干渉してみるのはどうだろう、と考え、entityManager.getTransaction().setRollbackOnly() を試してみたところ、あえなく拒否された(それは使えないから SpringのPlatformTransactionManagerを使え、といったメッセージで)。
上記のようにしてインジェクトされてきたEntityManagerをトランザクションの外で使おうとすると OpenJPAが例外をスローする。
org.apache.openjpa.persistence.InvalidStateException: The context has been closed. The stack trace at which the context was closed is available if Runtime=TRACE logging is enabled.
従って、共有EntityManagerを使う際は宣言的もしくはプログラム的トランザクションの内側で行うこと。
ソースコードがSpring依存にならない Keep POJOな 宣言的トランザクション
ソースコードはSpring依存になるが細かい制御の可能なプログラム的トランザクション
なおOpenJPA以外のJPAプロバイダではどうなるか試していない。
Java EEでは @PersistenceContext アノテーション (javax.persistenceパッケージにある) を用いることによって、コンテナから Beanに対して自動的に EntityManager (Factoryじゃなく、さっそく使える方)をインジェクトしてもらえるらしい(ちゃんと調べていないが多分そう)。これならファクトリから EntityManagerのインスタンスをもらったり、使った後に閉じたりする処理を記述しなくて済む分プログラムが少し短くできる(どれほどのもんよ?という疑問はあるが)。
実はこの機能、SpringでもSharedEntityManagerBeanとPersistenceAnnotationBeanPostProcessorの組み合わせで実現できるらしいので試してみた。
Bean定義ファイルに追加する内容
<bean id="entityManager"
class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
EntityManagerをインジェクトされる側
private EntityManager entityManager;インジェクトされる側に必要なのはアノテーションのみで、Bean定義ファイルに property定義をする必要はない模様。
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
以上で EntityManagerがインジェクトされてくるようになるのを確認できた。
それって本当にスレッドセーフなのか?という疑問が当然のごとく湧いてくるかと思うが、そのあたりは SharedEntityManagerBeanや AbstractEntityManagerFactoryBeanがうまくやっているに違いないと信じている。
とりあえず JPAのAPIを通じて進行中のトランザクションに干渉してみるのはどうだろう、と考え、entityManager.getTransaction().setRollbackOnly() を試してみたところ、あえなく拒否された(それは使えないから SpringのPlatformTransactionManagerを使え、といったメッセージで)。
OpenJPAでの注意事項
上記のようにしてインジェクトされてきたEntityManagerをトランザクションの外で使おうとすると OpenJPAが例外をスローする。
org.apache.openjpa.persistence.InvalidStateException: The context has been closed. The stack trace at which the context was closed is available if Runtime=TRACE logging is enabled.
従って、共有EntityManagerを使う際は宣言的もしくはプログラム的トランザクションの内側で行うこと。
ソースコードがSpring依存にならない Keep POJOな 宣言的トランザクション
ソースコードはSpring依存になるが細かい制御の可能なプログラム的トランザクション
なおOpenJPA以外のJPAプロバイダではどうなるか試していない。
