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 ); }