hybris Models

  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

更早的检查Data Model

  Models 的创建都是依赖items.xml文件的,是匹配的。所以我们可以依赖 items.xml来检查创建的Model,这可以比在hmc中updating or initializing更早的发现问题。下面是个例子

items.xml
<itemtype
  generate="true"
  code="ContactRequest"
  jaloclass="de.hybris.springmvcdemo.jalo.ContactRequest"
  extends="GenericItem"
  autocreate="true"
  >
    <attributes>
      <attribute qualifier="message" type="java.lang.String">
        <persistence type="property"/>
      </attribute>
    </attributes>
</itemtype>


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

  1. 排除所有的属性
<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是用其主键。可以使用ModelServiceget的方法

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的步奏:

  1. 新建一个Model
  2. 把属性的值改成我们想要搜索的Model的属性值(可以理解为where条件)
  3. 当我们的预期结果只有一个的时候,使用flexibleSearchService.getModelByExample(...) 方法
  4. 当我们预期的结果是有多个的时候,使用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);

这个代码示例的有效搜索参数有:

参数

设置方式

ProductModel.priceQuantity

1.0

LoadInterceptor 拦截器为ProductModel默认设置

ProductModel.code

test

代码中明确指定

ProductModel.approvalStatus

CHECK

LoadInterceptor 拦截器为ProductModel默认设置

所以如果要创建实例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:

  1. Create a new collection object.
  2. Add existing, non-modified values.
  3. Add new or modified values.
  4. Set the collection to the attribute.
  5. 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。比如要在一些项目类执行一些逻辑但是他没有一个服务类。modelServicegetSource(...) 方法把一个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恤的颜色

定义一个枚举值模型:

  1. 以下例子为在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 );
}
原文地址:https://www.cnblogs.com/pingjie/p/4319150.html