非同期スクリプト実行
最終更新日: 2020年3月14日
R8 | R9
Wagby の ScriptCodeRunner ジョブを非同期に実行することができます。
詳細画面のオリジナルボタンからジョブの実行を非同期で行うサンプルを紹介します。
はじめに"画面遷移 > 独自ボタンから外部コマンドを呼び出す"の手順でオリジナルボタンの設定を行ってください。
なおサンプルコードではモデル名を model1 としますので、開発者は ShowModel1_Original1.js というスクリプトを用意します。
上の説明ページに記載のとおり、次のスクリプトを用意することで WEB-INF/script/__job/myjob.js の内容を非同期に実行することができます。
キュー名の未指定時(空白設定)は、標準ではダウンロード用と同じ "wagbyJobQueue" という名前のキューを利用します。
Apache Active MQ Artemis と Rabbit MQ の両方を利用する設定を行った場合に、特定のメッセージングミドルウェアに送信させる場合は、対応するbeanを指定してください。
RabbitMQの場合は次のようになります。
ActiveMQの場合は次のようになります。
ジョブ実行状態は「管理処理 > ジョブ管理 > ユーザジョブ状態検索」画面で確認することができます。正常終了の場合、状態が "終了" となります。
なお、図1の画面を表示するためには、このユーザに "ジョブ管理者" 権限を割り当ててください。
参照連動自モデル保存は、参照先モデルの値をコピーして自モデルに保存する仕組みです。そのため、運用中に参照先モデルの値が変わった場合でも、自モデルにコピーされた値は(コピーした時点の値が)維持されます。
例として顧客モデルとサポートモデルを用意します。[詳細...]
非同期に参照先モデルの値に更新する方法により、システムに負荷をかけずに(ゆっくりと)データの整合性を維持することができるようになります。
customerモデルの "画面 > スクリプト > コントローラ > 画面:更新 > 実行タイミング:更新の実行" に次のスクリプトを指定します。内部では myjob2.js を実行するようにしています。更新処理本体は myjob2.js が行います。またジョブパラメータで自分自身の主キーを渡しています。
実際の更新処理を行うスクリプト myjob2.js を説明します。(ファイル名は変更できます。作成したファイルは customize/webapp/WEB-INF/script/__job フォルダに配置してください。)
上のスクリプト myjob2.js で、更新対象の support のロック取得に失敗したときは、この support モデルの更新は行われません。つまり確実にすべての support モデルを更新するという保証はありません。
そうではなく、書き換えが必須であるという要件で、ロック取得が成功するまでリトライさせるようにしたコード例を示します。
ロックエラーが発生した場合は5秒スリープし、ロックエラーが発生したデータを再度更新するようにしています。
準備
サンプル
アプリケーションを用意する
function process() {
// メッセージキューにジョブを登録する
var registryService = p.appctx.getBean(Java.type("java.lang.Class").forName("jp.jasminesoft.jfc.job.JfcjobstatusRegistryService"));
var jobparamMap = new java.util.HashMap();
jobparamMap.put("filename", "myjob.js");
registryService.sendJobMessage(null, null, "ScriptCodeRunner", jobparamMap, p);
// 元の詳細画面へ戻す (model1の詳細画面を想定)
var pkey = p.request.getParameter("pkey");
return "redirect:showModel1.do?pkey="+pkey;
}
メッセージキューの設定
特定のメッセージングミドルウェアを指定する
p.appctx.getBean("JfcjobstatusRegistryServiceAmqpImpl")
p.appctx.getBean("JfcjobstatusRegistryServiceJmsImpl")
ユーザによる非同期ジョブ実行状態の確認
応用 参照連動自モデル保存項目を非同期で更新する
ここで顧客モデルの会社名を変更したとき、自分(顧客モデル)を参照連動しているモデル(ここではサポートモデル)を検索し、自モデル保存の値を書き換えるというスクリプトを紹介します。
サンプルの解説
非同期ジョブの自動登録
function process() {
var registryService = p.appctx.getBean(Java.type("java.lang.Class").forName("jp.jasminesoft.jfc.job.JfcjobstatusRegistryService"));
var jobparamMap = new java.util.HashMap();
jobparamMap.put("filename", "myjob2.js");
jobparamMap.put("param:customerid", ExcelFunction.TEXT(customer.customerid,"0"));
registryService.sendJobMessage(null, null, "ScriptCodeRunner", jobparamMap, p);
}
更新を行うスクリプト
function process() {
/* 1件の customer データを取得 */
var customerEntityService = p.appctx.getBean("CustomerEntityService");
var customer =
customerEntityService.findById(ExcelFunction.TOINT(customerid));
var criteriaConverter = p.appctx.getBean("SupportCriteriaConverter");
var criteria = criteriaConverter.defaultCriteria();
var SupportMeta = Java.type("jp.jasminesoft.wagby.model.support.SupportMeta");
var supportMeta = new SupportMeta();
criteria.eq(supportMeta.customername, customer.customerid);
criteria.ne(supportMeta.companyname, customer.companyname);/*会社名が異なるデータのみ*/
var LockUtils = Java.type("jp.jasminesoft.jfc.core.util.LockUtils");
var supportDaoHelper = p.appctx.getBean("SupportDaoHelper");
var supportEntityService = p.appctx.getBean("SupportEntityService");
var list = supportEntityService.find(criteria);
if (list != null && list.size() > 0) {
// list から 1 つずつオブジェクトを取り出す
for (var i=0; i<list.size(); i++) {
var support = list.get(i);
try {
LockUtils.lock(support, supportDaoHelper);
support.companyname = customer.companyname;
supportEntityService.update(support); /* 更新 */
} catch (e if e instanceof PessimisticLockException) {
print("lock error " + e);
} catch (e) {
print("error " + e);
} finally {
LockUtils.release(support, supportDaoHelper);
}
}
}
}
ロック失敗への対応
これは非同期処理で support モデルの更新を行っている最中に(別の利用者が)support モデルの更新を行っていれば、それを優先したためです。
function process() {
/* 1件の customer データを取得 */
var customerEntityService = p.appctx.getBean("CustomerEntityService");
var customer =
customerEntityService.findById(ExcelFunction.TOINT(customerid));
var criteriaConverter = p.appctx.getBean("SupportCriteriaConverter");
var criteria = criteriaConverter.defaultCriteria();
var SupportMeta = Java.type("jp.jasminesoft.wagby.model.support.SupportMeta");
var supportMeta = new SupportMeta();
criteria.eq(supportMeta.customername, customer.customerid);
criteria.ne(supportMeta.companyname, customer.companyname);/*会社名が異なるデータのみ*/
var ArrayList = Java.type("java.util.ArrayList");
var Thread = Java.type("java.lang.Thread");
var supportEntityService = p.appctx.getBean("SupportEntityService");
var list = supportEntityService.find(criteria);
if (list != null && list.size() > 0) {
while (true) {
var lockfailed = new ArrayList();
updateSupports(supportEntityService, customer, list, lockfailed);
if (lockfailed.size() == 0) {
break;// ロック失敗がなくなれば終了する。
}
print("lock failed, continue " + lockfailed.size());
Thread.sleep(5000); // 5000 ms 待つ
list = lockfailed;// 失敗したオブジェクトを指す
}
}
}
function updateSupports(supportEntityService, customer, list, lockfailed) {
var LockUtils = Java.type("jp.jasminesoft.jfc.core.util.LockUtils");
var PessimisticLockException = Java.type("javax.persistence.PessimisticLockException");
var supportDaoHelper = p.appctx.getBean("SupportDaoHelper");
// list から 1 つずつオブジェクトを取り出す
for (var i=0; i<list.size(); i++) {
var support = list.get(i);
try {
LockUtils.lock(support, supportDaoHelper);
support.companyname = customer.companyname;
supportEntityService.update(support); /* 更新 */
} catch (e if e instanceof PessimisticLockException) {
print("lock error " + e);
lockfailed.add(support);
} catch (e) {
print("error " + e);
} finally {
LockUtils.release(support, supportDaoHelper);
}
}
}