App Engine for Java の JDO ののののののの のの のののののの の のの ()
App Engine for Java のJDO 使用上の考慮点
(株)ケーピーエス 平 克介
目次 RDB から JDO への実装の変更概要
今回トライした例について JDO の基本 考慮点 – Transaction 考慮点 – 関連 考慮点 – PersistenceManager 考慮点 – Index
参考:基本情報技術者試験 午前問題特訓システム
KPS フレームワークの機能 クライアントから直接擬似 HibernateAPI を操作
ORM オブジェクトを検索、更新、作成、削除 クライアントからサーバー側 Sesar2 コンポーネン
トの呼び出し Gxt GUI 画面デザインツール D-RexGxt
Hibernate 部分をJDO へ入れ替え
Criteria を Queryへ変更
集計処理部分の 変更
JDO への移行 サーバー側の ORM アクセス部分の変更
Hibernate から JDO へ フレームワーク側の変更
クライアント側検索 API の変更 Criteria から Query へ
フレームワークおよびクライアントコードの変更 集計処理部分の変更
集計処理を行っている Seaser2 コンポーネントの変更
JDO の基本1 JDO とは
Java 標準のデータの永続化・検索機能を提供する API
JDO の ORM クラス( GAE では kind と呼ぶ )@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Long id; @Persistent private String firstName; @Persistent private String lastName; :
この下に constructorgetter/setter が続く
JDO の基本 2
Object(entity) を永続化する
PersistenceManager pm = PMF.get().getPersistenceManager(); Employee e = new Employee("Alfred", "Smith", new Date()); try { pm.makePersistent(e); } finally { pm.close(); }
:
JDO の基本 3 Object の更新
Object の削除
PersistenceManager pm = PMF.get().getPersistenceManager(); try { Employee e = pm.getObjectById(Employee.class, 1); e.setTitle(newTitle); } finally { pm.close(); } :
pm.deletePersistent(e);
:
JDO の基本 4 Object の検索
Query query = pm.newQuery(Employee.class); query.setFilter("lastName == lastNameParam"); query.setOrdering("hireDate desc"); query.declareParameters("String lastNameParam"); try { List<Employee> results = (List<Employee>) query.execute("Smith"); if (results.iterator().hasNext()) { for (Employee e : results) { // ... } } else { // ... no results ... } } finally { query.closeAll(); } :
Where lastName=‘Smith’ Order by hireDate desc
に相当
JDO の基本 5 Transaction
Transaction tx = pm.currentTransaction(); try { tx.begin(); ClubMembers members = pm.getObjectById(ClubMembers.class, "k12345"); members.incrementCounterBy(1); pm.makePersistent(members); tx.commit(); } finally { if (tx.isActive()) { tx.rollback(); } }
:
Tx 間に他所で同じ entity が更新された場合
JDOOptimisticVerificationException となる
JDO の基本 6 com.google.appengine.api.datastore.Key
親のクラスを持つ場合は主キーに Key を使用する Key は親のクラスの Key への参照を持つ
子 Key→ 親 Key Entity の Key は変更できない
import com.google.appengine.api.datastore.Key;// ... @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; public void setKey(Key key) { this.key = key; } :
JDO の基本 7 関連
Employee.java
ContactInfo.java
Employee:Contactinfo = 1:N この場合 Employee が Contactinfo の親になる 共通の親(親の親も含む)を持つ entity の集合を entity
group と言う
// ... @Persistent(mappedBy = "employee") private List<ContactInfo> contactInfoSets;
// ... @Persistent private Employee employee;
参考資料 ORM クラス間の一般的な関連の実装方法
商品 CD 商品名 単価 商品メーカー CD
商品メーカー CD 商品メーカー名
商品テーブル
商品メーカーテーブル
主キー
RDBの場合:主キー /外部キーの関連
String shohinCdString shohinNm
Long tankaShohinMaker shohinMaker
Shohin
getShohinMaker()
String shohinMakerCdString shohinMakerNm
Set shohins
ShohinMaker
getShohins()
ORMでのオブジェクト間の関連
10..*
外部キー
関連相手の Objectの参照を持つ 関連相手の Object
の集合の参照を持つ
考慮点 (Transaction) 1 Transaction 内では同一の entity group に
属する entity しか扱えない
つまり、共通の親を持つ entity しか Transaction化できない
tx.begin(); Query query = pm.newQuery(Employee.class); List<Employee> results = (List<Employee>) query.execute(); Employee e1 = results.get(0); Employee e2 = results.get(1); e1.setFirstName(e1.getFirstName() + "1"); e2.setFirstName(e2.getFirstName() + "2"); tx.commit(); javax.jdo.JDOException
考慮点 (Transaction) Transaction の制約を受けない方法
単一の entity 「 root 」をすべての親の無い entityの親にする
root
employee3 company2
contactInfo2
employee2
contactInfo1
employee1
company1
すべては同一の entity group
考慮点 (Transaction) Root クラス ( 主キーだけあればよい )
@PersistenceCapable(identityType = IdentityType.APPLICATION)public class Root { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; public Key getKey() { return key; } public void setKey(Key key) { this.key = key; }}
考慮点 (Transaction) 親の無い entity の例( Root の Key を持たせる )
Root との ManyToOne 関連を持たせる方法でも OK
@PersistenceCapable(identityType = IdentityType.APPLICATION)public class Employee { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent @Extension(vendorName="datanucleus", key="gae.parent-pk", value="true") private Key rootKey; public void setRootKey(Key rootKey) { this.rootKey = rootKey; }}
考慮点 (Transaction) 親が root である Entity の新規作成
root の key をセットする (root のキー値が1の場合 )
// ...Employee e = new Employee("Alfred", "Smith", new Date());Key key = KeyFactory.createKey("Root", 1);e.setRootKey(key);pm.makePersistent(e); :
考慮点 ( 関連 ) 関連実装時の問題(関連が Key の構造にな
る ) 親を一度設定すると変更できない 複数の親を定義できない
employee1
division1 division2
x
Employee1 は一生 division1
season1
question1
genre1
x
2009 年春の「コンピュータの基礎」の問題
Season と Genre 両方親に出来ない
考慮点 7 ( 関連 - 解決策 ) 関連は親の Key を保持することで実装する
@PersistenceCapable(identityType = IdentityType.APPLICATION)public class Question {
@PrimaryKey@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)private Key key;@Persistentprivate Key seasonKey;@Persistentprivate Key genreKey;public Season getSeason() {
if (this.seasonKey == null) {return null;
}return getPm().getObjectById(Season.class, this.seasonKey);
}public void setSeason(Season season) {
this.seasonKey = season.getKey();} :
関連先 Object を要求された時に entity を読む
考慮点 (PersistenceManager) PersistenceManager の使いまわし
一連の処理で使用する PersistenceManager は同一のものを使用する必要がある。
public Season getSeason() { if (this.seasonKey == null) { return null; } return pm.getObjectById(Season.class, this.seasonKey);}
// ... Season s = question.getSeason(); s.setCnt(s.getCnt()+1); pm.makePersistent(s);
考慮点 (PersistenceManager) PersistenceManager を request に保持する
public class PMManager { public static final String pmName = " PMManager _PM"; public static PersistenceManager getPm() { HttpServletRequest request = null; PersistenceManager pm = null; try { request = (HttpServletRequest) SingletonS2ContainerFactory .getContainer().getComponent(HttpServletRequest.class); pm = (PersistenceManager) request.getAttribute(pmName); } catch (Exception e) { } if (pm == null) { pm = PMF.get().getPersistenceManager(); if (request != null) { request.setAttribute(pmName, pm); } } return pm; }}
この場合 Seasar2 のコンテナから request を得ている
考慮点 (PersistenceManager)
PMManager から入手する例
public Season getSeason() { if (this.seasonKey == null) { return null; } return PMManager.getPm() . getObjectById(Season.class, this.seasonKey);}
// ... Season s = question.getSeason(); s.setCnt(s.getCnt()+1); PMManager.getPm().makePersistent(s);
考慮点 (Index) 検索条件によっては明示的に Index を定義す
る必要がある。 Google の資料で Index 定義が必要な検索とは
複数のソートが指定された場合 Key の逆順ソートの場合 == でない条件と == の条件がある場合 == の条件と ancestor フィルターがある場合
Local 環境で OK でも Deploy した環境で NG の場合がある
考慮点 (Index) Index の設定例
war/WEB-INF/datastore-indexes.xml に指定
<?xml version="1.0" encoding="utf-8"?><datastore-indexes autoGenerate="false"> <datastore-index kind="Question" ancestor="false" source="auto"> <property name="seasonKey" direction="asc"/> <property name="genreKey" direction="asc"/> </datastore-index></datastore-indexes>
その他の考慮点
kind 間の Join による読み込みはできない group by を使用した集約関数は使用できない
count(*) / sum(xxx) 等 検索条件に != 、 IN は使用できない
Python ではクライアント側の実装として実現している
検索条件の組み合わせで or 、 not は使用できない
おわり
ありがとうございました