Service/Dao、ヘルパ、SQLの利用
最終更新日: 2024年6月17日
R8 | R9
Wagbyではモデルに対応したServiceクラスとDaoクラスを自動生成します。これは CRUD 処理 (*) に関するメソッドを提供します。
なお、Serviceは内部でDaoを利用しています。開発者はいずれを使うこともできますが、おおむね次の指針で使い分けを行なってください。
ここでは Service の使い方を説明します。開発者はスクリプト内で、次のようにしてServiceオブジェクトを取得することができます。(p.appctxはSpringが提供するDIコンテナです。)
Service名は "<モデルID>EntityService" となります。モデルIDはキャメル記法で表現します。例えば customer モデルに対するService名は "CustomerEntityService" になります。
具体的なコード例を紹介します。次のコードはデータを1件取得します。
続いて、取得したデータを更新する例を紹介します。
ロックについて:上記コードでは、findById で取得したロックは、その後の update メソッドの実行によって必ず解放されるため問題ありません。しかしロックを取得したが、update メソッドは実行しない場合、このスクリプトが終了してもロックは保持されたままとなります。そのため更新予定のないデータを findById メソッドで取得する場合、通常は第二引数を省略してください。この詳細と解決方法は後述する"ロックの取得と解放"をお読みください。
Customer モデルの新規登録を行うスクリプトを紹介します。new 演算子で生成したストアモデルのオブジェクトをデータベースに登録することができます。
サービス (EntityService) のメソッド呼び出し時に例外 PessimisticLockException が生じることがあります。例えば findById メソッドでデータを取得しようとしてこの例外が生じた場合、このデータは別の利用者がロックを取得していることを意味します。
開発者は次の点に注意してスクリプトを作成するとよいでしょう。
findById メソッド呼び出し時に第二引数に
サンプルコードを示します。ロック取得失敗時のコードを catch 節に追加することができます。ここではコンソールに出力するのみとしています。
トランザクションスクリプトは通常、モデル定義で関連しているモデル間で値の変更に関するコードを記述します。しかし Dao クラスを使うことで、任意のモデルを操作することができます。
トランザクションスクリプトでは、次のコードを用いてDaoを取得することができます。
Dao名は "<モデルID>Dao" となります。モデルIDはキャメル記法で表現します。例えば customer モデルに対するDao名は "CustomerDao" になります。
具体的なコード例を紹介します。
dao.get の第二引数はロックを取得することを示します。このあとの dao.update の実行によりロックは解放されます。
Dao クラスはトランザクションが開始されている場合に利用することができます。(Dao 自身はトランザクション境界ではありません。つまり自分自身でトランザクションを開始しません。)一方、上で説明した Service クラスはトランザクション境界になります。この違いから、Daoを使える場所はトランザクションスクリプトに限られます。トランザクションスクリプトは呼び出しの前後でトランザクションの開始と終了が自動的に実行されるため、Daoによる更新処理は正しく処理されます。
スクリプトで (Wagbyが自動生成した) ヘルパクラスを使うことができます。設計情報に記述した初期値や式を再利用できるだけでなく、s2p や p2s メソッドを用いたストアモデルとプレゼンテーションモデルの変換も再利用できます。
例を示します。quotation というストアモデルをプレゼンテーションモデルに変換し、それを po という変数に格納します。
次のコードは条件によって更新処理を行うサンプルです。このコードはロックの処理に問題があります。(なお、ここでは悲観ロックを利用した場合についての説明となっています。)
具体的には条件に合致しない場合は update メソッドが呼び出されません。このとき、findById の第二引数に true を指定したためロックを取得していますが、このロックが解放されずにスクリプトが終わる可能性があります。
ロック解放が行われない場合、メニュー表示時またはログオフ時までは、このロックは保持されたままとなります。その間、他の利用者はこのデータを更新することができません。
findById メソッドでロックを取得するのではなく、PageLockUtils クラスが提供する update メソッドの直前でロックを取得します。
上のコードはすべての例外を throw してロールバックしていますが、PessimisticLockException を補足したい場合は次のように記述することができます。
楽観ロック利用時は findById() の第2引数は無視されます。そのため「引数なし」「true を指定する」「false を指定する」のいずれも動作に違いはありません。
Serviceを使って複数件のデータを取得する場合、クライテリアを使って任意の検索条件を指定できます。モデル設計で、検索条件を有効にしていない項目であってもクライテリアとして(条件を)指定することができます。
モデル毎に用意される CriteriaConverter を使います。コード例を示します。
モデルの定義で暗黙条件を指定していた場合、上の方法で取得した criteria は暗黙条件がセットされた状態となっています。(この回避策をこのあと説明します。)
criteria を用いた検索条件の指定では、対象項目名は文字列ではなく、Wagbyが提供するメタクラスを使うことができます。例えば model1 モデルを定義するとメタクラス Model1Meta が自動生成されます。これは項目名と型の情報(メタデータ)を保持する特別なクラスです。メタクラスを使うことでスペルミスによる実行時エラーといった、デバッグしにくい問題を回避することができます。
メタクラスを使って criteria に検索条件を設定する例を示します。
eq は等しい、という条件になります。他にもさまざまな条件を指定することができます。
Service が提供する find メソッドを利用するときに、第一引数にクライテリアを渡します。戻り値はリストとなっており、この中に(検索条件に合致した)複数のストアモデルが格納されています。
Order クラスを使って、criteria に並び替え条件を追加することができます。例を示します。
Wagbyでは、主キーは必ずソートキーとして扱われます。すなわち SQL の ORDER BY 句に主キーが含まれます。
主キーを ORDER BY に含めるのはページネーション機能に SQL の LIMIT を利用しているためです。ORDER BY の結果の行が一意とならない場合、改ページしても(ORDER BY のソートルールの曖昧さにより)同じデータが次のページに出現してしまうことがあります。これを避けるため、ORDER BY 句に主キーを含めています。このルールを無効にすることはできません。
この制約を回避するために、CriteriaConverter クラスの defaultCriteria(Order[]) メソッドを使うことができます。開発者が引数に指定した並べ替え条件を優先し、主キーによる並び替えは ORDER BY 句の最後に行うようにすることができます。(つまり優先度を下げることで影響を抑えます。)
ここまでの説明を使ったサンプルコードを掲載します。モデルIDは "customer" とし、項目ID に "CUSTOMERNAME", "COMPANYNAME", "MEETDAY" があるとします。
クライテリアで利用できるその他の演算子や、OR との組み合わせによる複雑な条件の作成方法は「検索条件のカスタマイズ」をお読みください。
上のスクリプトで、取得した一覧結果を更新する例です。更新時にはロックを行う必要があります。PageLockUtils クラスの lock メソッドを使うことで、対象データのロックを取得することができます。
暗黙条件を適用させたくない場合は criteriaConverter を使わず、次のようにしてください。
スクリプトでクライテリアをカスタマイズすることもできます。Wagbyの標準機能による検索では、複数の検索項目に検索条件を指定した場合は常にAND検索となります。例えば、ここでOR検索を行わせたい場合はCriteriaをカスタマイズすることで対応できます。
複合キークラスをストアモデルのインナークラスとして用意しています。
トランザクションスクリプトでは return 文で任意の文字列を返すことで、処理をロールバックさせることができます。
ヘルパ系のスクリプトでは、例外 BusinessLogicException をスローすることでロールバックさせることができます。この方法を説明します。
第二引数に
この方法が利用できるスクリプトは、ヘルパ系で、かつ session (Hibernateのsession) が null でない場合に有効です。(*)
例えばヘルパの登録処理または更新処理のスクリプトで、ある条件に合致した場合に BusinessLogicException をスローさせることで、このトランザクションをロールバックさせるといった動作を実現します。
Wagby のトランザクション境界とは、外部データベースに対して "begin transaction" 命令を発行するタイミングをいいます。この命令が発行されてから、再びデータベースに対して "commit" または "rollback" 命令が発行されるまでは「1つのトランザクション」として扱われます。この間データベースに対して行われた変更処理はすべてコミットされるか、またはすべてロールバックされるかのどちらかになります。
コントローラクラス内の doInTransaction メソッドがトランザクション境界となります。
コントローラクラスから呼び出される EntityService がトランザクション境界となります。
EntityService が提供するメソッドは、まだトランザクションが開始されていない場合は新しくトランザクションを開始するが、すでにトランザクションが開始されている場合は(新しいトランザクションを開始せず)現在のトランザクションをそのまま利用するようになっています。
EntityService が提供するメソッドは、内部で Dao を呼び出します。EntitySerivce は上述したようにメソッド開始時に自動的にトランザクションが開始されます。メソッドが正常終了すれば自動的にコミットされ、例外(Exception)が発生すれば自動的にロールバックされます。一方 Dao はトランザクション管理機能を持ちません。Dao利用時にトランザクションが開始されていなければエラーが発生します。
一覧更新画面や親子同時更新画面は doInTransaction メソッドがトランザクション境界となっているため、保存時に(一意制約等のデータベースエラーとなった場合)処理がロールバックされ、編集画面に戻るようになっています。
また、コントローラの一覧更新のスクリプト「データベースコミット前」で例外を発生させると更新処理全体がロールバックされ、編集画面に戻るようになっています。すなわち修正したデータをすべてコミットさせるか、あるいはすべてロールバックさせるかをスクリプトでも制御できるようになっています。[詳細...]
通常、トランザクションスクリプトで他のモデルの値を操作する場合は Dao を使います。しかしここで説明するように、トランザクションスクリプトであっても Service を使う必要のある場合があります。次のような例を想定します。
売上伝票モデルの明細(繰り返しコンテナ)に含まれる「商品コード」は、販売商品モデルへの参照項目となっています。この項目にトランザクションクリプトを記述します。
実行したい内容は、売上伝票登録時に、明細に含まれる(いくつ購入したか、という)数量を、商品在庫から減じることです。
このときのトランザクションスクリプトは次のようになります。(この時点では Dao も Service も使う必要はありません。)
次に更新処理を考えてみます。更新のスクリプトは、登録と同じではありません。
更新の場合は「一度、登録した値」と「更新画面で変更した値」の差をとって、在庫数を再計算する必要があります。
ここで、一度登録した値というのは、データベースに保存された値を指します。
すなわちデータベース上の値と、画面から再入力された値の差を求めるという処理になります。
更新対象のデータとの比較のために更新前のデータをデータベースから取得する場合は別トランザクションで取得する必要があります。
この対応のために、別トランザクションで get 処理を行う仕組み newTransactionEntityService を用いてください。コード例を示します。
この例では、一度登録した売上伝票の更新を認めていますが、実際の業務では更新を認めないというシナリオもあります。また更新ではなく、変更のための伝票を新規で登録させるというシナリオもあります。今回のコードは一つの例としてお読みください。
上の例では、データベースの値を取得するためにサービスオブジェクトを用いました。別のアプローチとして、SQL式を使ってデータベースの値を取得することもできます。
上で説明した更新時のトランザクションスクリプトの説明を続けます。このスクリプトでは入力された値とは別に、現在データベースに保持されている値を Dao を使って取得しています。Dao を使うと、参照連動項目もすべて解決します。そのため、データベースに発行するSQLは参照連動の数に比例して増大します。
しかし今回の目的で利用する o_precord.PNo や o_precord.PNumber は参照連動項目ではありません。そこで Dao 利用時に、不要な参照連動項目の解決をスキップさせることでパフォーマンス向上を図ることができます。
DataBindingContextもWagbyが提供するクラスです。このクラスに、参照連動の解決を行う項目名を明示することができます。使い方の例を示します。
このように、dao の get メソッドの第3引数に、dataBindingContext を渡します。この引数が未指定の場合、すべての参照連動項目を解決します。
解決したい参照連動項目が繰り返しコンテナ内の項目の場合、コンテナ名は含めないでください。(コンテナ名の後ろの)項目名のみを指定します。
すべての参照連動処理をスキップする場合、空の dataBindingContext を渡します。
図4の更新用トランザクションスクリプトに、参照連動項目の解決をスキップするコードを加えた例を示します。
トランザクションスクリプト内で SQL を直接、実行することもできます。
ここでは上の題材を使って、Dao ではなく SQL を用いて明細データの数量を取得するコードを説明します。
Service の内部でさらにスクリプトを用意する方法もあります。一つのトランザクション内で、任意のモデルを操作することができます。
例えば model1 の登録、更新、削除時に、同一トランザクションでスクリプトを実行したい場合は次のスクリプトを用意することができま
す。
WEB-INF/script/model1/Model1EntityService_updateRelatedModelsInsertTransaction.js
WEB-INF/script/model1/Model1EntityService_updateRelatedModelsUpdateTransaction.js
WEB-INF/script/model1/Model1EntityService_updateRelatedModelsDeleteTransaction.js
これらのスクリプトは Designer からは設定できません。直接、ファイルを用意してください。
process 関数を作成することで、同一トランザクション内で動作するスクリプトを記述することができます。
トランザクションスクリプトは対象モデルを明示することで、スクリプトで直接、対象モデル(オブジェクト)を利用することができます。すなわち、対象モデル(オブジェクト)を別途、Dao を使って取得する必要はありません。その代わり、このモデルに紐づいていることが必要です。関連のないモデル(オブジェクト)を利用することはできません。
ここで説明した方法(EntityService の内部から呼び出すスクリプト)は、トランザクションが開始された状態であることがわかっています。そのため、スクリプト内で Dao を使って (1) Aモデルの取得(SELECT) (2) Bモデルの登録(INSERT) (3) Cモデルの更新(UPDATE) を記述すると、これらの処理はすべて同一のトランザクションとして扱われます。
図5からわかるように、ヘルパのスクリプトもまた EntityService のトランザクション内で実行されます。そのため上で説明した方法(EntityService の内部から呼び出すスクリプト)とヘルパのスクリプトは、トランザクション境界という視点では違いはありません。
細かい違いとして、あるモデルの更新時に(同じモデルの)他のデータの更新を行う処理を考えた場合、更新するデータの数だけ、そのモデルに関するヘルパのbeforeUpdateメソッドに紐づくスクリプトが呼び出されます。これが冗長である場合は、本方法が向いているといえます。
本方法が向いている例:
Wagbyが内部で利用しているデータベース操作のためのミドルウェアHibernateが提供するsessionオブジェクトを用いて、任意のSQLを実行することができます。トランザクションスクリプト内では、このsessionオブジェクトを利用できます。
次の例は、アカウントモデル(juser)の個数を求めるSQLを実行します。
トランザクションスクリプト以外の場所で session を利用する必要がある場合は、まず session が使えるかどうかを確認してください。session が null の場合、スクリプト内で HibernateUtil.openSession() を使って取得します。例を示します。
開発者が独自にオープンしたコネクションは、必ずクローズ処理を行ってください。クローズを忘れると、利用できるデータベースセッションが不足し、運用途中で実行時エラーになります。
トランザクションスクリプトからストアドプロシージャを呼び出す例を紹介します。Hibernate の session オブジェクトが利用できる場合は次のとおりです。
トランザクションスクリプト外でストアドプロシージャを呼び出す場合は、Hibernate の session オブジェクトを用意してください。
ストアドプロシージャの IN/OUT パラメータを利用することもできます。Hibernate が提供する JDBC プログラミング方法を利用します。
Statement クラスが提供する setQueryTimeout を指定することができます。サンプルコードを示します。
SQLまたはストアドプロシージャで更新・削除を行う場合はロックとキャッシュに関する制御を開発者自身で記述する必要があります。
更新したテーブルに対応した Wagby のモデルがあり、かつ悲観ロック方式を適用している場合、そのモデルのロックを取得する必要があります。この手順を踏まない場合、別の利用者がこのモデルの対象データを更新中かどうかを考慮せずにデータを更新してしまうため、データの整合性がとれなくなります。
model1 モデルの1件のデータのロックを取得するコードは次のとおりです。
ストアドプロシージャですべてのデータを更新するため、モデル全体をロックしたいという場合は次のコードになります。
更新したテーブルに対応した Wagby のモデルがある場合、そのモデルのキャッシュをクリアします。
model1 モデルのキャッシュクリアを行うコードは次のとおりです。
対象と同じモデルのデータの操作を SQLまたはストアドプロシージャを使って操作することはできません。
その理由は、Wagby内部の「ストアモデル」が、最終的に Hibernate によって SQL に変換され、DB 更新されるためです。SQLやストアドプロシージャを使ってデータベースの値を書き換えても、そのあと Hibernate の update メソッドで、もともと Wagby が管理しているストアモデルの情報で上書き保存されてしまいます。
同じモデルの操作を行う要件であればストアドプロシージャではなく、Wagbyが提供するオブジェクトを操作するようにしてください。具体的にはストアモデルが提供している setter/getter メソッドを使って値を変更してください。
SQLやストアドプロシージャを使って別のモデルを操作することは問題ありません。
Wagbyは標準で Spring が提供する宣言的トランザクションを利用しています。これとは別に開発者が直接、トランザクション管理を行うことも可能です。(プログラマティックトランザクション)
具体的には Spring が提供する TransactionTemplate クラスを使って、トランザクション境界の中で動作するコードを記述することができます。この処理をスクリプトで実現することができますが、事前知識として Java で書く方法を知っておくとよいでしょう。この詳細は "Javaを用いたカスタマイズ > Service/Daoクラス > プログラムによるトランザクション管理" をお読みください。
customer モデルの詳細画面に独自ボタン(イベント名:Original1)を配置し、ボタン押下時に実行される処理として作成しています。
このコードの解説はこちらをお読みください。(同じ処理を Java 言語で実装したコードを用意しています。)
TransactionCallbackWithoutResult や TransactionCallback 内部で Hibernate のセッションオブジェクトを取得することができます。
Serviceを利用する
var entityService = p.appctx.getBean(Service名);
データの取得と更新
var entityService = p.appctx.getBean("JuserEntityService");
var user = entityService.findById("user01"); /* 1件のデータを取得 */
print(user); /* デバッグ用ログ出力 */
var entityService = p.appctx.getBean("JuserEntityService");
var user = entityService.findById("user01", true); /* 1件のデータを取得 */
print(user); /* デバッグ用ログ出力 */
user.name = "ジャスミン太郎";
entityService.update(user); /* 更新 */
注意
新規登録
var customerClass = Java.type("jp.jasminesoft.wagby.model.customer.Customer");
// 空の状態の Customer オブジェクトを作成
var customer = new customerClass();
// 上記2行のコードを1行で記述することもできる。
//var customer = new Packages.jp.jasminesoft.wagby.model.customer.Customer();
var helper = p.appctx.getBean("CustomerHelper");
// helper#initialize() を利用することでリポジトリで定義した
// 初期値をセットする。
// 参考: https://wagby.com/wdn9/operation-script-init.html#initalize
helper.initialize(customer, p);
customer.customername = "ジャスミン太郎";
// EntityService を取得
var service = p.appctx.getBean("CustomerEntityService");
// customer データを登録
service.insert(customer);
例外 PessimisticLockException
無用なロックの取得を行わない。
true
を指定するとロックを取得しようとします。しかし単に値を参照するだけの目的であればロックは不要です。(ロック取得は、その後にデータの更新を行うために行います。)その場合は第二引数にfalse
を指定するとよいでしょう。
try-catch 節で処理を囲い、例外を補足する。
var PessimisticLockException = Java.type("javax.persistence.PessimisticLockException");
...
try {
var entityService = p.appctx.getBean("JuserEntityService");
var user = entityService.findById("user01"); /* 1件のデータを取得 */
print(user);
} catch (e) {
if (e instanceof PessimisticLockException) { /* ロック取得失敗 */
print("lock error " + e);
} else { /* その他の例外 */
print("error " + e);
}
throw e;
} finally {
}
Daoを利用する
コード例
var dao = p.appctx.getBean(Dao名);
var dao = p.appctx.getBean("JuserDao");
var user = dao.get("admin", true); /* 1件のデータを取得 */
print(user); /* デバッグ用ログ出力 */
user.name = "ジャスミン太郎";
dao.update(user); /* 更新 */
ワンポイント
ヘルパを利用する
var IPresentationHelper = Java.type("jp.jasminesoft.jfc.IPresentationHelper");
var helperClass = p.appctx.getBean("QuotationPHelper");
var po = helperClass.s2p(quotation, p, IPresentationHelper.SHOW);
print(po);
ロックの取得と解放
悲観ロック利用時の問題点
var entityService = p.appctx.getBean("JuserEntityService");
var user = entityService.findById("user01", true);
if (user.kind == 1) {
user.name = "ジャスミン太郎";
entityService.update(user);
}
解決方法
var LockUtils = Java.type("jp.jasminesoft.jfc.core.util.PageLockUtils");
var userDaoHelper = p.appctx.getBean("JuserDaoHelper");
var entityService = p.appctx.getBean("JuserEntityService");
var user = entityService.findById("user01");
if (user.kind == 1) {
try {
LockUtils.lock(user, userDaoHelper);/*ロック取得*/
user.name = "ジャスミン太郎";
entityService.update(user);
} catch (e) {
throw e;
} finally {
LockUtils.release(user, userDaoHelper);/*ロック解放*/
}
}
例外 PessimisticLockException を補足したい場合
var PessimisticLockException = Java.type("javax.persistence.PessimisticLockException");
...
try {
LockUtils.lock(user, userDaoHelper);/*ロック取得*/
user.name = "ジャスミン太郎";
entityService.update(user);
} catch (e if e instanceof PessimisticLockException) {
print("lock error " + e);
throw e;
} catch (e) {
print("error " + e);
throw e;
} finally {
LockUtils.release(user, userDaoHelper);/*ロック解放*/
}
楽観ロック利用の場合
クライテリアを利用する
クライテリアを用意する
var criteriaConverter = p.appctx.getBean("Model1CriteriaConverter");
var criteria = criteriaConverter.defaultCriteria();
メタクラスを使う
var Model1Meta = Java.type("jp.jasminesoft.wagby.model.model1.Model1Meta");
var model1Meta = new Model1Meta();
criteria.eq(model1Meta.item1, 1000); /* item1 の値が 1000 のデータで絞り込む */
検索する
var list = Model1Service.find(criteria);
if (list !== null && list.size() > 0) {
// list から 1 つずつオブジェクトを取り出す
for (var i=0; i<list.size(); i++) {
var m = list.get(i);
print(m);
}
}
並び替えを指定する [1]
var OrderClass = Java.type("org.hibernate.criterion.Order");
var order = OrderClass.desc(Model1Meta.start_date.name());
criteria.addOrder(order);/* 並べ替え条件の追加 */
/*criteriaを使って検索する*/
...
並び替えを指定する [2]
ワンポイント
var OrderClass = Java.type("org.hibernate.criterion.Order");
var CustomerMetaClass = Java.type("jp.jasminesoft.wagby.model.customer.CustomerMeta");
var customerMeta = new CustomerMetaClass();
var orders = [
OrderClass.asc(customerMeta.companyname.name()),
OrderClass.asc(customerMeta.deptname.name())
];
var criteriaConverter = p.appctx.getBean("CustomerCriteriaConverter");
// 開発者が並び順を指定する
var criteria = criteriaConverter.defaultCriteria(orders);
クライテリアのサンプルコード 条件による複数データの検索
var OrderClass = Java.type("org.hibernate.criterion.Order");
var CustomerService = p.appctx.getBean("CustomerEntityService");
var CustomerCriteriaConverter = p.appctx.getBean("CustomerCriteriaConverter");
var CustomerMeta = new Packages.jp.jasminesoft.wagby.model.customer.CustomerMeta();
// COMPANYNAME,MEETDAYの順でソートする。
var orders = [
OrderClass.asc(CustomerMeta.COMPANYNAME.name()),
OrderClass.asc(CustomerMeta.MEETDAY.name())
];
var CustomerCriteria = CustomerCriteriaConverter.defaultCriteria(orders);
// 検索条件1 CUSTOMERNAME に "山田" を含むこと
CustomerCriteria.like(CustomerMeta.CUSTOMERNAME, "山田");
// 検索条件2 MEETDAY が本日日付と一致していること
CustomerCriteria.eq(CustomerMeta.MEETDAY, ExcelFunction.TODAY());
var list = CustomerService.find(CustomerCriteria);
for (var i=0; i<list.size(); i++) {
var customer = list.get(i);
print(customer);
}
クライテリアのサンプルコード (2) 条件による複数データの検索から更新へ
var OrderClass = Java.type("org.hibernate.criterion.Order");
var CustomerService = p.appctx.getBean("CustomerEntityService");
var CustomerCriteriaConverter = p.appctx.getBean("CustomerCriteriaConverter");
var CustomerMeta = new Packages.jp.jasminesoft.wagby.model.customer.CustomerMeta();
// COMPANYNAME,MEETDAYの順でソートする。
var orders = [
OrderClass.asc(CustomerMeta.COMPANYNAME.name()),
OrderClass.asc(CustomerMeta.MEETDAY.name())
];
var CustomerCriteria = CustomerCriteriaConverter.defaultCriteria(orders);
// 検索条件1 CUSTOMERNAME に "山田" を含むこと
CustomerCriteria.like(CustomerMeta.CUSTOMERNAME, "山田");
// 検索条件2 MEETDAY が本日日付と一致していること
CustomerCriteria.eq(CustomerMeta.MEETDAY, ExcelFunction.TODAY());
var list = CustomerService.find(CustomerCriteria);
var CustomerDaoHelper = p.appctx.getBean("CustomerDaoHelper");
var LockUtils = Java.type("jp.jasminesoft.jfc.core.util.PageLockUtils");
for (var i=0; i<list.size(); i++) {
var customer = list.get(i);
try {
LockUtils.lock(customer, CustomerDaoHelper);// データのロックを行う。
customer.setNOTE("更新しました。");//更新したことを示すマーカーとして値をセットした例。
CustomerService.update(customer);
} catch (e) {
throw e;
} finally {
LockUtils.release(customer, CustomerDaoHelper);//ロック解放
}
}
暗黙条件を適用させないクライテリアを用意する
var DetachedCriteria = Java.type("jp.jasminesoft.jfc.hibernate.DetachedCriteria");
var CustomerClass = Java.type("jp.jasminesoft.wagby.model.customer.Customer");
var criteria = DetachedCriteria.forClass(CustomerClass.class);
スクリプトでクライテリアをカスタマイズする
複合キーの場合
zaiko モデルが複合キーの場合は次のような記述となります。
var Zaiko = Java.type("jp.jasminesoft.wagby.model.zaiko.Zaiko");
var key = new Zaiko.Key();
key.pkey1 = xxx;/* xxx には、主キーの値が入ります */
key.pkey2 = yyy;/* yyy には、主キーの値が入ります */
var zaiko = zaikoEntityService.findById(key,true);/* 第二引数がtrueのときロックをかける */
例外発生によるロールバック
トランザクションスクリプト
BusinessLogicException
throw new Packages.jp.jasminesoft.jfc.core.exception.BusinessLogicException("エラーメッセージ");
標準のエラーメッセージ出力を抑制する
true
を指定すると、登録・更新画面での標準エラーメッセージ "データベースのXX処理に失敗しました。" を画面に表示しません。第一引数のエラーメッセージだけが画面に表示されます。
throw new Packages.jp.jasminesoft.jfc.core.exception.BusinessLogicException("エラーメッセージ", true);
対象となるスクリプト
if (session !== null) {...}
で session が有効かどうかを確認してください。
トランザクション境界
新規登録・コピー登録画面、更新画面、一覧更新画面
削除、アップロード更新画面
doInTransaction メソッドと EntityService の関係
EntityService と Dao の関係
一覧更新画面や親子同時更新画面での応用
トランザクションスクリプトでもServiceを利用するケース
売上伝票 新規登録時のトランザクションスクリプト
var suryou = product4s.stock;
var syukko_num = precord.PNumber;
/*print("suryou="+suryou+",syukko_num="+syukko_num);*/
if (suryou - syukko_num < 0) {
return precord.PName + "の在庫 "+suryou+" に対して "+syukko_num+" を出庫しようとしました。";
}
product4s.stock = suryou - syukko_num;
return null;
売上伝票 更新時のトランザクションスクリプト
var entityService = p.appctx.getBean("SalesslipEntityService");
var newTransactionEntityService = entityService.newTransactionEntityService();/*別トランザクションになる*/
var o_salesslip = newTransactionEntityService.findById(salesslip.id, false);/* salesslip.idは主キー */
var o_precords = o_salesslip.precord;
var o_precord;
for (var i=0; i<o_precords.length; i++) {
o_precord = o_precords[i];
if (o_precord.PNo == precord.PNo) {
break;
}
}
if (o_precord !== null) {
var suryou = product4s.stock;
var syukko_num = precord.PNumber - o_precord.PNumber;
/*print("now suryou="+suryou+",syukko_num_diff="+syukko_num);*/
if (syukko_num !== 0) {
if (suryou - syukko_num < 0) {
return precord.PName + "の在庫 "+suryou+" に対して "+syukko_num+" を出庫しようとしました。";
}
product4s.stock = suryou - syukko_num;
}
}
return null;
ワンポイント
メモ
制約
トランザクションスクリプトで参照連動の解決を抑制する
DataBindingContext
var DataBindingContext = Java.type("jp.jasminesoft.jfc.dao.DataBindingContext");
var dao = p.appctx.getBean("SalesslipDao");
var dataBindingContext = new DataBindingContext();
var targetItemSet = new java.util.HashSet();
targetItemSet.add("customerName");/* 解決したい参照連動項目 */
dataBindingContext.setTargetItemSet(targetItemSet);
var o_salesslip = dao.get(salesslip.id, true, dataBindingContext);/* 第3引数に指定 */
参照連動処理をスキップする
var DataBindingContext = Java.type("jp.jasminesoft.jfc.dao.DataBindingContext");
var dao = p.appctx.getBean("SalesslipDao");
var dataBindingContext = new DataBindingContext();
var targetItemSet = new java.util.HashSet();
dataBindingContext.setTargetItemSet(targetItemSet);/* 項目を明示しない */
var o_salesslip = dao.get(salesslip.id, true, dataBindingContext);/* 第3引数に指定 */
例
var DataBindingContext = Java.type("jp.jasminesoft.jfc.dao.DataBindingContext");
var dao = p.appctx.getBean("SalesslipDao");
var dataBindingContext = new DataBindingContext();
dataBindingContext.setTargetItemSet(new java.util.HashSet());/* 項目を明示しない */
var o_salesslip = dao.get(salesslip.id, true, dataBindingContext);/* 第3引数に指定 */
print(o_salesslip);/* コンソールに出力。確認用 */
var o_precords = o_salesslip.precord;
var o_precord;
for (var i=0; i<o_precords.length; i++) {
o_precord = o_precords[i];
if (o_precord.PNo == precord.PNo) {
break;
}
}
if (o_precord !== null) {
var suryou = product4s.stock;
var syukko_num = precord.PNumber - o_precord.PNumber;
/*print("now suryou="+suryou+",syukko_num_diff="+syukko_num);*/
if (syukko_num !== 0) {
if (suryou - syukko_num < 0) {
return precord.PName + "の在庫 "+suryou+" に対して "+syukko_num+" を出庫しようとしました。";
}
product4s.stock = suryou - syukko_num;
}
}
return null;
トランザクションスクリプトでSQLを併用する
try {
if (session !== null) {
/*旧データ読み込み*/
var sql =
"SELECT \"p_number\" FROM \"salesslip$precord\"" +
" WHERE \"id\"=" + salesslip.id + " AND" +
" \"precordjshid\"=" + (precord.PNo - 1);
/*print("sql="+sql);*/
var o_PNumber = session.createSQLQuery(sql).uniqueResult();
/*print("o_PNumber="+o_PNumber);*/
var suryou = product4s.stock;
var syukko_num = precord.PNumber - o_PNumber;
print("now suryou="+suryou+",syukko_num_diff="+syukko_num);
if (syukko_num !== 0) {
if (suryou - syukko_num < 0) {
return precord.PName + "の在庫 "+suryou+" に対して "+syukko_num+" を出庫しようとしました。";
}
product4s.stock = suryou - syukko_num;
}
}
} catch (e) {
e.printStackTrace();
}
return null;
Service内部で動作するスクリプトを用意する
登録
更新
削除
function process() {
(ここに開発者独自のコードを記載してください。)
}
トランザクションスクリプトとの違い
"ヘルパ > 登録/更新/削除" スクリプトとの違い
SQLを利用する
SQLでデータを取得する
try {
if (session !== null) {
var o = session.createSQLQuery(
"SELECT COUNT(*) FROM \"juser\"").uniqueResult();
print("o="+o);/* デバッグ用 */
}
} catch (e) {
e.printStackTrace();
}
トランザクションスクリプト以外の場所で session を利用する
var HibernateUtil = Java.type("jp.jasminesoft.jfc.app.HibernateUtil");
var session = HibernateUtil.openSession();
try {
/* session を利用した処理を実装する */
...
} catch (e) {
e.printStackTrace();
} finally {
if (session !== null) {
session.close();/* 忘れないこと */
}
}
重要
ストアドプロシージャを利用する
try {
// 戻り値がないパターン (更新なし、データ取得のみ)
var call1 = "{CALL FOO()}";
session.createSQLQuery(call1).executeUpdate();
// 戻り値が1つのパターン (更新なし、データ取得のみ)
var call2 = "{CALL BAR()}";
var result = session.createSQLQuery(call2).uniqueResult();
print(result);
// 戻り値が複数のパターン (更新なし、データ取得のみ)
var call3 = "{CALL BAZ()}";
var list = session.createSQLQuery(call3).list();
print(list);
} catch(e) {
e.printStackTrace();
}
Hibernate の session オブジェクトを用意する
var HibernateUtil = Java.type("jp.jasminesoft.jfc.app.HibernateUtil");
var session = HibernateUtil.openSession();
try {
var tx = session.beginTransaction();
var call = "{CALL POWER(2, 3)}";
var result = session.createSQLQuery(call).uniqueResult();
print(result);
tx.commit();
} catch(e) {
e.printStackTrace();
tx.rollback();
} finally {
if (session !== null) {
session.close();
}
}
IN/OUTパラメータを利用する
var JFCUtils = Java.type("jp.jasminesoft.jfc.JFCUtils");
var HibernateUtil = Java.type("jp.jasminesoft.jfc.app.HibernateUtil");
var Work = Java.type("org.hibernate.jdbc.Work");
var DbUtils = Java.type("org.apache.commons.dbutils.DbUtils");
var Types = Java.type("java.sql.Types");
var session = HibernateUtil.openSession();
try {
/* トランザクションを開始する */
var tx = session.beginTransaction();
var out = null;
var MyWork = Java.extend(Work, {
execute: function(connection) {
var st = null;
try {
// ストアドプロシージャ呼び出し (更新なし、データ取得のみ)
st = connection.prepareCall("{CALL XXX(?, ?)}");
// IN パラメータの指定(1番目のパラメータ)
st.setInt(1, id);
// OUTパラメータの指定(2番目のパラメータ)
st.registerOutParameter(2, Types.INTEGER);
st.execute();
// OUTパラメータの取得
out = st.getInt(2);
} finally {
DbUtils.closeQuietly(st);
}
}
});
session.doWork(new MyWork());
print(out);//デバッグ用
/* コミットする */
tx.commit();
} catch (e) {
e.printStackTrace();
tx.rollback();
} finally {
if (session !== null) {
session.close();
}
}
時間のかかるストアドプロシージャを呼び出す
var JFCUtils = Java.type("jp.jasminesoft.jfc.JFCUtils");
var HibernateUtil = Java.type("jp.jasminesoft.jfc.app.HibernateUtil");
var Work = Java.type("org.hibernate.jdbc.Work");
var DbUtils = Java.type("org.apache.commons.dbutils.DbUtils");
var Types = Java.type("java.sql.Types");
var HibernateUtil = Java.type("jp.jasminesoft.jfc.app.HibernateUtil");
var Work = Java.type("org.hibernate.jdbc.Work");
var DbUtils = Java.type("org.apache.commons.dbutils.DbUtils");
var Types = Java.type("java.sql.Types");
var session = HibernateUtil.openSession();
try {
var tx = session.beginTransaction();
var MyWork = Java.extend(Work, {
execute: function(connection) {
var st = null;
try {
st = connection.prepareCall("EXEC TEST1 ?, ?, ?");
st.setQueryTimeout(10000); /* タイムアウトの設定 */
st.registerOutParameter(3, Types.INTEGER);
st.setInt(1, 4);
st.setInt(2, 5);
st.execute();
print("result:[" + st.getInt(3) + "]");
} catch (e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(st);
}
}
});
print("Original1 procedure execute start time: ", new Date());
session.doWork(new MyWork());
print("Original1 procedure execute end time: ", new Date());
print("");
tx.commit();
} catch (e) {
e.printStackTrace();
tx.rollback();
} finally {
if (session !== null) {
session.close();
}
}
SQLやストアドプロシージャでデータを更新する
ロックの取得
var LockUtils = Java.type("jp.jasminesoft.jfc.core.util.PageLockUtils");
var model1DaoHelper = p.appctx.getBean("Model1DaoHelper");
var lockName = model1DaoHelper.getLockName();
...
try {
LockUtils.lock(lockName, "1000", p);/*ロック取得*/
...
// 更新処理
...
} catch (e) {
throw e;
} finally {
LockUtils.release(lockName, "1000", p);/*ロック解放*/
}
$SEP$
で連結した文字列としてください。詳細はWagbyが生成したコード <モデルID>Helper.java の getPrimaryKeyAsString メソッドをお読みください。(主キーの文字列表現は)このメソッドの戻り値と同じ結果となるようにしてください。モデル全体をロックする
var LockUtils = Java.type("jp.jasminesoft.jfc.core.util.PageLockUtils");
var model1DaoHelper = p.appctx.getBean("Model1DaoHelper");
var lockName = model1DaoHelper.getLockName();
...
try {
LockUtils.modelLock(lockName, p);/*ロック取得*/
...
// 更新処理
...
} catch (e) {
throw e;
} finally {
LockUtils.releaseModelLock(lockName, p);/*ロック解放*/
}
キャッシュのクリア
// ... データベースの更新が終了した ...
var CacheManagerClass = Java.type("jp.jasminesoft.wagby.app.CacheManager");
var cman = CacheManagerClass.getInstance(p);
cman.clearModel1();
clear<モデルID>
が、モデルごとに用意されています。このメソッドを実行してください。ご注意ください - 同じモデルの操作はできません
プログラムによるトランザクション管理
例 コントローラクラスの拡張(独自ボタン)
このスクリプトファイルは WEB-INF/script/customer フォルダに ShowCustomer_Original1.js として保存します。
function process() {
var ExcelFunction = Java.type("jp.jasminesoft.util.ExcelFunction");
var Jfcerror = Java.type("jp.jasminesoft.jfc.error.Jfcerror");
// トランザクション処理結果の戻り値が必要な場合は
// TransactionCallback クラスを利用します。
// 今回は戻り値が不要なので TransactionCallbackWithoutResult クラス
// を使っています。
var TransactionCallbackWithoutResult = Java.type(
"org.springframework.transaction.support.TransactionCallbackWithoutResult");
// TransactionCallbackWithoutResult クラスの doInTransactionWithoutResult()
// メソッドをオーバーライドします。
var MyTransactionCallbackWithoutResult
= Java.extend(TransactionCallbackWithoutResult, {
doInTransactionWithoutResult: function(status) {
// トランザクションが開始された状態で本メソッドが実行され、
// メソッド内で Exception が発生すると自動的にロールバック
// されます。Exception が発生せずに本メソッドが正常終了する
// と自動的にコミットされます。
var helper = p.appctx.getBean("JnewsHelper");
var service = p.appctx.getBean("JnewsEntityService");
// 1件目のデータ登録
var jnews = new Packages.jp.jasminesoft.wagby.model.jnews.Jnews();
helper.initialize(jnews, p);
jnews.limitdate = ExcelFunction.TODAY();
jnews.title = "お知らせ1";
service.insert(jnews);
// 2件目のデータ登録
jnews = new Packages.jp.jasminesoft.wagby.model.jnews.Jnews();
helper.initialize(jnews, p);
jnews.limitdate = ExcelFunction.TODAY();
jnews.title = "お知らせ2";
service.insert(jnews);
}
});
try {
// Spring が提供する TransactionTemplate を使って、
// @Transactional アノテーションを用いずにプログラム実装による
// トランザクション機能を実現します。
var transactionTemplate = p.appctx.getBean("RequiredTransactionTemplate");
transactionTemplate.execute(new MyTransactionCallbackWithoutResult());
} catch (e) {
var error = new Jfcerror();
error.content = "エラーが発生しました。" + e.message;
p.errors.addJfcerror(error);
}
// 元の画面へ遷移する。
var id = p.request.getParameter("customerid");
return "redirect:showCustomer.do?customerid="+id;
}
TransactionCallback内でHibernateのセッションを取得する
var session = p.appctx.getBean("sessionFactory").getCurrentSession();
もっと詳しく