Models是hybris項目中的一種表現形式,每個item中的配置都有一個與之對應的models類。一個Model包含不同擴展中這個項目的所有屬性,從而能直接訪問到項目中所有的數據。
他比我們正常JAVA中的pojo要更輕量級並且不需要存儲,因此更容易模擬和測試。
基本 Model
hybris 有2個基礎的Model
1.generation Model類,hybris Commerce Suite 編譯時完成
2.Model 生命周期,在hybris Commerce Suite 運行時體現
Model Class Generation
在構建hybris系統時,構建框架為每個項目生成模型類和配置文件類型。Models的構建體現了所有的擴展,不論這個擴展是否擴能是否可用的在源碼包或者二進制包中。Model構建過程中忽略了擴展
里extensioninfo.xml配置文件中generated屬性 <coremodule>的值。
Model自動構建到bootstrap/gensrc位置
生成任何一種類型的Model,都遵循下面的規則:
1.model的名稱為生成的類型加上Model后綴。比如,生成Product 的Model名稱為ProductModel
2.還將為Model生成一個類似的包名:
字符串model 將最為包名的一部分被加到擴展的跟節點后面,而jalo 將被從包名中去除
例如:de.hybris.platform.europe1.jalo.TaxRow 對應的Model為 de.hybris.platform.europe1.model.TaxRowModel.
3.所有屬性類型都是private的
4.會自動為所有的屬性創建getter,setter方法
在hybris系統構建的過程中非常早期的時候Models就已經生成了。
Tip
在platform 的modelclasses
文件夾生成的Model 類如下:
package de.hybris.springmvcdemo.model; import de.hybris.platform.core.model.ItemModel; /** * Generated Model class for type ContactRequest first defined at extension *springmvcdemo* */ @SuppressWarnings("all") public class ContactRequestModel extends ItemModel { /** <i>Generated type code constant.</i>*/ public final static String _TYPECODE = "ContactRequest"; /** * <i>Generated constant</i> - Attribute key of <code>ContactRequest.message</code> attribute defined * at extension <code>springmvcdemo</code>. */ public static final String MESSAGE = "message"; /** <i>Generated variable</i> - Variable of <code>ContactRequest.message</code> attribute defined * at extension <code>springmvcdemo</code>. */ private String _message; /** * <i>Generated constructor</i> - for all mandatory attributes. * @deprecated Since 4.1.1 Please use the default constructor without parameters */ @Deprecated public ContactRequestModel() { super(); } /** * <i>Generated constructor</i> - for all mandatory and initial attributes. * @deprecated Since 4.1.1 Please use the default constructor without parameters * @param _owner initial attribute declared by type <code>Item</code> at extension <code>core</code> */ @Deprecated public ContactRequestModel(final ItemModel _owner) { super( _owner ); } /** * <i>Generated method</i> - Getter of the <code>ContactRequest.message</code> attribute defined * at extension <code>springmvcdemo</core>. * @return the message */ public String getMessage() { if( !isAttributeLoaded(MESSAGE)) { this._message = getAttributeProvider() == null ? null : (String) getAttributeProvider().getAttribute(MESSAGE); getValueHistory().loadOriginalValue(MESSAGE, this._message); } throwLoadingError(MESSAGE); return this._message; } /** * <i>Generated method</i> - Setter of <code>ContactRequest.message</code> attribute defined * at extension <code>springmvcdemo</code>. * * @param value the message */ public void setMessage(final String value) { this._message = value; markDirty(MESSAGE); } }
觀察一下構造方法和Message屬性的getter,setter方法
Modifying the Model Generation
Models中的屬性都是默認基於配置中的屬性自動生成getter,setter方法。你可以在生成的過程中指定不需要哪個屬性,或者執行所有的屬性都不要那么就不會生成這個type對應的Model
- 排除所有的屬性
<itemtype generate="true" code="ContactRequest" ... > <model generate="false"/> <attributes>...</attributes> </itemtype>
2.如果要排除某一個屬性,你必須明確定義它。這樣無論的數private 屬性還是getter,setter方法都不會有這個屬性。配置方法是在 items.xml這個屬性里添加 <model generate="false" />。如下:
<attribute qualifier="message" type="java.lang.String"> <persistence type="property"/> <model generate="false"/> </attribute>
從4.1之后你可以給構造函數指定參數類型,如下
<itemtype generate="true" code="ContactRequest" ... > <model> <constructor signature="message"/> </model> <attributes>...</attributes> </itemtype>
生成的model構造函數如下:
/** * <i>Defined constructor from items.xml</i> * @param _message mandatory attribute declared by type <code>ContactRequest</code> at * extension <code>impex</code> */ public ContactRequestModel(final String _message) { super(); setMessage(_message); }
從4.2.2之后可以指定為屬性生成的getter,setter方法
<attribute qualifier="message" type="java.lang.String"> <persistence type="property"/> <model> <getter name="myMessage"/> </model> </attribute>
在Model中對應的生成的getter方法getMyMessage() 如下
public String getMessage() { ... } public String getMyMessage() { return this.getMessage(); }
此外,你還可以通過指定的getter,setter方法作為默認的getter,setter方法
<attribute qualifier="message" type="java.lang.String"> <persistence type="property"/> <model/> <getter name="myMessage" default="true"/> </model> </attribute>
生成了getMyMessage 方法,而原來的getMessage 方法沒有了
public String getMyMessage() { // now executes logic of former getMessage() method ... }
還有標記生成的getter,setter方法為過期的
<attribute qualifier="message" type="java.lang.String"> <persistence type="property"/> <model/> <getter name="myMessage" default="deprecated"/> </model> </attribute>
生成的getMyMessage 方法被添加上了@deprecated 注解
public String getMessage() { ... } /** * @deprecated use {@link #getMessage()} instead */ @Deprecated public String getMyMessage() { return this.getMessage(); }
Model 生命周期
Model反應數據庫中一個對應表的狀態,這種反應不是同步的,這意味着更改Model的值的時候不會自動更新到數據庫中。所以,在更新Model時,您必須顯式地將其保存到數據庫狀態反映出來。
上圖為Model 的生命周期示意圖,藍色的部分表示那些攔截器在過程中影響Model
模型中的相應階段的生命周期包括:
- Instantiating the Model
這可以通過創建一個新的模型實例或從數據庫中加載模型。
-
-
- 新建一個實例
-
這可以通過兩種方式
-
-
-
-
- 通過它的構造函數。
- ModelService通過工廠方法。
-
- 從數據庫中加載一個現有的模型可以通過使用pk或使用一個查詢表達式。See section Loading a Model below.
-
-
- Modifying Model Values 若需要:Set the properties of a Model
- Saving Model Values 如果新建或者修改
- Removing the model 如果不再需要模型,數據庫記錄被刪除。
可以在Model的生命周期內加入攔截器
Lazy Loading
惰性加載是在實例化對象的時候不是立刻就設置對象的全部屬性值。Model的加載原理就是惰性加載
一旦你加載一個Model,他包含所有的原始值。然而,在這種狀態下,關系還未填寫。一旦你通過調用相應的getter訪問這樣一種關系,這種關系是按需加載。這意味着一旦你加載一個模型,你需要擔心加載任何依賴模型。
在hybris 5.0。當加載模型,Model value是不加載的。Model只是一個空的java實例,沒有設置任何值。 All Model values are only loaded when any value of the Model is retrieved。這種機制在Model初始化時可以提高性能
如果要改變這種機制,在你的local.properties配置文件中設置 servicelayer.prefetch為 all or literal。literal 只會預加載原子屬性的值,而不會加載引用的其他對象屬性。
不建議設置all的加載機制,可能會出現循環依賴導致堆棧溢出錯誤。
Lazy Loading Model Relations
因為按需加載模型的關系,在某些情況下避免調用getter和setter方法。觀察下面這個簡單的例子和2中方式。統計一個用戶的總金額
在服務層根據用戶的訂單來累計總金額。使用User Model的getOrders() 方法可以迭代所有的訂單,再來統計總金額。我們知道現在場景下數據量是很小的。
... public Double getTotal(UserModel user ) { double cumulativeTotalPrice = 0.0d; for (final OrderModel order : user.getOrders()) { cumulativeTotalPrice += order.getTotalPrice().doubleValue(); } } ...
但是,想想如果是訂單數據量很大的場景呢。比如,B2B商城,可能一個廠商只用了一個用戶。第一次調用getOrders()返回這個用戶的所有的相關訂單,理想情況下,結果都會被緩存起來。但是糟糕的情況是,執行查詢並返回成千上萬的訂單行。查詢結果中的每一行,訂單模型實例化,然后緩存。
另一種方式是在DAO層使用FlexibleSearch 查詢。查詢匯總每個訂單的總價格為單個結果只有一行被從數據庫獲取:
public Double getTotal() { final FlexibleSearchQuery fq = new FlexibleSearchQuery("SELECT SUM(totalPrice) AS CUMULATIVE_PRICE FROM {Order} WHERE {user} = ?session.user"); fq.setResultClassList(Lists.newArrayList(Double.class)); final SearchResult<Double> search = fs.search(fq); final List<Double> result = search.getResult(); if (!result.isEmpty()) { return result.iterator().next(); } return Double.valueOf(0); }
Model Context
一旦你加載一個模型或通過modelService創建它,就會放入Model 的上下文。Model context會跟蹤Model 的所有變化,尤其是引用的新的還沒有保存的Model
如果選擇單獨的保存Model,注意只有沒有保存的Model會自動保存。已經創建的Model是不會保存的。
比如,你現在有一個CategoryModel對象,這個對象持有ProductModel對象,你修改了CategoryModel:
- 如果ProductModel是新建的還沒有保存的,那么保存CategoryModel對象的時候同樣會保存ProductModel。
這是因為ProductModel是一個新的對象,並且新的引用會保存
- 如果ProductModel已經保存過,再次保存CategoryModel的時候ProductModel不會再保存了
這是因為引用的對象ProductModel是已經存在的Model,存在的Model不會再次保存
因為Model context 會跟蹤你的所有操作,可以保存所有的更改。你不需要單獨保存每個模型。你可以一次保存所有的Model.關於怎么保存Model,請參考 Saving a Model 小節
如果你是用構造方法新建了一個Model,那么Model不會放入Model Context中。參見下面 Using a Constructor小節
你可以手工的為Model修改Model context
- 把一個Model加入context:
modelService.attach(model)
- 從context里刪除一個Model:
modelService.detach(model)
Model context 綁定在HybrisRequestScope上,類似於標准的request作用域但是只作用於一個線程
當作用的session或者request唄關閉或者超時Model context會自動清除相關Model.所以可以判斷,一個沒有保存的Model被刪除並不會影響線程的內存泄露
Model的創建或者修改儲存在context中,如果你從數據庫加載這樣的模型,例如通過使用 flexible search,你得到同樣的模型的實例存儲在上下文。你必須意識到這種作坊是不能保證加載未修改的模型。如果你嘗試展示2次在沒有修改的前提下,你會得到相同的對象,但是你不能依賴這個。最好的做法是在你的代碼里持續保持對這個對象的引用。
記住Model context是線程本地的並且Model不是線程安全的。如果Model是多線程使用的情況需要對Model做同步處理。可以使用SessionService
最好不要使用事物回滾后的Model context。如果你要在事物回滾期間保存Models,可能會出錯。這取決於你的使用模式。這是因為Model可能與他們的數據庫表示處於不一致的狀態。比如要保存一個Model,他的主鍵已經生成但是因為回滾操作這個Model並沒有被保存。因此不要使用事物回滾后的Model做檢索或者創建
ModelService
ModelService 是處理Model生命周期內的所有操作的service。可以通過modelService 在spring中的ID或者繼承 de.hybris.platform.servicelayer.model.ModelService接口得到ModelService。主要任務包含如下幾點:
- 通過PK加載Models
- 通過item加載Models
- 新建Models
- 更新Models
- 刪除Models
Creating a Model Instance
2中方式
- 使用構造函數新建
- 使用工廠方法
Using a Constructor
你不需要一個特殊的創建方法或其他類型的工廠,就可以通過NEW方法創建一個簡單的對象
ProductModel product = new ProductModel();
Model 值不會立刻寫入數據庫,只有在你明確指定保存他的時候才會寫入數據庫。因此,你在剛實例化一個Model的時候不用為屬性都設置值。但是,當你要保存Model的時候,必須為屬性賦值,除了有默認值的屬性。
Model Constructor Methods From 4.1.1
從Hybris 4.1.1版本開始,構造方法強制傳入參數被取消。可以先只用無參的構造方法實例化對象,再通過setter方法設置屬性。
ProductModel product = new ProductModel(); product.setCatalogVersion(catalogVersion); product.setCode(code);
Furthermore you can use constructor defined at items.xml
as explained in Modifying the Model Generation
但是用這種方式實例化Model對象,不會加載入Model context.有2個方法加入
- 使用ModelService 's save(Object)方法。Model會存入數據庫並且自動放入Model context中
modelService.save(Object)
- 使用ModelService 's attach(Object)方法。Model不會保存到數據庫,但是會放入Model context中
modelService.attach(Object)
可以按照如下的方法為Model設置item.xml中設置的默認值
modelService.initDefaults(model);
如果沒有明確的調用這個方法,會在Model保存的時候自動設置
Using a Factory Method
使用ModelService新建Model 對象
ProductModel product = modelService.create(ProductModel.class)
或者,指定Model的名稱(在item.xml中配置的名稱)
ProductModel product = modelService.create("Product")
這可以幫你在程序運行時創建一個Model實例,並且會直接放到Model context中。默認值也是會自動分配的。
Loading an Existing Model
從數據庫中加載一個Model,有下面幾種方法:
- 使用的這個Model 的PK
- 使用FlexibleSearch query
- 4.1.0版本之后:Using an example Model as search parameter
Loading by Primary Key
最簡單的情況下加載Model是用其主鍵。可以使用ModelService的get的方法
ProductModel product = modelService.get(pk)
Loading by Query Expression
一般情況下,我們習慣使用FlexibleSearch查詢語句來查找Model。需要使用到flexibleSearchService.繼承自de.hybris.platform.servicelayer.search.FlexibleSearchService接口。可以通過flexibleSearchService在spring bean的ID來獲取到他
FlexibleSearchQuery query = new FlexibleSearchQuery("SELECT {pk} FROM {Product} WHERE {code} =?" + Product.CODE); query.addQueryParameter(Product.CODE, code); SearchResult<ProductModel> result = flexibleSearchService.search(query); List<ProductModel> = result.getResult();
如果search()方法沒有查到對應的Model,那么會拋出ModelNotFoundException異常
4.2.2版本后,咱們可以FlexibleSearchService 新的方法searchUnique() ,這個方法類似search() 。但是略有不同,searchUnique()方法或者返回我們需要的結果,要么拋出下面2個錯誤中的一種:
- ModelNotFoundException: 沒有找到對應的Model
- AmbiguousIdentifierException:滿足查詢條件的結果不止一條。如下一個查詢
FlexibleSearchQuery query = new FlexibleSearchQuery("SELECT {pk} FROM {Product} WHERE {code} =?code"); query.addQueryParameter("code", code); ProductModel result = flexibleSearchService.searchUnique(query);
Loading by Example Model
4.1.0以后的版本,可以使用一個例子Model作為一個搜索參數模型加載已有的Model
4.2.2以后版本,這個方法從ModelService 移動到了FlexibleSearchService 中。並且分為了2個方法:getModelByExample() 和getModelsByExample().
替代 FlexibleSearch查詢方法來查詢Model。我們新建一個Model,改變其屬性的值並且搜索系統中已經存在的這個Model。本質上來說,匹配相同類型的Model就會被查詢到並且返回了。 getModelByExample() 方法不能查詢多個Models。如果要查詢多個Models 使用getModelsByExample()方法。
使用一個實例Model搜索已經存在的Model的步奏:
- 新建一個Model
- 把屬性的值改成我們想要搜索的Model的屬性值(可以理解為where條件)
- 當我們的預期結果只有一個的時候,使用flexibleSearchService.getModelByExample(...) 方法
- 當我們預期的結果是有多個的時候,使用flexibleSearchService.getModelsByExample(...) 方法
例如:
//search for a product with the unique code "test" ProductModel exampleProduct = new ProductModel(); exampleProduct.setCode("test"); ProductModel foundProduct = flexibleSearchService.getModelByExample(exampleProduct);
getModelByExample() 方法拋出2種類型的錯誤
- ModelNotFoundException:沒有找到對應的Model
- AmbiguousIdentifierException:找到多個結果。
如何避免AmbiguousIdentifierException錯誤,使用getModelsByExample() 方法
//search for a products with the nonunique ArticleApprovalStatus ProductModel exampleProduct = new ProductModel(); exampleProduct.setApprovalStatus(ArticleApprovalStatus.APPROVED); List<ProductModel> foundProducts = flexibleSearchService.getModelsByExample(exampleProduct);
create() 方法 vs new Operator 如果使用modelService.create()方法來創建實例Model .會觸發默認的攔截器。因此,Model屬性會被設置默認值並且這些默認值會被作為搜索的條件。例如: //search for a product with the unique code "test", ApprovalStatus="check", PriceQuantity=1.0 ProductModel exampleProduct = modelService.create(ProductModel.class); exampleProduct.setCode("test"); ProductModel foundProduct = flexibleSearchService.getModelByExample(exampleProduct); 這個代碼示例的有效搜索參數有:
所以如果要創建實例Model請使用new ProductModel()方法來創建,這樣不會觸發攔截器給實例Model設置默認值來對查詢造成不必要的干擾 |
這也可以搜索本地化屬性。但是如果我們這么做了,必須要手工把Model加入到Model context中。如果沒有做這不,那么LocaleProvider 這個值就不會被設置,查詢結果會拋出如下錯誤
java.lang.IllegalStateException: got no locale provider - cannot access default localized getters and setters.
//search for a product where the english name is "uniqueName_english" and the german name "uniqueName_deutsch". ProductModel exampleProduct = new ProductModel(); exampleProduct.setName("uniqueName_deutsch", Locale.GERMAN); exampleProduct.setName("uniqueName_english", Locale.ENGLISH); modelService.attach(exampleProduct); // <- important
額外的代碼實例,請參考FlexibleSearchServiceGetModelByExampleTest.java
Saving a Model
有兩種基本的方式保存模型
- 某些情況,保存一個Model和他引用的其他Model。使用modelService 's save(...) 方法
modelService.save(model);
如果一個Model還引用了其他的Model,如果這些被引用的Model之前沒有被保存過那么都會被保存。如果之前已經被保存過的話那么就不會再保存
- 保存所有的模型
modelService.saveAll();
將保存Model context上記錄的所有改動。參見Model Context
-
Collections
There is a special behavior when using collections. You cannot simply get a collection-based attribute of a Model, modify the collection's contents and call the ModelService 's save(...) method. The getter methods of Models return unmodifiable lists, so you cannot modify the collection. Instead, you have to:
- Create a new collection object.
- Add existing, non-modified values.
- Add new or modified values.
- Set the collection to the attribute.
-
Store the attribute by calling the save(...) for the Model.
This is the intended behavior to ensure data consistency: You explicitly have to create a new Collection, set values to it, and save the attribute for the collection. Thus, you know which values have been added and stored, and which values have not.
Removing a Model
刪除一個Model就調用modelService刪除方法:
modelService.remove(product)
Refresh a Model
modelService.refresh(product)
注意:從數據庫刷新檢索Model的值,會覆蓋現有的值,如果沒有保存的話,數據會丟失
Model 與 hybris Items 之間的轉換
我們可能經常會遇到Model 與hybris Items之間的轉換
你應該總是避免直接訪問Jalo Items,除非在ServiceLayer沒有替代者。
原文:
You should always avoid accessing Jalo items directly, unless there is no replacement in the ServiceLayer.
Converting a Model to an Item(把一個Model 轉換為Item)
Sometimes you need to get access to the underlying item of a Model, for example to perform some logic only available in the item class and not yet ported to a service.
有時我們需要得到一個Model的底層Item。比如要在一些項目類執行一些邏輯但是他沒有一個服務類。modelService的getSource(...) 方法把一個Model轉換為Item
Product productItem = modelService.getSource(productModel)
Converting an Item to a Model(把Item轉換為Model)
有時,我們需要使用Model,但是這是只有Item.This typically occurs if legacy, Jalo Layer-based code is involved.To make use of this item in your service layer related code, you have to convert the item to a Model. Use the special get method in the modelService that takes an item as parameter and returns a Model, such as:
final Cart cart = JaloSession.getCurrentSession().getCart(); final CartModel result = modelService().get(cart); return result;
Defining Enums for Models(為Models 定義枚舉)
Models 可以使用JAVA的枚舉:你可以為Models的屬性預定義可選擇的值。枚舉的典型用例:
- 星期
- 月
- T恤的顏色
定義一個枚舉值模型:
- 以下例子為在Items.xml 中定義enum:
<enumtypes> <enumtype code="ArticleApprovalStatus" autocreate="true" generate="true"> <value code="check"/> <value code="approved"/> <value code="unapproved"/> </enumtype> <enumtypes>
2. 在attribute 標簽中定義enum:
<attribute qualifier="approvalStatus" type="ArticleApprovalStatus"> <modifiers read="true" write="true" search="true" optional="false"/> <persistence type="property"/> </attribute>
3.出發Model生成。該Model生成枚舉的getter和setter方法
/** *Generated method- Getter of theProduct.approvalStatus * attribute defined at extensioncatalog. * @return the approvalStatus */ public ArticleApprovalStatus getApprovalStatus() { if( !isAttributeLoaded(APPROVALSTATUS)) { this._approvalStatus = (ArticleApprovalStatus) loadAttribute(APPROVALSTATUS); } throwLoadingError(APPROVALSTATUS); return this._approvalStatus; } /** *Generated method- Getter of theProduct.articleStatus * attribute defined at extensioncatalog. * @return the articleStatus */ public MapgetArticleStatus() { return getLocalizedValue(this._articleStatus, ARTICLESTATUS, getCurrentLocale()); } /** *Generated method- Getter of theProduct.articleStatus * attribute defined at extensioncatalog. * @param loc the value localization key * @return the articleStatus * @throws IllegalArgumentException if localization key cannot be mapped to data language */ public MapgetArticleStatus(final Locale loc) { return getLocalizedValue(this._articleStatus, ARTICLESTATUS, loc); } /** *Generated method- Setter ofArticleApprovalStatus.approvalStatus * attribute defined at extensioncatalog. * * @param value the approvalStatus */ public void setApprovalStatus(final ArticleApprovalStatus value) { this._approvalStatus = value; markDirty(APPROVALSTATUS); } /** *Generated method- Setter oflocalized:ArticleStatusMapType.articleStatus * attribute defined at extensioncatalog. * * @param value the articleStatus */ public void setArticleStatus(final Mapvalue) { setLocalizedValue( this._articleStatus,ARTICLESTATUS, getCurrentLocale(), value ); } /** *Generated method- Setter oflocalized:ArticleStatusMapType.articleStatus * attribute defined at extensioncatalog. * * @param value the articleStatus * @param loc the value localization key * @throws IllegalArgumentException if localization key cannot be mapped to data language */ public void setArticleStatus(final Mapvalue, final Locale loc) { setLocalizedValue( this._articleStatus,ARTICLESTATUS, loc, value ); }