Table of Contents

Hybris' persistence mechanism

When Hybris was first released in 1997, the first version Enterprise JavaBeans (EJB) was still immature and Hibernate ORM didn't exist. So Hybris developed its own proprietary persistence mechanism which generates Java classes from an xml file. These classes and their relations can easily stored into a relational database. As this is the only persistence mechanism supported by Hybris, every backend developer working with the e-commerce suite must understand how it works and its limitations.

Old Jalo classes

There was an evolution in the Hybris ORM. Until version Hybris 4, you could only generate Jalo classes which are Java persistent classes containing business logic. This led to spaghetti code on those classes. Because of this, Models which are Java persistent POJOs were introduced. Because it is a bad practice to use Jalo classes, I won't explain them here.

Defining persistent classes in items.xml

Note: I will use the source code of the Hybris extension Areco Deployment Scripts Manager to explain how the persistence mechanism works.

Every Hybris extension contains the file resources/<extensionName>-items.xml which is used to define the persistent classes which are used by the extension. Let take a closer look to the file arecoDeploymentScriptsManager-items.xml

<itemtype generate="true"
   code="ScriptExecution"
   jaloclass="org.areco.ecommerce.deploymentscripts.jalo.ScriptExecution"
   autocreate="true">
    <deployment table="arscriptexecution" typecode="32101"/>
    <attributes>
        <attribute qualifier="extensionName" type="java.lang.String">
            <description>Location of the deployment script</description>
            <modifiers optional="false" initial="true" />
            <persistence type="property"/>
        </attribute>
        <attribute qualifier="scriptName" type="java.lang.String">
            <description>Name of the deployment script. It is usually the directory name.</description>
            <modifiers optional="false" initial="true"/>
            <persistence type="property"/>
        </attribute>
        <attribute qualifier="result" type="ScriptExecutionResult">
            <description>Final state of the execution</description>
            <modifiers optional="false" initial="true"/>
            <persistence type="property"/>
        </attribute>
        <attribute qualifier="phase" type="SystemPhase">
            <description>When was the deployment script run.</description>
            <modifiers optional="false" initial="true"/>
            <persistence type="property"/>
        </attribute>
        <attribute qualifier="stacktrace" type="java.lang.String">
            <description>Stacktrace if the script was unsuccessful. It is 4KB long.</description>
            <persistence type="property">
                <columntype database="oracle">
                    <value>text</value>
                </columntype>
                <columntype>
                    <value>varchar(4096)</value>
                </columntype>
            </persistence>
        </attribute>
    </attributes>
</itemtype>

Recommendations

Attributes

Hybris don't trigger the remove interceptors when updating a partof relations. If a base product is updated with a subset of the variants which are on the database, the missing variants will be removed and you can't use a remove interceptor to prevent the deletion. This happens on SAP commerce 1905.
To really prevent the removal of products from the database you need a database trigger like the following one for Microsoft SQL Server:

return jaloSqlScriptService.runDeleteOrUpdateStatement("CREATE OR ALTER TRIGGER PRODUCTS_DEL ON products INSTEAD OF DELETE AS THROW 51000, 'Products must not be deleted', 1")

This code create database trigger preventing any removal of products. It depends on Areco Deployment Script Manager

<itemtype code="Product" jaloclass="de.hybris.platform.jalo.product.Product" autocreate="false" generate="true">
    <attributes>
    (...)
    <attribute qualifier="dangerousGood" type="java.lang.Boolean" >
            <persistence type="dynamic" attributeHandler="isDangerousGoodAttributeHandler"/>
            <modifiers write="false" optional="false"/>
        </attribute>
    </attributes>
</itemtype>
@Component("isDangerousGoodAttributeHandler")
public class IsDangerousGoodAttributeHandler implements DynamicAttributeHandler<Boolean, ProductModel> {
  @Override
  public Boolean get(final ProductModel pProduct) {
    // If all the fields are empty, the product is not a dangerous good.
    return !(pProduct.getHazardStatementCodes().isEmpty() //
        && pProduct.getHazardStatementTexts().isEmpty() //
        && pProduct.getEuHazardStatementCodes().isEmpty() //
        && pProduct.getEuHazardStatementTexts().isEmpty() //
        && pProduct.getPrecautionaryStatementCodes().isEmpty() //
        && pProduct.getPrecautionaryStatementTexts().isEmpty() //
        && pProduct.getHazardPictographCodes().isEmpty() //
        && pProduct.getHazardPictographDescriptions().isEmpty() //
        && pProduct.getHazardSignalWords().isEmpty() //
        && pProduct.getBiocideNotes().isEmpty());
  }
 
  @Override
  public void set(final ProductModel pProductModel, final Boolean pBoolean) {
    throw new UnsupportedOperationException("This attribute can't be set.");
  }
}    

This handler makes a calculation based on other fields. Every handler must implement the interface DynamicAttributeHandler<AttributeType, TypeModel>

<attribute qualifier="description" type="localized:java.lang.String">
    <description>Description of the state.</description>
    <persistence type="property"/>
</attribute>

The values of these attributes are saved in a separated table with the sufix lp like arscriptresultlp for the type ScriptExecutionResult.

Recommendations

<attribute qualifier="id" type="java.lang.String">
  <description>DEPRECATED: hybris doesn't remove old mandatory attributes, so we keep it.</description>
  <modifiers optional="true" read="false" write="false" />
  <persistence type="property"></persistence>
</attribute>

After an update running system in production, you could remove it. This means that it takes two releases to remove it.
The best alternative is to use the Areco Deployment Script Manager and remove the old attribute using a deployment script.

Relations between Hybris Types

One to many relations

<relation code="Customer2EmailAddress" generate="true" localized="false" autocreate="true">
    <sourceElement qualifier="customer" type="Customer" cardinality="one"/>
    <targetElement qualifier="emails" type="EmailAddress" cardinality="many" collectiontype="list" ordered="true"/>
</relation>

This example creates the column customer in the table where the items EmailAddress are stored and getters and setters on the customers to set and get the list of emails. The sourceElement and targetElement may have modifiers like the attributes. You have to read the source and target elements XML tags crossed to understand where the setters and getters are generated.

One to many relations cannot be localized. You have to use a many to many relationship and manage the removal of associations.

Many to many relations

<relation code="InterestGroupVoucherRestrictionToInterestGroupRelation" localized="false" generate="true" autocreate="true">
    <deployment table="voucherToInterestGroup" typecode="15189"/>
    <sourceElement qualifier="voucherRestrictions" type="InterestGroupRestriction" cardinality="many" />
    <targetElement qualifier="interestGroups" type="InterestGroup" cardinality="many" />
</relation>

This examples creates the table voucherToInterestGroup with the links between voucher restrictions and the interest groups. And generates the fields InterestGroupRestriction.interestGroups and InterestGroup.voucherRestrictions. A deployment table is required.

Hybris Enumerations

<enumtypes>
    <enumtype code="SystemPhase" dynamic="false" >
        <value code="INITIALIZATION" />
        <value code="UPDATE" />
    </enumtype>
</enumtypes>

This static enumeration is generated as the Java enum org.areco.ecommerce.deploymentscripts.enums.SystemPhase and can be referenced in any attribute of Hybris types. The list of values can only changed during compilation time by changing the items.xml file.
If you want to change the values of an enumeration during runtime, creating values with hmc or importing them with Impex you have to declare an enumeration as dynamic:

<enumtype code="StockLevelStatus" generate="true" autocreate="true" dynamic="true">
    <description>Flag for real stock level status</description>
    <value code="inStock" />
    <value code="outOfStock" />
</enumtype>

This creates a Java class which has an static field for each code. This class has the field code and from the point of view of a developer, it is used like a Hybris type and you can import or export the items using Impex. If you need more fields in your custom dynamic enumeration, you must declare a Hybris type instead.

Recommendations

How to persist and load items

In every case you must use the modelService:

@Component("deploymentScript2ExecutionConverter")
public class DeploymentScript2ExecutionConverter implements Converter<DeploymentScript, ScriptExecutionModel> {
  private static final Logger LOG = Logger.getLogger(DeploymentScript2ExecutionConverter.class);
 
  @Autowired
  private ModelService modelService;
 
  /*
   * (non-Javadoc)
   * 
   * @see de.hybris.platform.servicelayer.dto.converter.Converter#convert(java.lang.Object)
   */
  @Override
  public ScriptExecutionModel convert(final DeploymentScript source) throws ConversionException {
    return this.convert(source, (ScriptExecutionModel) this.modelService.create(ScriptExecutionModel.class)); // Creation of a new item
  }
 
  /*
   * (non-Javadoc)
   * 
   * @see de.hybris.platform.servicelayer.dto.converter.Converter#convert(java.lang.Object, java.lang.Object)
   */
  @Override
  public ScriptExecutionModel convert(final DeploymentScript source, final ScriptExecutionModel execution)
      throws ConversionException {
    ServicesUtil.validateParameterNotNullStandardMessage("source", source);
    ServicesUtil.validateParameterNotNullStandardMessage("execution", execution);
    if (DeploymentScript2ExecutionConverter.LOG.isDebugEnabled()) {
      DeploymentScript2ExecutionConverter.LOG.debug("Creating an script execution model from the deployment script " + source);
    }
    execution.setExtensionName(source.getExtensionName());                                                         // The model is filled
    execution.setScriptName(source.getName());
    execution.setResult(null); // The caller must set the result before saving the execution.
    execution.setPhase(source.getPhase());
 
    return execution;
  }
 
}
 
@Service
public class ArecoDeploymentScriptsRunner implements DeploymentScriptRunner {
 
  private static final Logger LOG = Logger.getLogger(ArecoDeploymentScriptsRunner.class);
 
  @Autowired
  private ModelService modelService;
 
(...)
 
  private void saveAndLogScriptExecution(final UpdatingSystemExtensionContext context,
      final ScriptExecutionModel scriptExecution) {
    this.modelService.save(scriptExecution);                                                            // The model is stored into the database
    context.logScriptExecutionResult(scriptExecution);
  }
}

To retrieve the model you may use ModelService#get(de.hybris.platform.core.PK) but you usually use Hybris Query Language FlexibleSearch to get items from the database.
You can remove an item using ModelService#remove(java.lang.Object).
You can update the item with the contents of the database using ModelService#refresh. If two thread modify simultaneously two instances of the same model containing different values, the last thread is going to override the changes from the first one. If you are updating critical models like stock levels you must do it inside a database transaction. Hybris don't have any optimistic locking mechanism like Hibernate ORM.

Localization of Hybris types and attributes

The names and descriptions of the Hybris types, enumerations, relations and attributes can be localized using a property file inside resources/localization. For example,

File arecoDeploymentScriptsManager/resources/localization/arecoDeploymentScriptsManager-locales_de.properties
(...)
type.DeploymentEnvironment.name=Deployment Umgebungen
type.DeploymentEnvironment.name.name=Name
type.DeploymentEnvironment.description.name=Beschreibung

type.SystemPhase.name=Phase des Systems
type.SystemPhase.INITIALIZATION.name=Initialization
type.SystemPhase.UPDATE.name=Aktualisierung
(...)

This sets the name of a Hybris type, an enumeration and two attributes in German.

General Recommendations

–Based on Hybris 6.5