jpa – こみなのメモ帳 / 趣味と実益のネタ帳 Wed, 12 May 2021 21:00:00 +0000 ja hourly 1 https://wordpress.org/?v=6.1.1 [play1-jpa]JPQLの結果をList<Map>で受け取る /archives/810/ /archives/810/#respond Wed, 12 May 2021 21:00:00 +0000 https://www.komina.info/?p=810 サンプルアプリのYABEの models/Tag.java のL27~あたりより抜粋。

public static List<Map> getCloud() {
    List<Map> result = Tag.find(
        "select new map(t.name as tag, count(p.id) as pound) from Post p join p.tags as t group by t.name"
    ).fetch();
    return result;
}

受取専用のbeanクラスを用意する方が、おそらくメモリ消費が少なくて済む。(Mapオブジェクトだと管理部分が必要なので)だが、少量の簡単なデータ取得なんかは、こちらの記述方法の方が向いていると思う。

]]>
/archives/810/feed/ 0
[play1-jpa]テーブル間の関係記述 /archives/804/ /archives/804/#respond Tue, 11 May 2021 21:00:00 +0000 https://www.komina.info/?p=804 jpaの機能ですが、playframework1の場合はgetter/setter不要なこともあり一般的なサンプルと少し記述が違います。

1:n

自分1レコードに対し、子nレコードの場合は、@OneToManyアノテーションを使う。
子エンティティを参照するためのコレクション型のプロパティを設け、このプロパティへのアクセスが子エンティティへのアクセスとなる。

public class Parent {
    @OneToMany(mappedBy = "parent")
    public List<Child> children;
}

mappedByを省略すると、ParentテーブルとChildテーブルの関係を保持するための中間テーブルが自動で作成される。
mappedByで子クラスの親参照プロパティを指定すると、中間テーブルは作られない。
(中間テーブルは、通常 n:n の場合に必要に迫られて作る感覚だったので、省略すると中間テーブルが作られるのはちょっと違和感あり。)

逆に子クラスが親クラスを参照する時は、@ManyToOneアノテーションを使う。
親エンティティを参照するためのプロパティを設け、このプロパティへのアクセスが親エンティティへのアクセスとなる。

public class Child {
    @ManyToOne
    public Parent parent;
}

順序

関係性を記述してコレクション型のプロパティを設けたとき、@OrderByアノテーションによりその要素の順序を指定することができる。

    @ManyToMany
    @OrderBy("lastname ASC", "seniority DESC")
    public List<Employee> getEmployees() {
        ...
    };

応用してソート条件ごとにコレクション型プロパティを設けることもできる。(名前降順で取得したいときのプロパティ、年齢昇順で取得したいときのプロパティ、のように)

orphanRemoval

親子関係のあるエンティティに対して、JPA2の新機能であるorphanRemovalの機能を使うことで単に親のコレクションから削除するだけで、関連付けが削除されると同時に子エンティティ自身もDELETEされる。
子エンティティの数だけDELETEが発行されるようなので、パフォーマンスを求めるなら自前で条件を書いてDELETEした方が良いかもしれない。

CascadeType.REMOVE, orphanRemoval=true にして関連エンティティの削除を自動で行わせようとするとき、参照整合性制約エラーが発生することがある。このようなときは関連エンティティを先に削除する処理を自前で記述しなければいけなさそう。検証が必要。

子を全て削除

子Objのdelete()メソッドを呼びまくる。⇒DELETEが子Objの数だけ発行される。良くない。

子Objを親IDで絞り込んで Child.delete("parent = ?", parent); で削除すると DELETEは1回で済む。
親Objへ削除を反映させる為に、親Obj.reflesh()しておきたい。

参考

今からでも遅くない JPAを学ぼう!(後編) オブジェクト間の関連を理解し、JPQLを使用する (1/6):CodeZine(コードジン)

https://codezine.jp/article/detail/5061
]]>
/archives/804/feed/ 0
[play1-jpa]トランザクションの制御 /archives/799/ /archives/799/#respond Sun, 09 May 2021 21:00:00 +0000 https://www.komina.info/?p=799 基本的にフレームワークがトランザクションを管理している。

httpリクエスト毎にトランザクションを開始し、レスポンスを返すときにコミットされる。
例外で終わったときには自動的にロールバックされる。

play1.3で複数DB接続をサポートした影響で、トランザクション制御に影響が出ている。
分かる範囲でそれも追記しておく。

アノテーション

DBの更新を行わないとき、以下のアノテーションを該当のメソッドに設定することで読み出し専用にすることができる。

@play.db.jpa.Transactional(readOnly=true)

トランザクションの自動開始をさせたくないときは、以下のアノテーションを該当メソッド、もしくは該当コントローラクラスに設定する。

@play.db.jpa.NoTransaction

DBアクセスが不要な場合、積極的に利用すると性能アップにつながる。

トランザクションの開始

第1引数はDB識別名、第2引数はreadOnlyなトランザクションかどうかを指定する。
既にトランザクションを開始している場合は、そのトランザクションは破棄してから新たにトランザクションを開始する。

play.db.jpa.JPA.startTx(String, boolean)

デフォルトのDB識別名はJPAクラスに定義してある定数を使って指定する方法が妥当かと。

JPA.startTx(JPA.DEFAULT, false);

ちなみにplay1.3より前はJPAPlugin.startTxを使って下記のような記述をしていた。現在は非推奨。

JPAPlugin.startTx(false);

コミット

トランザクションの開始と同じく、DB識別名を指定して操作する。

JPA.closeTx(JPA.DEFAULT);

play1.3より前は、JPAPlugin.closeTxを使用していた。引数はtrueならロールバックする。

JPAPlugin.closeTx(false);

ロールバック

JPA.rollbackTx(JPA.DEFAULT);

play1.3より前の場合は、下記のような2通りの記述方法。

JPA.setRollbackOnly();

JPAPlugin.closeTx(true);

並列トランザクションを扱う場合(play1.3以降)

幾つかのトランザクションを並列に扱うときは、カレントのトランザクション退避してつつ2つ目のトランザクションを開始して切り替えることで実現することになる。

playframework1としては1リクエスト1トランザクションを基本としているので、リクエスト処理中に例外が発生したときはトランザクションに対してロールバックが行われるようになっている。

しかしそれはカレントのトランザクションであって、退避中のトランザクションについてはコミットもロールバックもしてくれない。@Finallyなどで必ずトランザクションが閉じれるように実装すべき。
対処を忘れるといずれ開けるトランザクションが枯渇してエラーで落ちる。

退避

// コンテキスト退避
JPAContext defContext = JPA.currentEntityManager.get().get(JPA.DEFAULT);
JPA.currentEntityManager.get().remove(JPA.DEFAULT);

// 新たなトランザクションを開始
JPA.startTx(JPA.DEFAULT, false);

復帰

// トランザクションを閉じる
JPA.closeTx(JPA.DEFAULT);

// コンテキスト復帰
if (defContext != null) {
    JPA.currentEntityManager.get().put(JPA.DEFAULT, defContext);
}

※退避コンテキストがNULLのときは復帰しなくていい。

並列トランザクションを扱う場合(play1.3より前)

対象DBが1つのためトランザクションはJPA.local.get(), .set() でアクセスすることができる。

例外発生によるロールバックについてはplay1.3以降と同じく注意が必要。

退避

// カレントのトランザクションをtempContextに退避。カレントトランザクションは無効にしておく。
JPA tran1 = JPA.local.get();
JPA.local.remove();

復帰

// 退避していたトランザクションを、カレントトランザクションに設定する。
JPA.local.set(tran1);

参考

blog.k11i.biz: Play framework でトランザクションを複数並列に扱うには?

http://blog.k11i.biz/2012/01/play-framework.html

トランザクションの閉じ忘れ

トランザクションを閉じ忘れが原因のエラー例。”Connection is not available” 例外が発生している様子が分かる。

13 Feb 2021 20:06:39,184 WARN  ~ SQL Error: 0, SQLState: null
13 Feb 2021 20:06:39,184 ERROR ~ HikariPool-1 - Connection is not available, request timed out after 5254ms.
 Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 5254ms.
     at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:676)
     at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:190)
     at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:155)
     at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128)
     at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122)
     at org.hibernate.internal.NonContextualJdbcConnectionAccess.obtainConnection(NonContextualJdbcConnectionAccess.java:35)
     at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:106)
     … 54 more

]]>
/archives/799/feed/ 0
[play1-jpa]レコードロックする /archives/796/ /archives/796/#respond Sat, 08 May 2021 21:00:00 +0000 https://www.komina.info/?p=796 レコードを取得しつつ、レコードロックする。

User user = JPA.em().find(User.class, id, LockModeType.PESSIMISTIC_READ);

事前の処理で取得していたレコードをロックする。

JPA.em().lock(user, LockModeType.PESSIMISTIC_READ);
]]>
/archives/796/feed/ 0
[play1-jpa]ロックの種類 /archives/794/ /archives/794/#respond Fri, 07 May 2021 21:00:00 +0000 https://www.komina.info/?p=794 playframework1.2.7の時点で、同梱されているJPAのバージョンは2.0。について調べたメモ。(現在play1.5.3ではJPA2.2をサポートしているみたい)

  • OPTIMISTIC
  • OPTIMISTIC_FORCE_INCREMENT
  • PESSIMISTIC_READ
  • PESSIMISTIC_WRITE
  • PESSIMISTIC_FORCE_INCREMENT

OPTIMISTICは楽観ロック、PRESSIMISTICは悲観ロックを意味する。

楽観ロックはJPA1.0で対応していたロック(READ/WRITE)に対応するもの。
@Versionを使用したバージョン更新を行うエンティティ専用。

種類説明
OPTIMISTIC更新のコミット時のみ、対象データをロックし、バージョン更新をする。
OPTIMISTIC_FORCE_INCREMENT更新時だけでなく参照しただけでも、対象データをロックし、バージョン更新をする。
PESSIMISTIC_READエンティティを参照した時点で、対象データがロックされる。コミット時に解放される。
PESSIMISTIC_WRITEエンティティを更新した時点で、対象データがロックされる。
データ参照だけではロックされないので、参照→更新の間に他者に割り込まれる可能性がある。
ロックはコミット時に解放される。
PESSIMISTIC_FORCE_INCREMENT@Versionのバージョン管理が前提。
更新時だけでなく参照しただけでも、対象データをロックし、バージョン更新をする。
コミット時に解放される。
ロックの種類

悲観ロックは、@Versionのバージョン管理が無くても使用できる。(一部不可)

SELECT FOR UPDATEでロックしつつデータ取得、更新して、コミット。という流れを実現するのであれば、PESSIMISTIC_READが相当する。

]]>
/archives/794/feed/ 0
[play1-jpa]直接DBを操作する /archives/792/ /archives/792/#respond Thu, 06 May 2021 21:00:00 +0000 https://www.komina.info/?p=792 jpaを経由しないで直接DBへ接続する方法も書いておく。

jpaは基本、すべてメモリ上に展開する思想なので、大量のデータを抽出しながら出力するような用途には向かない。
そのような時は、ResultSetを回しながら出力するような昔ながらの書き方が必要になると思う。

コネクション取得

Connection conn = DB.getConnection();

簡易なSQL実行

ResultSet rs = DB.executeQuery(SQL);

play.db.helper に、jdbcを使用したDBアクセスのためのヘルパがある。
こちらの使い方はこれから調べたい。

jpaの管理外でDBを直接操作して変更してしまうと、jpaの管理しているキャッシュと齟齬が発生してしまう。使いどころ、扱いには注意が必要だ。

ちなみにjpaのキャッシュはL1とL2があり(下表)、play1ではL2キャッシュを使用しないキャッシュ分離レベル(独立)を採用している。なので、DB直接操作でのデータ更新はトランザクション内でのjpa操作との干渉にさえ注意すればよさげ。

種類タイプ担当説明
L1キャッシュ独立型 永続性コンテキスト・キャッシュEntityManagerトランザクション内で使用しているオブジェクトを保持する。
L2キャッシュ共有型 永続性ユニット・キャッシュEntityManagerFactoryデータソースとやり取り(取得や更新)したオブジェクトを保持する。
jpaキャッシュの種類

]]>
/archives/792/feed/ 0