JPA 永続化
Play は、JPA エンティティの管理を容易にするとても便利な一連のヘルパを提供します。
いつでも素の JPA API に立ち返ることができることに 注意 してください。
JPA エンティティマネージャの開始
Playは、 @javax.persistence.Entity アノテーションで注釈されたクラスをひとつ以上見つけた場合、自動的に Hibernate エンティティマネージャを開始します。とは言え、JDBC データソースを正しく設定していない場合、エンティティマネージャの開始は失敗してしまうので、確実に設定してください。
JPA エンティティマネージャの取得
JPA エンティティマネージャが開始されている場合、JPA ヘルパを使用してアプリケーションコードからそれを取得することができます。例えば、以下のようにします:
public static index() {
Query query = JPA.em().createQuery("select * from Article");
List<Article> articles = query.getResultList();
render(articles);
}
トランザクション管理
Play は自動的にトランザクションを管理します。Play は、HTTPリクエスト毎にトランザクションを開始し、HTTP レスポンスを送信するときにコミットします。アプリケーションコードが例外を創出した場合、トランザクションは自動的にロールバックされます。
アプリケーションコードから強制的にトランザクションをロールバックする必要が場合、 JPA.setRollbackOnly() メソッドを使用することができます。
play.db.jpa.Model サポートクラス
このクラスは JPA の中心的なヘルパクラスです。JPA エンティティのうちの 1 つに play.db.jpa.Model クラスを継承させると、ヘルパクラスは JPA アクセスを容易にする多くのヘルパトメソッドを提供します。
例えば、次の Post モデルオブジェクトを見てください:
@Entity
public class Post extends Model {
public String title;
public String content;
public Date postDate;
@ManyToOne
public Author author;
@OneToMany
public List<Comment> comments;
}
play.db.jpa.Model クラスは自動的に 自動生成された Long 型の id フィールドを提供します。自動生成された Long 型の id を JPA モデルのための主キー (技術的な主キー) として保持し、別のフィールドを機能的な主キーとして管理することは一般的に良い案だと考えます。
Play が、Post クラスの public メンバを自動的に プロパティ と見なすという事実に注意してください。このため、このオブジェクトのためにすべての setter/getter メソッドを書く必要はありません。
オブジェクトの検索
play.db.jpa.Model はデータを検索するいくつかの方法を提供します。例えば、以下のようにします:
ID による検索
オブジェクトを検索する最も簡単な方法です。
Post aPost = Post.findById(5L);
全件検索
List<Post> posts = Post.findAll();
これは、 すべての ポストを検索する最も簡単な方法ですが、同じことは次のようにしても可能です:
List<Post> posts = Post.all().fetch();
次のようにして、ページングした結果を得ることができます:
List<Post> posts = Post.all().fetch(100); // 100 max posts
または、次のようにすることも可能です,
List<Post> posts = Post.all().from(50).fetch(100); // 100 max posts start at 50
簡易なクエリによる検索
クエリは、とても表現豊かなファインダを作成することを可能にしますが、シンプルなクエリについてのみ動作します。
Post.find("byTitle", "My first post").fetch();
Post.find("byTitleLike", "%hello%").fetch();
Post.find("byAuthorIsNull").fetch();
Post.find("byTitleLikeAndAuthor", "%hello%", connectedUser).fetch();
JPQL クエリによる検索
JPQL クエリを使用することができます:
Post.find(
"select p from Post p, Comment c where c.post = p and c.subject like ?", "%hop%"
);
一部だけ使用することも可能です:
Post.find("title", "My first post").fetch();
Post.find("title like ?", "%hello%").fetch();
Post.find("author is null").fetch();
Post.find("title like % and author is null", "%hello%").fetch();
Post.find("title like % and author is null order by postDate", "%hello%").fetch();
order by 構文を指定することも可能です:
Post.find("order by postDate desc").fetch();
オブジェクトのカウント
簡単にオブジェクトをカウントすることができます。
long postCount = Post.count();
クエリを使ってカウントすることも可能です:
long userPostCount = Post.count("author = ?", connectedUser);
明示的な保存
Hibernate は、データベースから問い合わせたオブジェクトのキャッシュを保持します。これらオブジェクトは、これを取得するために使用された EntityManager がアクティブである限り、永続的なオブジェクトとして参照されます。これは、トランザクション境界内で行われる、これらのオブジェクトに対するいかなる変更についても、トランザクションがコミットされるときに自動的に永続化されることを意味します。JPA の標準では、これらの更新はトランザクション境界内において暗黙的です; これらの値を永続化するためにメソッドをコールする必要はありません。
主に不都合な点は、すべてのオブジェクトを手動で管理しなければならないということです。EntityManager に (もっと直感的であるべき) オブジェクトの更新を指示する代わりに、どのオブジェクトを更新しないのかを EntityManager に指示しなければなりません。これを行うために、本質的にはひとつのエンティティをロールバックする refresh() メソッドをコールします。トランザクションのコミットを呼び出す前、またはオブジェクトを更新すべきでないことを認識している場合に、これを行います。
以下は、フォームがサブミットされた後に永続オブジェクトを編集する一般的なユースケースです:
public static void save(Long id) {
User user = User.findById(id);
user.edit(params);
validation.valid(user);
if(validation.hasErrors()) {
// Here we have to explicitly discard the user modifications...
user.refresh();
edit(id);
}
show(id);
}
これまで見てきたところ、ほとんどの開発者がこのことに気付いておらず、save() を明示的にコールしなければオブジェクトは保存されないと仮定し、エラーが発生した場合にオブジェクトの状態を破棄し忘れます。
このため、Play ではまさにこれを変えました。JPASupport/JPAModel を継承するすべての永続オブジェクトは save() メソッドを明示的にコールしなければ保存されません。このため、実際のところ前述のコードを次のように書き直すことができます:
public static void save(Long id) {
User user = User.findById(id);
user.edit(params);
validation.valid(user);
if(validation.hasErrors()) {
edit(id);
}
user.save();
show(id);
}
これははるかに直感的です。さらには、大きなオブジェクトグラフについて明示的に save() をコールすることは退屈でなので、 cascade=CascadeType.ALL 属性で注釈された関連について、save() は連鎖的にコールされます。
ジェネリック型の問題についてもっと詳しく
play.db.jpa.Model はひと揃えのジェネリックメソッドを定義します。これらのジェネリックメソッドは、メソッドの戻り値の型を指定するために型パラメータを使用します。これらのメソッドを使用する場合、戻り値として使用される具体的な型は実行コンテキストから型推論を使用することで決定されます。
例えば、 findAll メソッドは次のように定義されています:
<T> List<T> findAll();
これは次のように使用します:
List<Post> posts = Post.findAll();
この場合、Javaコンパイラは、メソッドの結果に List<Post> が割り当てられているという事実を使用することで、実際の型 T を解決します。このため、T は Post 型として解決されます。
残念ながら、ジェネリックメソッドの戻り値を、別のメソッドの実行引数に直接使用する場合や、ループ処理で使用する場合は、型推論による型の決定は行われません。このため、以下のコードは “型の不一致: 要素タイプ Object から Post には変換できません” というコンパイルエラーが発生します:
for(Post p : Post.findAll()) {
p.delete();
}
もちろん、次のように一時的なローカル変数を使用することで、この問題を解決することができます:
List<Post> posts = Post.findAll(); // type inference works here!
for(Post p : posts) {
p.delete();
}
でも、ちょっと待ってください。もっといいやり方があります。Java 言語には、一度にコードを短く、しかしながら読み易くする、実用的だけれど、あまり知られていない機能があります:
for(Post p : Post.<Post>findAll()) {
p.delete();
}
考察を続けます
今度は ログ を設定する方法を確認しましょう。