在前面實做單表CRUD的過程中,我們并沒有使用的立體數(shù)據(jù)模型,不過在這個過程中我們對基于數(shù)據(jù)模型的開發(fā)有了一個初步的了解,下面我們基于一個主從表維護的工作來實際體驗立體數(shù)據(jù)模型的開發(fā)過程。
訪問如下的URL:??http://dorado.bstek.com/sample-center/com.bstek.dorado.sample.data.MasterDetail.d
我們可以在瀏覽器中看到如下的視圖:
這是一個主從表的維護界面,主表顯示產(chǎn)品類別,從表表示對應(yīng)產(chǎn)品類別的產(chǎn)品列表。當(dāng)我們選擇上面這個Grid的不同產(chǎn)品類別的時候,下面Grid會自動的顯示相對產(chǎn)品類別的產(chǎn)品列表。如果我們用過Dorado5技術(shù),就會知道做這么一個頁面并不復(fù)雜,我們只要在頁面上定義兩個DataSet對象,并采用主從關(guān)系的MasterLink技術(shù)做好關(guān)聯(lián)設(shè)定,就可以開發(fā)出這個頁面。而在Dorado中我們只要采用一個DataSet就可以完成開發(fā),從技術(shù)上來說我們可以認(rèn)為它就是一個立體數(shù)據(jù),第一層是產(chǎn)品分類,每一個產(chǎn)品分類下有不同的產(chǎn)品。
下面我們根據(jù)SampleCenter中的范例了解立體數(shù)據(jù)的開發(fā)技術(shù),首先打開MasterDetail.view.xml:
首先我們關(guān)注DataType的定義,其中的parent屬性,在該處被定義為:"global:Category",parent屬性是告訴Dorado該DataType的繼承關(guān)系,其中g(shù)lobal關(guān)鍵字是表明這個parent的DataType是一個全局的DataType。否則如果我們parnet屬性只配置為"Category",它就會認(rèn)為這是一個私有的DataType。全局DataType都是定義在系統(tǒng)默認(rèn)的models下,我們找到這個全局的DataType:
在視圖中我們可以看到Category的定義,在本例中為了說明DataType支持繼承而專門做了復(fù)雜的繼承關(guān)系(實際上并不一定需要設(shè)計這種繼承關(guān)系),其中Category繼承BaseCategory,BaseCategory繼承CommonEntity,在本例中CommonEntity的作用是申明一個名稱為id的propertyDef,并設(shè)置了其required屬性為true,這樣在繼承的BaseCategory,Category等DataType中就不需要關(guān)心ID的設(shè)置,自動繼承了這個特性:
前面我們說過,頁面的邏輯是選擇不同的產(chǎn)品分類,下面的Grid顯示不同的產(chǎn)品列表,對于DomainObject來講這是一個引用關(guān)系,是一個立體結(jié)構(gòu)的數(shù)據(jù)。我們在DomainObject的定義中就能看到這個引用關(guān)系:
package com.bstek.dorado.sample.entity;
@Entity
@Table(name = "CATEGORIES")
public class Category implements Serializable {
private static final long serialVersionUID = 6076304611179489256L;
private long id;
private Category parent;
private String categoryName;
private String description;
private Collection<Category> categories;
private Collection<Product> products;
...省略
@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
@JoinColumn(name = "CATEGORY_ID")
public Collection<Product> getProducts() {
return products;
}
public void setProducts(Collection<Product> products) {
this.products = products;
}
}
其中的products就是Category的產(chǎn)品列表。那么我們就容易理解Category中的中名稱為products的PropertyDef的定義了,此處的products中我們注意到未設(shè)定dataType屬性,這是用到了DataType的一個特性,這個特性我們在DataType的說明文檔中的其他特性中提到DataType與JavaBean的一一映射關(guān)系,而在Demo.model.xml中定義了名稱為Product的DataType對象:
可以看到Product對象設(shè)定了matchType,這樣com.bstek.dorado.sample.entity.Product就與Product這個全局的DataType建立了一一對應(yīng)的映射關(guān)系,在Category中雖然我們沒有定義products的DataType屬性,但Category對象根據(jù)自身的引用關(guān)系:
private Collection<Product> products;
知道這是一個Product的集合,那么根據(jù)前面的一一映射關(guān)系,就很容易的知道這個默認(rèn)的DataType為Demo.model.xml中定義的那個全局的Product。
如果沒有特殊的必要,不要在全局的Model文件中建立對象之間的關(guān)系,而是應(yīng)該在自身的View中去建立,為什么呢?這是因為如果我們在Model中就直接建立了這個關(guān)聯(lián)關(guān)系,但是我們不知道最終有多少個視圖會需要這種關(guān)系,這樣導(dǎo)致任何一個視圖引用這個全局DataType的時候都得到一個立體的數(shù)據(jù)對象,而這對很多視圖來說可能并不是必須的。另外這種立體視圖也可能會導(dǎo)致持久層不必要的數(shù)據(jù)加載從而性能損耗。
了解了DataType的基本設(shè)定之后,我們再來看DataSet的設(shè)定,如下圖:
有了前面單表CRUD的了解,對其中dataType和dataProvider的設(shè)定都比較熟悉了,根據(jù)dataProvider的設(shè)定,也很容易的可以找到對應(yīng)的Java代碼:
package com.bstek.dorado.sample.interceptor;
@Component
public class CategoryInterceptor {
@Resource
private CategoryDao categoryDao;
@DataProvider
public Collection<Category> getAll() {
return categoryDao.getAll();
}
...省略
其中的getAll方法用以向外返回一個產(chǎn)品分類列表。應(yīng)該注意到這兒我們沒有再返回product對象,這是因為我們以及在DataType中添加的products屬性了,這樣Dorado試圖解析Category數(shù)據(jù)的時候就會自動的讀取products屬性,同時我們注意的Category.java對products的裝載設(shè)定:
@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
@JoinColumn(name = "CATEGORY_ID")
public Collection<Product> getProducts() {
return products;
}
這兒采用了LAZY的裝載方式,一旦試圖讀取category的products屬性的時候,hibernate就會幫助我們自動的裝載對應(yīng)的產(chǎn)品列表。
好了到目前為止我們已經(jīng)完成了最重要的工作:通過定義一個DataSet我們可以拿到一個立體數(shù)據(jù)模型的Category列表了。接下來的工作就比較簡單了,只要在頁面上放置一些數(shù)據(jù)敏感控件,把它關(guān)聯(lián)到立體數(shù)據(jù)模型上的不同節(jié)點下就可以。下面我們來了解一下數(shù)據(jù)敏感控件的使用,我們來看一下View的定義,在SplitPanel控件中我們看到有兩個Grid,分別用來展示產(chǎn)品分類和產(chǎn)品列表,其中展示Category的Grid的定義比較簡單,我們已經(jīng)比較熟悉了,設(shè)定一下dataSet屬性就可以:
我們主要關(guān)注Product的Grid的定義:
這個Grid除了指定dataSet屬性之外,還定義了dataPath屬性"#.products"。這個表達式的含義是:"#":表示當(dāng)前記錄,連起來的意思是"當(dāng)前Category記錄下的products"。
當(dāng)前記錄是什么意思呢?
就是當(dāng)我們在Category那個Grid選擇不同行的時候,當(dāng)前行的背景色會變綠,選中的這一行就是當(dāng)前記錄,當(dāng)我們在瀏覽中選擇不同的行操作時,這個#代表的記錄會實時的變化,而這樣"#.products"就代表了當(dāng)前選中行的產(chǎn)品列表,它也是動態(tài)變化的。好了這就是這兩個Grid的重要屬性設(shè)定。
通過這個例子我們了解了立體數(shù)據(jù)模型的使用,另外我們還接觸到了一個新的技術(shù):DataPath,這個我們將在下一章中再詳細(xì)介紹。
剛才的范例,如果我們分析其HTTP請求,通過Chrome的F12打開Developer Tools,并重新刷新頁面后,查看Developer Tools中的AJAX請求,找到其中view-service的AJAX請求,并切換標(biāo)簽也到Response:
仔細(xì)分析Response的代碼:
<?xml version="1.0" encoding="UTF-8"?>
<result>
<request>
<response type="json"><![CDATA[
{
"data":[
{
"id":1,
"categoryName":"Beverages",
"products":[
{
"id":1,
"productName":"Chai",
"categoryId":1,
"discontinued":false,
"quantityPerUnit":"10 boxes x 20 bags",
"reorderLevel":12,
"unitPrice":18.0,
"unitsInStock":39,
"unitsOnOrder":0
},
{
"id":2,
...
},
...省略多個Product
],
"description":"Soft drinks, coffees, teas, beers, and ales"
},
{
"id":2,
"categoryName":"Condiments",
"products":[
{
"id":3,
"productName":"Aniseed Syrup",
"categoryId":2,
"discontinued":false,
"quantityPerUnit":"12 - 550 ml bottles",
"reorderLevel":66,
"unitPrice":10.0,
"unitsInStock":13,
"unitsOnOrder":70
},
{
"id":4,
...
},
...省略多個Product
],
"description":"Sweet and savory sauces, relishes, spreads, and seasonings"
},
...省略多個Category
],
"$dataTypeDefinitions":[
],
"$context":{
}
}
]]></response>
</request>
</result>
簡單的閱讀一下代碼,我們不難發(fā)現(xiàn)這個AJAX請求將立體數(shù)據(jù)模型中的產(chǎn)品分類和產(chǎn)品列表的所有信息都下載到客戶端了。那么這兒存在一個問題,如果產(chǎn)品分類特別多,比如1000個產(chǎn)品分類,同時每個產(chǎn)品分類下有1000個產(chǎn)品,如果按這種機制處理的話這一個AJAX請求會產(chǎn)生100W個產(chǎn)品信息的數(shù)據(jù)下載,這是不可想象的。雖然我們列舉了一種比較極端的情況,但這也說明了一個問題,對于立體數(shù)據(jù)模型的下載,我們還是有性能上的考慮的,這也對軟件開發(fā)人員提出了要求:就是開發(fā)的初期軟件開發(fā)人員要大概預(yù)期可能的數(shù)據(jù)量,再匹配相對合理的開發(fā)模式,如該分頁的時候分頁,該懶加載的時候懶加載。下面我們看針對本例中的功能實現(xiàn)懶加載實現(xiàn)的一個處理,在SampleCenter中已經(jīng)實現(xiàn)了這個范例,我們大概預(yù)覽一下這個頁面:
頁面鏈接:http://dorado.bstek.com/sample-center/com.bstek.dorado.sample.data.MasterDetailLazy.d
頁面效果:
界面效果完全一樣,唯一不同之處是在我們選擇不同的Category的時候,可以在界面的右上角看到一個提示框。當(dāng)我們切換不同的Category的時候都可以看到這么一個提示框,很容易就看出這就是懶加載的一種效果,多次單擊不同的Category后,我們再將瀏覽器切換到Developer Tools中就可以看到很多的View Service請求,打開其中的一個查看Response信息:
分析其中的數(shù)據(jù)不難發(fā)現(xiàn),現(xiàn)在每一次都只是下載當(dāng)前Category對應(yīng)的產(chǎn)品列表。通過這種懶加載處理機制就能很好的解決我們之前說的1000個產(chǎn)品分類,每個產(chǎn)品分類有1000個產(chǎn)品的性能問題。
下面再來看看實現(xiàn)懶加載的開發(fā)與之前有什么不同之處,首先打開對應(yīng)的視圖配置文件:MasterDetailLazy.view.xml
其不同之處在于其中Category這個DataType下的products為橙色,而原來的MasterDetail.view.xml下為綠色:
他們之間的差別是什么呢?我們選擇Category這個DataType,注意看IDE中的工具欄,其中DataType下可以添加三個元素:PropertyDef, Lookup, Reference
上圖我們看到的綠色的代表PropertyDef, 橙色的代表Reference。
接下來我們就介紹這兩種對象之間的差別。
PropertyDef可以認(rèn)為就是一個bean的引用,但是Reference相對來說就是一種松散的關(guān)系,它不要求Bean內(nèi)部包含這種邏輯關(guān)聯(lián)關(guān)系,而可以直接在View中的Dorado層面建立這種關(guān)系。我們來看看Reference的基本屬性設(shè)定:
其中可以定義dataProvider屬性,由于Reference不要求通過Bean本身的引用建立關(guān)系,可以直接利用Reference建立兩個Bean之間的關(guān)聯(lián)。它允許通過Reference本身定義當(dāng)前屬性的數(shù)據(jù)來源。數(shù)據(jù)的獲取是通過DataProvider得到的,這個dataProvider屬性我們已經(jīng)很熟了,我們找到相關(guān)的Java方法:
package com.bstek.dorado.sample.interceptor;
@Component
public class ProductInterceptor {
..省略
@Resource
private ProductDao productDao;
@DataProvider
public Collection<Product> getProductsByCategoryId(Long parameter) {
return productDao.find("from Product where category.id=" + parameter);
}
..省略
}
這個方法通過一個category的id獲取對應(yīng)產(chǎn)品分類中的產(chǎn)品列表。那么其中的parameter參數(shù)怎么傳進來的呢,我們看一下Reference中的parameter屬性設(shè)定:"$${this.id}"這個雙$的表達式,我們之前在EL表達式介紹的時候說明過,這時使用時動態(tài)計算的一種表達式,每一次使用都會被重新計算。這種表達式是在瀏覽器端執(zhí)行的,因此其中的this就比較容易理解了,就是指當(dāng)前使用中的實體對象(Entity),在本例就是當(dāng)前的Category。這個參數(shù)的整體含義就是獲取當(dāng)前Category的id屬性作為Reference的parameter參數(shù)的值。在范例中,當(dāng)每一次我們將當(dāng)前的Category切換為別的Category的時候,當(dāng)前Category對象都會發(fā)生變化,也就是說this所指的實體對象就會發(fā)生變化,由于采用的是動態(tài)表達式,這樣綁定產(chǎn)品列表的Grid在每一次Category發(fā)生變化的時候都會嘗試著訪問其中的products屬性,由于products是Reference類型,它就會激活其中的dataProvider機制獲取數(shù)據(jù),并返回自身的parameter屬性,獲取parameter屬性的時候就會激活動態(tài)EL表達式的計算規(guī)則,得到當(dāng)前Category的id,并作為DataProvider的參數(shù)發(fā)出獲取數(shù)據(jù)的請求。這樣我們之前在ProductInterceptor.java中的getProductsByCategoryId方法就能得到對應(yīng)的categoryId的值了。 Reference這個對象是懶裝載的,它只是把這個關(guān)系告訴瀏覽器,只有在瀏覽器中我們試圖訪問Reference的時候才會觸發(fā)數(shù)據(jù)加載工作,并通過一系列的機制調(diào)用到DataProvider獲取相關(guān)的數(shù)據(jù)。通過這種處理機制,我們可以提高某些類型頁面的性能,并極大的降低網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)量。實作主從維護界面
更多建議: