モデルとオブジェクト
Designerに入力した「モデル」の設計情報(リポジトリ)から、データベースのテーブル定義 (CREATE DDL 文)と、クラス定義 (Javaのクラス) が自動生成されます。この関係を図1に示します。
モデル定義とテーブル、クラス図の関係
「モデル」は、すべて Java オブジェクトとして操作することができます。リレーショナルデータベースからみた場合、1データ(1レコード)が、1つのモデルの実体となります。
この実体のことを「オブジェクト」と呼びます。
スクリプトでは、対象のオブジェクトを直接、操作できます。例えばモデルID customer を扱うためには、スクリプト内に直接 "customer" という変数名が使えます。これがオブジェクトです。
「項目ID」は、「オブジェクト名 + "." + 項目ID」で参照することができます。例えば name という項目を用意していた場合、"customer.name" と記述できます。
モデルの種類
Designerで定義した「モデル」から、図2に示す複数のクラスが生成されます。
モデルの種類
分類
名称
説明
入れ物
ストアモデル
データベースのレコードと 1:1 の関係がある。
コンディションモデル
検索条件を格納する。
リストモデル
一覧表示項目を格納する。
プレゼンテーションモデル
ストア/コンディション/リストそれぞれに対となるプレゼンテーションモデルが用意される。Webフォーム(画面に表示される入力フォーム)に対応したモデル。すべて文字列型の値となる。
操作
サービス
業務処理を行う。
データ操作(DAO)
ストアモデルとデータベース間の基本的なやりとり(CRUD)を行う。
ヘルパ
モデルの初期や計算、参照連動の解決といったモデル操作に付随する処理を行う。また各モデルとプレゼンテーションモデルの相互変換も行う。
スクリプト内では、次の変数名で利用できます。
名前 スクリプト内での表現 例 説明
ストアモデル
モデル名
customer
データベースに保存されるオブジェクト。
プレゼンテーションモデル
モデル名_p
customer_p
Webフォームから入力された値。
コンディションモデル
モデル名_c
customer_c
検索条件が格納されたオブジェクト。
コンディションのプレゼンテーションモデル
モデル名_cp
customer_cp
Webフォームから入力された検索条件の値。
リストモデル
モデル名_l
customer_l
一覧表示される値が格納されたオブジェクト。
リストのプレゼンテーションモデル
モデル名_lp
customer_lp
一覧表示オブジェクトの画面出力用。(入力は用意されない)
注意
これらのオブジェクトは、存在しない場合もあります。例えば削除処理の場合、ストアモデルは用意されていません。検索画面でなければコンディションモデルは用意されません。画面遷移の流れによって存在する/しないが変わる場合では、オブジェクトが存在するかどうかのチェック処理 (if文による判断) を行うようにしてください。
ストアモデル
ストアモデルは「テーブル」の「1レコード」の情報を格納するためのオブジェクトです。ほとんどのスクリプト中で利用することができます。
Wagbyによって生成されたコードの一部を抜粋します。
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "customer")
@XmlType(propOrder={ "customerid_", "name_", "namekana_", "email_", "tel_", "fax_", "companyname_", "companynamekana_", "companytel_", "companyfax_", "companyzipcode_", "companyaddress_", "companyurl_", "officename_", "officenamekana_", "officetel_", "officefax_", "officezipcode_", "officeaddress_", "keyperson_", "retiredate_", "note_" })
public class Customer extends jp.jasminesoft.jfc.app.ContainerBase<Customer> implements java.io.Serializable, Cloneable {
@XmlElement(name="customerid")
private int customerid_;
@XmlElement(name="name")
private String name_;
...
public final String getName() {
return (name_);
}
public final void setName(String name) {
this.name_ = name;
}
...
}
特徴
「項目ID」が(Java クラスの)"フィールド" に対応します。
すべてのフィールドはprivateで宣言されます。
フィールドに対応するアクセッサ (setter/getter) メソッドが用意されます。
"@Xml" ではじまるアノテーションは JAXB が提供するものです。これによりオブジェクトと XML 表現の相互変換を実現しています。(例 : Wagby のインポートエクスポート機能でやりとりする xml ファイルは、このアノテーションによって実現されています。)
リレーショナルデータベースのテーブル、列は、それぞれストアモデルのクラス名、フィールド名と対応しています。この対応処理は(Wagbyに同梱されている)Hibernate というミドルウェアが管理しています。[Wikipedia... ]
クラス名、フィールド名の命名規則
クラス名
モデルIDの先頭を大文字にします。元々が大文字の場合は、そのままです。
アンダースコア (_) は除去し、その次の文字を大文字にします。sales_slip は SalesSlip になります。
フィールド名
項目IDに対応したフィールドが用意されます。
項目名の末尾にアンダースコアを含めます。id は id_ になります。
アンダースコア (_) を取り除き、その次の文字を大文字にします。customer_id は、customerId_ になります。
データを取得する getter と、値をセットする setter メソッドが用意されます。例えば getCustomerId() や setCustomerId(引数) などです。
スクリプトから操作する場合、常に getter/setter メソッドが使われます。例えばスクリプト中で customer.customerId と記述したとき、内部では getCustomerId() メソッドが呼び出されています。つまりスクリプトで直接、モデルのフィールドを操作しません。
Javaの型
Designerで指定した型と、実際に生成されるコードで使われる Java の型との関係は次のとおりです。
Designerで指定した型
Javaの型
整数型
Integer (非・必須) int (必須)
1,2,4,8バイト整数
Byte, Short, Integer, Long (非・必須) byte, short, integer, long (必須)
4バイト小数
Float (非・必須) float (必須)
8バイト小数
Double (非・必須) double (必須)
文字列、ファイル、メール、URL、郵便番号
String
日付
java.sql.Date
時刻
java.sql.Time
日付時刻
java.sql.Timestamp
必須項目の扱い
Designerで「必須」と設定した場合、生成されるコードが変わります。
Designerの型
区分
Javaの型
初期値
未入力の確認方法
テーブル
整数型
非・必須
Integer
null
checkメソッド
NOT NULL が付与される
整数型
必須
int
0
なし
-
整数型項目で非・必須の場合、checkメソッドが提供されます。具体的には check項目ID() というメソッドです。戻り値は boolean 型となり、値はtrue
かfalse
のいずれかです。これを使って未入力かどうかの確認を行うことができます。
なお getメソッド (get項目ID) を使ったとき、値が null の場合は "-1" が返されます。
Integer と int の違い / null と 0 の違い
Integer と int は異なる扱いです。Integer は null (未入力) という状態がありますが、int は必ず何らかの値が入るもので、null を許容しません。int の初期値は 0 となりますが、入力された値が 0 なのか、それとも未入力で初期値が 0 なのかは判断できません。
テーブル定義が変わります
必須項目とした場合、そのモデルのテーブル定義が変更されます。該当項目に NOT NULL が付与されます。
単一の値と、配列
モデル項目の型によってスクリプトで扱う方法が変わります。
項目の型 扱い
文字列型、数値型、日付型、モデル参照(リストボックス、ラジオボタン、検索) 一つの値をもつ
モデル参照(チェックボックス)、繰り返し項目、繰り返しコンテナ項目 配列となる
詳細はこのあと説明します。
モデルの基本操作
customer モデルを用意し、「画面 > スクリプト」で実行タイミングを「登録」とし、次のスクリプトコードを入力します。
var customerid = customer.customerid; /* 主キー(整数型) */
var name = customer.name; /* 文字列型 */
var companyname = customer.companyname; /* モデル参照 */
var companynamekana = customer.companynamekana; /* 参照連動 */
var retiredate = customer.retiredate; /* 日付型 */
print("customerid="+customerid);
print("name="+name);
print("companyname="+companyname);
print("compnaynamekana="+companynamekana);
print("retiredate="+retiredate);
customer モデルの新規登録が成功すると、Tomcat を実行しているコンソールに次のように表示されます。
customerid=1000
name=山田 太郎
companyname=1000
compnaynamekana=じゃすみんそふと
retiredate=2010-03-31
数字、文字列、日付はデータベースに格納している値がそのまま出力されます。
モデル参照項目 companyname は、参照先モデルの「主キー」が格納されていることがわかります。また、参照連動項目 companynamekana は、参照先のデータが含まれていることがわかります。なお、参照していない場合は companyname の値は "-1" になります。(参照先モデルの主キー型項目が整数型の場合)
モデル参照項目の詳細は、このあと説明します。
[例] 値をセットする
上の例は、もともと格納されている値を表示させるものでした。今度は値をセットするスクリプトを紹介します。
customer.age = 20; /* 整数型 */
customer.name = "鈴木 一郎"; /* 文字列型 */
customer.retiredate = ExcelFunction.DATEVALUE("1972-5-15"); /* 日付型 */
整数型の項目は直接、数値を指定することができます。
文字列型の項目はダブルクォーテーションで値を指定します。日本語文字も指定できます。
日付型の項目は Java の日付オブジェクトを指定します。
ここでは Wagby が提供している関数 DATEVALUE を用いた例を紹介します。
スクリプトで関数を利用する場合は接頭語 ExcelFunction を付与してください。
DAO
1つのストアモデルに対して、1つのDAO (Data Access Object) が用意されます。
DAOが提供する get メソッドを使って、データベースから1つのデータをオブジェクトとして読み込むことができます。スクリプト中での利用例を示します。
// 顧客ID(主キー)が1000の顧客データを読み込む。
var customerDao = p.appctx.getBean("CustomerDao");
var customer = customerDao.get(1000);
DAO の実体は Java のオブジェクトです。p.appctx.getBean("モデルIDDao"); で、オブジェクトを取得してください。
スクリプト内では通常、customer オブジェクトを DAO を使って取得したり、あるいは (Java の new 演算子を用いて)生成することはありません。多くの場合、スクリプトが呼び出される前 に、Wagby がデータベースから読み込んだ値からオブジェクトを生成し、すぐにスクリプトで使えるように準備済みです。現在扱っているオブジェクトとは別の オブジェクトを利用する必要がある、というときに DAO から値を取得してください。この場合、取得したいデータの主キーが必要です。
繰り返し
繰り返し項目
Designerで「繰り返し」を指定した場合、内部では次のように扱われます。
繰り返し項目は「文字列」「数字」「日付、時刻、日付時刻」に指定できます。
内部では List というコレクションに複数の値を保持します。
@XmlElement(name="tel")
private java.util.List<String> tel_ = new java.util.ArrayList<String>();
データベースには別テーブルが用意されます。例えば customer モデルの tel という項目が繰り返し指定されたとき、別テーブル "customer$tel" が用意されます。
DAOからオブジェクトを取得したとき、Listに値は入った状態です。(つまりテーブルの join は必ず行われます。)
繰り返しコンテナ
Designerで「繰り返しコンテナ」を指定した場合、内部では次のように扱われます。
繰り返しコンテナは、独立した(コンテナ名の)クラスになります。例えば繰り返しコンテナ "cont" という項目を用意したとき、Cont というクラスが生成されます。
内部では List というコレクションに複数のコンテナを保持します。
@XmlElement(name="cont")
private java.util.List<Cont> cont_ = new java.util.ArrayList<Cont>();
データベースには別テーブル "customer$cont" が用意されます。自動的に外部キー関係になります。
DAOからオブジェクトを取得したとき、Listに値は入った状態です。(つまりテーブルの join は必ず行われます。)
繰り返しコンテナの入れ子はできません。
スクリプト例 : 参照
スクリプトでは、繰り返しコンテナは「配列」として扱うことができます。
取得した繰り返しコンテナは、モデルと同様にオブジェクトとして操作できます。
次の例は休暇申請 (leave) モデルに含まれる繰り返しコンテナ「理由 (memo)」を取得するスクリプトです。
var array = leave.memo; /* memoは繰り返しコンテナ。arrayは配列形式となっている。*/
for (var i=0; i<array.length; i++) {
print("memo="+array[i]);
print("memo.memo_id="+array[i].memoId);
print("memo.memo_reason="+array[i].memoReason);
}
スクリプト例 : 和を求める
次の例は売上伝票 (salesslip) モデルに含まれる「明細情報 (precord)」を操作し、単価と数量の和を求めるコードです。
var total = 0;
var array = salesslip.precord; /* 繰り返しコンテナ */
for (var i=0; i<array.length; i++) {
if (条件が成立) {
total = total + (array[i].PPrice * array[i].PNumber);
}
}
salesslip.totalPrice = total; /* 値を代入(セット)*/
上記コードにあるように、計算結果をオブジェクト(のモデル項目)に代入することもできます。
スクリプト例 : 新規追加
次の例は見積伝票 (quotation) モデルの新規登録画面を開いたタイミングで、繰り返しコンテナ "precord" を3件、追加するスクリプトです。
var PRecordClass = Java.type("jp.jasminesoft.wagby.model.quotation.Precord");
var array = new Array(3);
array[0] = new PRecordClass();
array[1] = new PRecordClass();
array[2] = new PRecordClass();
(各配列への、具体的な値の設定コードは省略)
quotation.precord = array;
スクリプト例 : 削除
「ヘルパ > 更新」のスクリプトを用いると、更新直前のモデルに何らかの操作を行うことができます。ここでは kanridata モデルに含まれる繰り返しコンテナ zrecord について、(zrecord の項目) suryo が未入力のコンテナ行を削除するという例を紹介します。
var array = kanridata.zrecord;
var removeList = new java.util.ArrayList();
for (var i=0; i<array.length; i++) {
if (!array[i].checkSuryo()) {/*非必須の数値項目はcheckXXメソッドが利用できる*/
removeList.add(i);/*いったん削除対象の行のインデックスを記憶しておく*/
}
}
for (var i=removeList.size()-1; i>=0; --i) {/*逆から削除していく*/
var index = removeList.get(i);
kanridata.removeZrecord(index);/*removeメソッドを用いる*/
}
/*繰り返しコンテナの内部IDを詰める*/
array = kanridata.zrecord;
for (var i=0; i<array.length; i++) {
array[i].zrecordjshid = i;
}
トランザクションとの関係
繰り返しコンテナは(データベース上では)別テーブルですが、ストアモデルでは一つのデータ内に明細情報が含まれる構造です。ここで、繰り返しコンテナを含むモデルの登録・更新・削除は、二つのテーブルの操作がセットで行われます。すなわち、暗黙的なトランザクション処理になります。
図3 Javaクラスとテーブルの関係
削除に関する留意点
上図において、注文モデルを削除すると、注文明細も同時に削除されます。つまり繰り返しコンテナは強い結合関係にあります。
モデル参照(リストボックス、ラジオボタン、検索ウィンドウ)
モデル参照(リストボックス、ラジオボタン、検索ウィンドウ)では、参照しているモデルの主キーを保持します。
図4 モデル参照関係
「注文DAO」からモデルを読み込んだとき、外部キーである「顧客ID」の値 (例:"1000")は含まれていますが、「顧客」の情報そのものは含まれません。顧客IDを引数とし、「顧客DAO」を使うことで「顧客」データを取得することができます。
var customerDao = p.appctx.getBean("CustomerDao");
var customer = customerDao.get(order.customerid);
削除に関する留意点
「注文」モデルを削除しても、キーで紐付いている「顧客」モデルが削除されることはありません。
一方、「顧客」モデルを削除すると、「注文」モデルの参照先が不定となります。
モデル参照(チェックボックス)
モデル参照(チェックボックス)では、参照元モデルの主キーをListに保持します。
図5 モデル参照関係(チェックボックス)
「アンケートDAO」からモデルを読み込んだとき、外部キーである「選択肢ID」の値を含むリストが用意されます。スクリプトでは、これを配列として取得することができます。
値の参照
enq モデルに、チェックボックス項目である options を用意しました。どのような値をもっているかを確認するためのコードは次のようになります。
var array = enq.options; /* チェックボックスは配列として取得できる */
...
for (i=0; i<array.length; i++) {
print("option="+array[i]);
}
チェックされたものだけが配列 array に含まれます。例えば、次のように表示されます。
このように配列となっている値であっても、スクリプトでは常に「var 型」として用意します。
配列なので、for ループなどを用いて値を参照することができます。
IDではなく内容部を取得する
上のコードにあるように、ストアモデルには「選択肢」の情報そのものは含まれません。「選択肢DAO」を使って「選択肢」データを取得することができます。
var optionDao = p.appctx.getBean("OptionDao");
var array = enq.options;
for (int i=0; i<array.length; i++) {
var option = optionDao.get(array[i]);
print(option.content);/* 内容部を表示する */
}
チェックボックスを個別に操作する
enq.options に(チェックボックスの)ID を追加したり、削除することができます。addXXX, removeXXX, clearXXX, setXXX, size というメソッドを使います。これらは常に自動生成されるメソッドです。
/* test 1:値を一つずつ追加する */
enq.addOptions(1);
enq.addOptions(2);
print("test1 "+enq.options);
/* test 2:値を消去する */
/* 添字は0から始まる。以下は最初にチェックされていたものを解除する */
enq.removeOptions(0);
print("test2 "+enq.options);
/* test 3:すべての値を消去する */
enq.clearOptions();
print("test3 "+enq.options);
/* test 4:複数の値をまとめてセットする */
var IntArrayType = Java.type("int[]");
var arr = new IntArrayType(3);
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
enq.setOptions(arr);
print("test4 "+enq.options);
/* いくつチェックされているかを知る */
var size = enq.sizeOptions();
print("size="+size);
外部キー
親子関係では、親 (1) に対して子 (N) という関係が成立します。親には特段の記述はなく、子が親のキーを保持します。
図6 外部キー
このことから、Wagbyの「モデル参照」と「外部キー」に、クラス定義上の差異はありません。「外部キー」と指定することで、親の詳細画面に子の一覧が表示されるといった機能が自動的に付与されます。
削除に関する留意点
標準では「親」モデルを削除すると、キーで紐付いている「子」モデルも同時に削除されます。
しかし設定によって、子モデルを残すこともできます。
この場合の親子関係は弱い参照関係になります。
参照連動
参照連動は、参照元モデルの値を、参照先に転記する機能です。自動生成されたコードではDAOからデータを読み込み、値のセット処理を行います。
図7 参照連動
ストアモデルの役割
ストアモデルは「データベースに保持される情報」だけでなく「画面に表示される情報」も含めることができます。参照連動はそのために用意された仕組みです。
図8 ストアモデルの役割
ファイル型
ファイル型は内部では二つのフィールド(ならびにテーブル上でも二つのカラム)から構成されます。
Designerで定義した項目名には、ファイル名が入ります。
内部で自動的に用意される「項目名_jshfilename」は、ファイルの実パス情報(文字列)が格納されます。
ファイル情報そのものはBLOBとして管理しません。あくまでファイル名と実パスとして管理しています。そのため物理ファイルが削除されてしまうと、参照エラーになります。
生成されたモデルのJavaコードを直接確認する
自動生成された Java のソースコードを直接、確認することで、どのようなメソッドが用意されているかを知ることができます。
例えば customer モデルに関するストアモデルは次のファイルになっています。
wagbydesigner/webapps/wagbydesigner/WEB-INF/env/work/srcgen/jp/jasminesoft/wagby/model/customer/Customer.java
それぞれの区分毎に、生成されるモデルのソースコードのフォルダが変わります。(フォルダの起点は、wagbydesigner/webapps/wagbydesigner/WEB-INF/env/work/srcgen です。)
モデルIDを customer としたときの例を示します。
ストアモデル
FQCN jp.jasminesoft.wagby.model.customer.Customer
ファイル jp/jasminesoft/wagby/model/customer/Customer.java
プレゼンテーションモデル
FQCN jp.jasminesoft.wagby.model.customer_p.CustomerP
ファイル jp/jasminesoft/wagby/model/customer_p/CustomerP.java
コンディションモデル
FQCN jp.jasminesoft.wagby.model.customer_c.CustomerC
ファイル jp/jasminesoft/wagby/model/customer_c/CustomerC.java
コンディションのプレゼンテーションモデル
FQCN jp.jasminesoft.wagby.model.customer_cp.CustomerCp
ファイル jp/jasminesoft/wagby/model/customer_cp/CustomerCp.java
リストモデル
FQCN jp.jasminesoft.wagby.model.customer_l.CustomerL
ファイル jp/jasminesoft/wagby/model/customer_l/CustomerL.java
リストのプレゼンテーションモデル
FQCN jp.jasminesoft.wagby.model.customer_lp.CustomerLp
ファイル jp/jasminesoft/wagby/model/customer_lp/CustomerLp.java