トラシスラボ 技術ブログでタグ「jpa」が付けられているもの

背景 - ロック手法について

データベース更新時のロックの方法として、次の2種類が存在する。
  • 悲観的ロック (Pessimistic lock)
  • 楽観的ロック (Optimistic lock)
悲観的ロックは、更新対象を更新完了までロックし、他の人が更新を行ったり、場合によっては参照も禁止する方法。
データをより安全に更新・参照できるメリットがあるが、ロック待ちによるアプリケーションの性能低下要因となりやすい。

楽観的ロックは「自分が操作している情報は他人が同時に更新する可能性が低い」更新を行うのに向いたロック方法。
更新対象を本当に更新する段階までぎりぎりまでロックしない。

例えば、「一旦データを取得したあと、内容を変更して実際に変更しようとしたら、データ取得後に誰かが同じレコードを更新していた」というケースの場合、悲観的ロックであれば「データ取得時に行ロックを行い、誰も更新できない状況」を作ってから更新を行う。その間、他の更新処理は自分の処理が完了するまで待ち状態となる。
一方、楽観的ロックであれば、自分がデータ取得後に他人がデータを更新できてしまう。自分がその後に更新しようとすると JPA 利用時、 OptimisticLockException が発生する。

概要

JPA(Java Persistence API) 1.0では楽観的ロックのみサポートしている。2.0 からは悲観的ロックもサポートするらしい。

Seasar2 + Hibernate + JPA 1.0 の構成において悲観的ロックでデータを更新したいと思い試行錯誤したが、意外とつまづいた。
EntityManager に lock() というメソッドがあって、write lock もできそうだが、うまくいかない。seasar がサポートしていないのか、設定がうまくいっていないのか...

結局、以下の方法で実現できた。

悲観的ロックによる更新方法

次のようなコードになる。
package jp.trasis.sample.service;

import javax.persistence.EntityManager;

import jp.trasis.sample.entity.User;

import org.seasar.extension.tx.annotation.RequiresNewTx;
import org.seasar.framework.container.annotation.tiger.Binding;

public class TestService {
	@Binding
	private EntityManager entityManager;
	
	@RequiresNewTx
	public void test(Long userId, String name) {
		User user = (User) entityManager.createNativeQuery(
				"select * from User_ where id=:userId for update", User.class)
			.setParameter("userId", userId)
			.getSingleResult();
		
		user.setName(name);

		entityManager.persist(user);
	}
}

解説

@RequiresNewTx アノテーションにより、新たなトランザクションの中で更新処理を行う。

まず、更新対象のエンティティを select ... for update により、行ロックを行いながら取得する。
この SQL は createNativeQuery() メソッドで実行する必要がある。crateQuery() で使用する HQL では、 for update 文は利用できない。

そしてエンティティに対して修正を行い、 persist() でコミットを行う。メソッドを抜けるときに実際に DB へのコミット処理が行われ、トランザクションが終了し、ロックも開放される。

Maven+Spring+Hibernate+JPA+JUnit4環境構築

|

概要

今回は Maven + Spring Framework + Hibernate + JPA + JUnit の組み合わせで環境構築。

開発環境

項目
OS Windows XP Pro SP2
IDE 環境 Eclipse 3.3.2(Preiades All in One)
Java JDK 6 update 6
DB PostgreSQL 8.3

準備

次の手順までHibernateを使ったシンプルなJPA環境構築と同じように進める。
  • Maven プロジェクト作成
  • Java コンパイラー準拠レベルの設定
  • pom.xml を設定する
  • 設定ファイル作成
    • META-INF/persistence.xml
    • ehcache.xml
    • log4j.properties
  • BaseEntity, User クラス作成

pom.xml 修正

次のように修正する。

src/main/resources ソース・フォルダ

META-INF/persistence.xml

次のように修正する。 Maven を使う場合、エンティティクラスは src/main/java に、META-INF/persistence.xml ファイルなどは src/main/resources フォルダに置く。
この環境で Spring Framework + Hibernate にうまくクラスのアノテーションをスキャンしてもらうために、次のように記述している。

applicationContext.xml

Spring Framework の設定。
Spring Framework 2.5 からサポートされたいくつかの記述により、前のバージョンよりも記述が簡単になっている。

context:property-placeholder によって、jdbc.properties を読み込んでいる。
${jdbc.url} のように applicationContext.xml 内に記述すると置換される。

context:annotation-config によって、クラスをスキャンし、@Service などのアノテーションを解釈する。
<context:component-scan base-package="test.test" /> によってスキャンするパッケージを明示的に指定している。

tx:annotation-driven によって、@Transactional アノテーションを解釈する。

jdbc.properties

新たに作成。applicationContext.xml の中で ${jdbc.driverClassName} などが置換される。

Dao 作成

src/main/java フォルダの中で test.test.dao.UserDao クラスを作成する。
@Service アノテーションを指定すると、Spring Framework のビーンとして解釈され、他のビーンから @Resource アノテーションなどによって呼び出せる。

@Transactional アノテーションによって、このクラスのメソッド呼び出し時に、トランザクションがまだオープンされていなければ、自動的にオープンする。

JUnit4 によるテスト

テストケースの作成

src/test/java フォルダの中で test.test.DBTest を作成する。

Java Persistence API(JPA) とは

Java Persistence API(JPA) は Java の O/R マッピング標準 API。
概要は (日経BPの記事)が分かりやすい。
メリットは
  • オブジェクト指向で DB 開発がサクサクできる。
  • テーブルや制約などを自動的に作成できる。
  • オブジェクト・キャッシュの仕組みなどによって DB アクセスの負荷軽減・高速化が期待できる。
という点。デメリット(注意点)は
  • DB や SQL に関する知識が不要になるわけではない。
    上手に DB 設計しないとメモリを馬鹿食いしたり、逆に性能低下で悩まされやすい。
    エンティティ設計者は、どういうテーブル・制約・SQLが生成されるかを意識する必要がある。
  • ということで、開発者は既存の DB や SQL に加え、JPA に関する知識の習得を要求される。
といったところ。

デメリットに関しては一人、熟練技術者がいれば解決し、メリットだけを享受できるので、ぜひ「一家に一台」そういう技術者が欲しいところ。

概要

JPA 実装として代表的なのが Hibernate。最近だと EclipseLink(TopLink) も活気が出てきている。

実際の開発では Spring FrameworkSeasar などの DI コンテナとともに使われることが多い Hibernate。
今回は Hibernate だけでシンプルな JPA 環境を構築する。

開発環境

項目
OS Windows XP Pro SP2
IDE 環境 Eclipse 3.3.2(Preiades All in One)
Java JDK 6 update 6
DB PostgreSQL 8.3

まずは環境構築

Maven プロジェクト作成

まずは Eclipse 上で Maven プロジェクトを作成する。
  • [ファイル] メニュー ⇒ 新規 ⇒ プロジェクト
  • Maven ⇒ Maven Project。「New Manve Project」ダイアログが開く。
  • [次へ] ボタンを押す。
  • archetype 選択画面でそのまま [次へ] ボタンを押す。(maven-archetype-quickstart を選択)
  • グループId とアーティファクトID に test と入力して [終了]ボタンを押す。すると、 test プロジェクトが作成される。

Java コンパイラー準拠レベルの設定

  • Eclipse 上でプロジェクト名 test を右クリック ⇒ プロパティ を選択。
  • 左側のメニューから「Java コンパイラー」を選択。
  • 「プロジェクト固有の設定を可能にする」にチェックを入れる。
  • 「コンパイラー準拠レベル」を「6.0」に変更して [OK] ボタンを押す。
  • 「プロジェクトをビルドしますか?」と聞かれるので、「はい」を押す。

pom.xml を設定する

プロジェクト・トップに作成された pom.xml を編集し、利用するソフトウェアを自動ダウンロードする。
解説
Hibernate を使った JPA 環境を構築するには Hibernate EntityManager を利用するので、まず hibernate-entitymanager が必要。
Hibernate のログ出力環境として commons-logging と log4j も指定。
DB に PostgreSQL を使うので postgresql を指定。

作成するエンティティの中で ToStringBuilder を使用するので、 commons-lang を指定。

設定ファイル作成

まず、maven のルールに従い、リソースファイル置き場 src/main/resources フォルダを作成する。
  • プロジェクト test の上で右クリック、[新規] ⇒ [ソース・フォルダー] を選択する。
  • フォルダー名に「src/main/resources」と入力して [終了] ボタンを押す。
作成するファイルは次の3つ。
  • META-INF/persistence.xml
  • ehcache.xml
  • log4j.properties

META-INF/persistence.xml

persistence.xml は JPA の設定ファイル。

src/main/resources フォルダの中に META-INF フォルダを作成。その中に作成する。
いくつか設定項目いついて解説。詳しい設定項目についてはこちら
hibernate.cache.provider_class で二次キャッシュ機能を提供してくれるクラスを指定する。

hibernate.cache.use_query_cache はクエリキャッシュ機能を有効にするかどうか。
キャッシュしたくないクエリに対しては次のように設定する。
Query query = entityManager.createNativeQuery("...", User.class);

query.setHint("org.hibernate.cacheable", Boolean.FALSE);
または
((HibernateQuery) query).getHibernateQuery().setCacheable(false);

hibernate.cache.use_second_level_cache で、二次キャッシュ機能を有効(true)に設定している。
バッチ処理などで大量の insert や update を行う場合(Bulk Insert/Bulk Update)は、ここを false にしておいた方が、余計なキャッシュ処理でメモリや CPU リソースを消費せずに済むらしい。

hibernate.hbm2ddl.auto は、実行時にテーブルなどの作成を行うかどうか。開発中は update、運用時は none、開発環境で作り直すときは create-drop を指定するとよい。
  • update を指定すると、テーブルなどが存在しなければ自動的に作成。
  • create-drop を指定すると、削除して再作成する。
  • none を指定すると、何もしない。

hibernate.show_sql は、実行された SQL をログに出力するかどうか。開発時にチューニングを行う時に true、それ以外は false を指定するといい。

ehcache.xml

src/main/resources フォルダの中に作成する。

log4j.properties

src/main/resources フォルダの中に作成する。

エンティティ作成

ここまでで環境構築は終了。次にエンティティを作成する。

BaseEntity クラス作成

まずは、全てのエンティティのベースとなる BaseEntity を作成する。
src/main/java フォルダの中で test.test.entity パッケージを作成し、その中に BaseEntity クラスを作成する。

ついでに自動的に作成された App クラスは不要なので、消しておく。

User クラス作成

続いて、User エンティティを作成する。

@Entity アノテーションを設定すると、JPA が自動的に「このクラスはエンティティである」と判断してくれる。
テーブル名は User_ とする。
mailAddress, hashedPassword フィールドを持つ。BaseEntity を継承しているので、id, createTimestamp, updateTimestamp も持つ。
mailAddress にはユニークキーを @UniqueConstraint で設定している。
@Column(nullable = false) で mailAddress, hashedPassword フィールドは null のまま DB に追加できないよう設定している。

利用プログラム作成

insert - エンティティ追加

まずはデータの追加を行うプログラム。
src/test/java フォルダの test.test パッケージの中に InsertSample クラスを作成する。
DB に対して insert や update などを行うときはトランザクションの中で行う。

select - ネイティブクエリによる検索

次は SelectSample1 クラスを作成。

select - HSQL による検索

次の SelectSample2 クラスでは HSQL による検索を行っている。

update - データ更新

次の UpdateSample1 クラスではオブジェクトの値を書き換えることで更新を行っている。

update - ネイティブクエリによる更新

次の UpdateSample2 クラスではネイティブクエリによる更新処理を行っている。
生成される SQL を見ると分かるように、自分でネイティブクエリを書いたほうが最適な更新処理を行える。
ただし、1つの EntityManager セッションの中でキャッシュされているオブジェクトの値とずれが生じている点に注意。

delete - エンティティの削除

次の DeleteSample ではエンティティを削除している。

参考文献

2010年9月

      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30