實(shí)作主從維護(hù)頁(yè)面

2024-03-07 18:39 更新

在前面實(shí)做單表CRUD的過(guò)程中,我們并沒(méi)有使用的立體數(shù)據(jù)模型,不過(guò)在這個(gè)過(guò)程中我們對(duì)基于數(shù)據(jù)模型的開(kāi)發(fā)有了一個(gè)初步的了解,下面我們基于一個(gè)主從表維護(hù)的工作來(lái)實(shí)際體驗(yàn)立體數(shù)據(jù)模型的開(kāi)發(fā)過(guò)程。 訪問(wèn)如下的URL:??http://dorado.bstek.com/sample-center/com.bstek.dorado.sample.data.MasterDetail.d 我們可以在瀏覽器中看到如下的視圖: 這是一個(gè)主從表的維護(hù)界面,主表顯示產(chǎn)品類別,從表表示對(duì)應(yīng)產(chǎn)品類別的產(chǎn)品列表。當(dāng)我們選擇上面這個(gè)Grid的不同產(chǎn)品類別的時(shí)候,下面Grid會(huì)自動(dòng)的顯示相對(duì)產(chǎn)品類別的產(chǎn)品列表。如果我們用過(guò)Dorado5技術(shù),就會(huì)知道做這么一個(gè)頁(yè)面并不復(fù)雜,我們只要在頁(yè)面上定義兩個(gè)DataSet對(duì)象,并采用主從關(guān)系的MasterLink技術(shù)做好關(guān)聯(lián)設(shè)定,就可以開(kāi)發(fā)出這個(gè)頁(yè)面。而在Dorado中我們只要采用一個(gè)DataSet就可以完成開(kāi)發(fā),從技術(shù)上來(lái)說(shuō)我們可以認(rèn)為它就是一個(gè)立體數(shù)據(jù),第一層是產(chǎn)品分類,每一個(gè)產(chǎn)品分類下有不同的產(chǎn)品。 下面我們根據(jù)SampleCenter中的范例了解立體數(shù)據(jù)的開(kāi)發(fā)技術(shù),首先打開(kāi)MasterDetail.view.xml: 首先我們關(guān)注DataType的定義,其中的parent屬性,在該處被定義為:"global:Category",parent屬性是告訴Dorado該DataType的繼承關(guān)系,其中g(shù)lobal關(guān)鍵字是表明這個(gè)parent的DataType是一個(gè)全局的DataType。否則如果我們parnet屬性只配置為"Category",它就會(huì)認(rèn)為這是一個(gè)私有的DataType。全局DataType都是定義在系統(tǒng)默認(rèn)的models下,我們找到這個(gè)全局的DataType: 在視圖中我們可以看到Category的定義,在本例中為了說(shuō)明DataType支持繼承而專門(mén)做了復(fù)雜的繼承關(guān)系(實(shí)際上并不一定需要設(shè)計(jì)這種繼承關(guān)系),其中Category繼承BaseCategory,BaseCategory繼承CommonEntity,在本例中CommonEntity的作用是申明一個(gè)名稱為id的propertyDef,并設(shè)置了其required屬性為true,這樣在繼承的BaseCategory,Category等DataType中就不需要關(guān)心ID的設(shè)置,自動(dòng)繼承了這個(gè)特性: 前面我們說(shuō)過(guò),頁(yè)面的邏輯是選擇不同的產(chǎn)品分類,下面的Grid顯示不同的產(chǎn)品列表,對(duì)于DomainObject來(lái)講這是一個(gè)引用關(guān)系,是一個(gè)立體結(jié)構(gòu)的數(shù)據(jù)。我們?cè)贒omainObject的定義中就能看到這個(gè)引用關(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的一個(gè)特性,這個(gè)特性我們?cè)贒ataType的說(shuō)明文檔中的其他特性中提到DataType與JavaBean的一一映射關(guān)系,而在Demo.model.xml中定義了名稱為Product的DataType對(duì)象: 可以看到Product對(duì)象設(shè)定了matchType,這樣com.bstek.dorado.sample.entity.Product就與Product這個(gè)全局的DataType建立了一一對(duì)應(yīng)的映射關(guān)系,在Category中雖然我們沒(méi)有定義products的DataType屬性,但Category對(duì)象根據(jù)自身的引用關(guān)系:

private Collection<Product> products;

知道這是一個(gè)Product的集合,那么根據(jù)前面的一一映射關(guān)系,就很容易的知道這個(gè)默認(rèn)的DataType為Demo.model.xml中定義的那個(gè)全局的Product。 如果沒(méi)有特殊的必要,不要在全局的Model文件中建立對(duì)象之間的關(guān)系,而是應(yīng)該在自身的View中去建立,為什么呢?這是因?yàn)槿绻覀冊(cè)贛odel中就直接建立了這個(gè)關(guān)聯(lián)關(guān)系,但是我們不知道最終有多少個(gè)視圖會(huì)需要這種關(guān)系,這樣導(dǎo)致任何一個(gè)視圖引用這個(gè)全局DataType的時(shí)候都得到一個(gè)立體的數(shù)據(jù)對(duì)象,而這對(duì)很多視圖來(lái)說(shuō)可能并不是必須的。另外這種立體視圖也可能會(huì)導(dǎo)致持久層不必要的數(shù)據(jù)加載從而性能損耗。 了解了DataType的基本設(shè)定之后,我們?cè)賮?lái)看DataSet的設(shè)定,如下圖: 有了前面單表CRUD的了解,對(duì)其中dataType和dataProvider的設(shè)定都比較熟悉了,根據(jù)dataProvider的設(shè)定,也很容易的可以找到對(duì)應(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方法用以向外返回一個(gè)產(chǎn)品分類列表。應(yīng)該注意到這兒我們沒(méi)有再返回product對(duì)象,這是因?yàn)槲覀円约霸贒ataType中添加的products屬性了,這樣Dorado試圖解析Category數(shù)據(jù)的時(shí)候就會(huì)自動(dòng)的讀取products屬性,同時(shí)我們注意的Category.java對(duì)products的裝載設(shè)定:

@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
@JoinColumn(name = "CATEGORY_ID")
public Collection<Product> getProducts() {
    return products;
}

這兒采用了LAZY的裝載方式,一旦試圖讀取category的products屬性的時(shí)候,hibernate就會(huì)幫助我們自動(dòng)的裝載對(duì)應(yīng)的產(chǎn)品列表。 好了到目前為止我們已經(jīng)完成了最重要的工作:通過(guò)定義一個(gè)DataSet我們可以拿到一個(gè)立體數(shù)據(jù)模型的Category列表了。接下來(lái)的工作就比較簡(jiǎn)單了,只要在頁(yè)面上放置一些數(shù)據(jù)敏感控件,把它關(guān)聯(lián)到立體數(shù)據(jù)模型上的不同節(jié)點(diǎn)下就可以。下面我們來(lái)了解一下數(shù)據(jù)敏感控件的使用,我們來(lái)看一下View的定義,在SplitPanel控件中我們看到有兩個(gè)Grid,分別用來(lái)展示產(chǎn)品分類和產(chǎn)品列表,其中展示Category的Grid的定義比較簡(jiǎn)單,我們已經(jīng)比較熟悉了,設(shè)定一下dataSet屬性就可以: 我們主要關(guān)注Product的Grid的定義: 這個(gè)Grid除了指定dataSet屬性之外,還定義了dataPath屬性"#.products"。這個(gè)表達(dá)式的含義是:"#":表示當(dāng)前記錄,連起來(lái)的意思是"當(dāng)前Category記錄下的products"。 當(dāng)前記錄是什么意思呢? 就是當(dāng)我們?cè)贑ategory那個(gè)Grid選擇不同行的時(shí)候,當(dāng)前行的背景色會(huì)變綠,選中的這一行就是當(dāng)前記錄,當(dāng)我們?cè)跒g覽中選擇不同的行操作時(shí),這個(gè)#代表的記錄會(huì)實(shí)時(shí)的變化,而這樣"#.products"就代表了當(dāng)前選中行的產(chǎn)品列表,它也是動(dòng)態(tài)變化的。好了這就是這兩個(gè)Grid的重要屬性設(shè)定。 通過(guò)這個(gè)例子我們了解了立體數(shù)據(jù)模型的使用,另外我們還接觸到了一個(gè)新的技術(shù):DataPath,這個(gè)我們將在下一章中再詳細(xì)介紹。

性能優(yōu)化

剛才的范例,如果我們分析其HTTP請(qǐng)求,通過(guò)Chrome的F12打開(kāi)Developer Tools,并重新刷新頁(yè)面后,查看Developer Tools中的AJAX請(qǐng)求,找到其中view-service的AJAX請(qǐng)求,并切換標(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,
                    ...
                },
                ...省略多個(gè)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,
                    ...
                },
                ...省略多個(gè)Product
            ],
            "description":"Sweet and savory sauces, relishes, spreads, and seasonings"
        },
        ...省略多個(gè)Category
    ],
    "$dataTypeDefinitions":[
    ],
    "$context":{
    }
}
]]></response>
</request>
</result>

簡(jiǎn)單的閱讀一下代碼,我們不難發(fā)現(xiàn)這個(gè)AJAX請(qǐng)求將立體數(shù)據(jù)模型中的產(chǎn)品分類和產(chǎn)品列表的所有信息都下載到客戶端了。那么這兒存在一個(gè)問(wèn)題,如果產(chǎn)品分類特別多,比如1000個(gè)產(chǎn)品分類,同時(shí)每個(gè)產(chǎn)品分類下有1000個(gè)產(chǎn)品,如果按這種機(jī)制處理的話這一個(gè)AJAX請(qǐng)求會(huì)產(chǎn)生100W個(gè)產(chǎn)品信息的數(shù)據(jù)下載,這是不可想象的。雖然我們列舉了一種比較極端的情況,但這也說(shuō)明了一個(gè)問(wèn)題,對(duì)于立體數(shù)據(jù)模型的下載,我們還是有性能上的考慮的,這也對(duì)軟件開(kāi)發(fā)人員提出了要求:就是開(kāi)發(fā)的初期軟件開(kāi)發(fā)人員要大概預(yù)期可能的數(shù)據(jù)量,再匹配相對(duì)合理的開(kāi)發(fā)模式,如該分頁(yè)的時(shí)候分頁(yè),該懶加載的時(shí)候懶加載。下面我們看針對(duì)本例中的功能實(shí)現(xiàn)懶加載實(shí)現(xiàn)的一個(gè)處理,在SampleCenter中已經(jīng)實(shí)現(xiàn)了這個(gè)范例,我們大概預(yù)覽一下這個(gè)頁(yè)面: 頁(yè)面鏈接:http://dorado.bstek.com/sample-center/com.bstek.dorado.sample.data.MasterDetailLazy.d 頁(yè)面效果: 界面效果完全一樣,唯一不同之處是在我們選擇不同的Category的時(shí)候,可以在界面的右上角看到一個(gè)提示框。當(dāng)我們切換不同的Category的時(shí)候都可以看到這么一個(gè)提示框,很容易就看出這就是懶加載的一種效果,多次單擊不同的Category后,我們?cè)賹g覽器切換到Developer Tools中就可以看到很多的View Service請(qǐng)求,打開(kāi)其中的一個(gè)查看Response信息: 分析其中的數(shù)據(jù)不難發(fā)現(xiàn),現(xiàn)在每一次都只是下載當(dāng)前Category對(duì)應(yīng)的產(chǎn)品列表。通過(guò)這種懶加載處理機(jī)制就能很好的解決我們之前說(shuō)的1000個(gè)產(chǎn)品分類,每個(gè)產(chǎn)品分類有1000個(gè)產(chǎn)品的性能問(wèn)題。 下面再來(lái)看看實(shí)現(xiàn)懶加載的開(kāi)發(fā)與之前有什么不同之處,首先打開(kāi)對(duì)應(yīng)的視圖配置文件:MasterDetailLazy.view.xml 其不同之處在于其中Category這個(gè)DataType下的products為橙色,而原來(lái)的MasterDetail.view.xml下為綠色: 他們之間的差別是什么呢?我們選擇Category這個(gè)DataType,注意看IDE中的工具欄,其中DataType下可以添加三個(gè)元素:PropertyDef, Lookup, Reference 上圖我們看到的綠色的代表PropertyDef, 橙色的代表Reference。 接下來(lái)我們就介紹這兩種對(duì)象之間的差別。 PropertyDef可以認(rèn)為就是一個(gè)bean的引用,但是Reference相對(duì)來(lái)說(shuō)就是一種松散的關(guān)系,它不要求Bean內(nèi)部包含這種邏輯關(guān)聯(lián)關(guān)系,而可以直接在View中的Dorado層面建立這種關(guān)系。我們來(lái)看看Reference的基本屬性設(shè)定: 其中可以定義dataProvider屬性,由于Reference不要求通過(guò)Bean本身的引用建立關(guān)系,可以直接利用Reference建立兩個(gè)Bean之間的關(guān)聯(lián)。它允許通過(guò)Reference本身定義當(dāng)前屬性的數(shù)據(jù)來(lái)源。數(shù)據(jù)的獲取是通過(guò)DataProvider得到的,這個(gè)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);
    }
    ..省略
}

這個(gè)方法通過(guò)一個(gè)category的id獲取對(duì)應(yīng)產(chǎn)品分類中的產(chǎn)品列表。那么其中的parameter參數(shù)怎么傳進(jìn)來(lái)的呢,我們看一下Reference中的parameter屬性設(shè)定:"$${this.id}"這個(gè)雙$的表達(dá)式,我們之前在EL表達(dá)式介紹的時(shí)候說(shuō)明過(guò),這時(shí)使用時(shí)動(dòng)態(tài)計(jì)算的一種表達(dá)式,每一次使用都會(huì)被重新計(jì)算。這種表達(dá)式是在瀏覽器端執(zhí)行的,因此其中的this就比較容易理解了,就是指當(dāng)前使用中的實(shí)體對(duì)象(Entity),在本例就是當(dāng)前的Category。這個(gè)參數(shù)的整體含義就是獲取當(dāng)前Category的id屬性作為Reference的parameter參數(shù)的值。在范例中,當(dāng)每一次我們將當(dāng)前的Category切換為別的Category的時(shí)候,當(dāng)前Category對(duì)象都會(huì)發(fā)生變化,也就是說(shuō)this所指的實(shí)體對(duì)象就會(huì)發(fā)生變化,由于采用的是動(dòng)態(tài)表達(dá)式,這樣綁定產(chǎn)品列表的Grid在每一次Category發(fā)生變化的時(shí)候都會(huì)嘗試著訪問(wèn)其中的products屬性,由于products是Reference類型,它就會(huì)激活其中的dataProvider機(jī)制獲取數(shù)據(jù),并返回自身的parameter屬性,獲取parameter屬性的時(shí)候就會(huì)激活動(dòng)態(tài)EL表達(dá)式的計(jì)算規(guī)則,得到當(dāng)前Category的id,并作為DataProvider的參數(shù)發(fā)出獲取數(shù)據(jù)的請(qǐng)求。這樣我們之前在ProductInterceptor.java中的getProductsByCategoryId方法就能得到對(duì)應(yīng)的categoryId的值了。 Reference這個(gè)對(duì)象是懶裝載的,它只是把這個(gè)關(guān)系告訴瀏覽器,只有在瀏覽器中我們?cè)噲D訪問(wèn)Reference的時(shí)候才會(huì)觸發(fā)數(shù)據(jù)加載工作,并通過(guò)一系列的機(jī)制調(diào)用到DataProvider獲取相關(guān)的數(shù)據(jù)。通過(guò)這種處理機(jī)制,我們可以提高某些類型頁(yè)面的性能,并極大的降低網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)量。實(shí)作主從維護(hù)界面

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)