SAP Hybris Tutorial – Data Modeling | items.xml File | Unit 9

Pinestran  > SCC Tutorial >  SAP Hybris Tutorial – Data Modeling | items.xml File | Unit 9

SAP Hybris Tutorial – Data Modeling | items.xml File | Unit 9

0 Comments
hybris data modeling, items.xml file

This is the very important part we need to know about the data modeling in Hybris.

SAP Commerce allows you to match your specific business needs through product and data modeling. Good understanding of the type system allows you to reflect in your application your business model. The bean and enum generation feature also serves that purpose.

The items.xml file specifies the types (classes) of items (objects). By editing the items.xml file, you can define new types or extend existing types. In addition, you can define, override, and extend attributes in the same way.

In this post, you will know how important the items.xml file is.

Let’s go deep dive into each part.

1. Sections of the file, in order

1.1 The location of the items.xml file

The items.xml is located in the resources directory of an extension. The items.xml files are prefixed with the name of their respective extension in the form of <extension name>-items.xml. For example:

  • For the core extension, the file is called core-items.xml.
  • For the catalog extension, the file is called catalog-items.xml.

1.2 The structure of the items.xml file

<items>
  <atomictypes/>      <!-- rarely used: basic Java types -->
  <collectiontypes/>  <!-- List/Set of something -->
  <enumtypes/>
  <maptypes/>
  <relations/>
  <itemtypes/>        <!-- the actual tables -->
</items>

The order is enforced by the XSD, and so is the order inside <itemtype>: <description> must come before <deployment>.

Every type is identified by its code, for example SearchItem. At build time Hybris generates a Model class for it (SearchItemModel), and update system creates the database columns.


2. Item types (≈ tables)

<itemtype code="SearchItem" extends="GenericItem" autocreate="true" generate="true">
    <description>...</description>
    <deployment table="SearchItemTbl" typecode="12106"/>
    <attributes>...</attributes>
    <indexes>...</indexes>
</itemtype>

AttributeMeaning
extendsThe parent type. GenericItem is the root for new types. You can also extend Product, B2BCustomer, and so on. A subtype inherits all parent attributes and is returned by FlexibleSearch queries on the parent.
autocreate="true"This file creates the compose type. Use false to add attributes to an existing type from another extension.
generate="true"Generate the Jalo source for the type (the Generated<Type> class, plus the jaloclass stub if it doesn’t exist yet). It does not control the Model class: <Type>Model is still generated with generate="false". To skip the Model, add <model generate="false"/> inside the <itemtype> (items.xsd: “Whether a model for the type and models for subtypes will be generated”).
abstract="true"The type can’t be instantiated; only its subtypes can.
jaloclassLegacy Jalo layer class. (identify the custom package name)

A few things to watch for:

  • Don’t specify a jaloclass attribute pointing to a class that won’t exist.
  • Avoid persistence type="jalo" attributes. Those are computed by methods in the Jalo class. Use persistence type="dynamic" with a DynamicAttributeHandler instead, which is the ServiceLayer approach.
  • Legacy code that calls the Jalo API for your type directly, such as old custom ImpEx translators or Jalo-based logic, would need the Jalo class. For a new type written only with models and services, this usually doesn’t apply.
  • Only add custom code (if any) in the SearchItem.java file but not GeneratedSearchItem.java file (cause this file is an abstract class in your extension’s gensrc folder. It is regenerated on every build, so you should never edit it.)

<deployment>: table and typecode

  • The typecode must be unique across the whole system. Custom types should use 10000 or higher; values below that are reserved for SAP.
  • A subtype without its own <deployment> shares its parent’s table. That’s why most Product subtypes end up in the products table.
  • Once a type has data, never change its typecode or table.

3. Attributes (≈ columns)

<attribute qualifier="dataType" type="SearchDataType">
    <persistence type="property"/>
    <modifiers optional="false" unique="true"/>
    <defaultvalue>...</defaultvalue>
    <description>...</description>
</attribute>

qualifier: the attribute name, which generates getDataType() and setDataType().

type: what the attribute holds

KindExamples
Atomicjava.lang.String, java.lang.Integer, java.lang.Boolean, java.util.Date, java.math.BigDecimal
Item referenceB2BCustomer, Product: stored as a PK column (foreign-key style)
EnumSearchDataType
CollectionGenderList
Localizedlocalized:java.lang.String: one value per language, stored in a separate ...lp table
<persistence type=...>
  • property: stored in a database column (the normal case).
  • dynamic: not stored. It’s computed by a Spring attributeHandler bean on every get.
  • Column size and type: add <columndefinition> to override the default per database. SearchFilter.filters uses HYBRIS.LONG_STRING, which maps to CLOB/NVARCHAR(MAX) for long JSON.
<modifiers>: constraints and access rules
ModifierEffect
optional="false"Mandatory. The service layer rejects saving the model if the value is null.
unique="true"Part of the type’s uniqueness key; checked at the service layer. All attributes marked unique together form one combined key. That’s how customer + dataType became a unique pair.
read / writeGenerate the getter / setter. write="false" makes the attribute read-only after creation.
initial="true"Can be set when the item is created, even if write="false". The usual combination is write="false" initial="true", for a value set at creation and never changed.
search="true"Can be used in FlexibleSearch queries.
partof="true"Cascade delete: removing the owner removes this child too (searchItems, searchFilters).
encrypted="true"The value is encrypted in the database.
<defaultvalue>

A Java expression, for example Boolean.FALSE. For an enum: em().getEnumerationValue("SearchDataType","DELIVERYNOTE").


4. Enums

<enumtype code="BrandType" dynamic="true">
    <value code="A"/>
    <value code="B"/>
</enumtype>

  • dynamic="false": compiled into a real Java enum. New values require a code change and a build.
  • dynamic="true": stored as rows in the database. You can add values with ImpEx or in Backoffice without a build, so the generated class isn’t a real Java enum (it’s a class with constants).
  • Extending an existing enum: use autocreate="false" generate="false" and list only the new values. That’s how to adds statuses to OOTB OrderStatus.

5. Relations

A relation links two item types.

One-to-many

There’s no extra table; the “one” side is stored as a column on the “many” side.

<relation code="B2BCustomer2SearchItem" localized="false">
    <sourceElement type="B2BCustomer" qualifier="customer" cardinality="one">
        <modifiers optional="false" unique="true"/>
    </sourceElement>
    <targetElement type="SearchItem" qualifier="searchItems" cardinality="many" collectiontype="list">
        <modifiers partof="true"/>
    </targetElement>
</relation>

The qualifier names are easy to read backwards. Each element’s qualifier is the property name on the other type:

  • customer is the column on SearchItem, so you call searchItem.getCustomer().
  • searchItems is the property on B2BCustomer, so you call customer.getSearchItems(). It’s not a column: Hybris queries it from the other side.

So the modifiers on sourceElement describe the customer attribute of SearchItem. That’s why unique="true" went there.

Many-to-many

This uses a link table, so it needs its own <deployment>:

<relation code="PromoReq2Approver" ...>
    <deployment table="PromoReq2ApproverRel" typecode="10108"/>
    <sourceElement ... cardinality="many"/>
    <targetElement ... cardinality="many"/>
</relation>

  • collectiontype="list|set|collection": the Java type of the getter’s return value.
  • ordered="true": keeps the list order with an extra position column. This is the order items were saved in, not a sort by attribute; see “Ordering the many side” below.

Relation vs. collection-type attribute

Use a relation. A collection-type attribute (for example List<Product>) is stored as a comma-separated PK string in one column. You can’t query it with FlexibleSearch joins, and it gets slow. Collection types are fine for small lists of enums, like GenderList.

Reading a relation: getter navigation vs. a DAO query

Question (2026-10-17): why not get the filters with customer.getSearchItems().stream().filter(DELIVERYNOTE).findFirst().getSearchFilters() instead of a DAO query?

// Navigating the relation
final List<SearchFilterModel> filters = customer.getSearchItems().stream()
        .filter(searchItem -> SearchDataType.DELIVERYNOTE.equals(searchItem.getDataType()))
        .findFirst()
        .map(SearchItemModel::getSearchFilters)
        .orElse(List.of());

This works. Behind the getters, Hybris runs two queries: WHERE p_customer = ? for the search items, then WHERE p_searchitem = ? for the filters. It then loads each row as a model.

Relation gettersDAO query (DefaultSearchItemDao)
Database round trips2, plus loading each row as a model1
Rows loadedAll search items, then all of their filtersOnly the rows that match
ORDER BY codeDone in Java. The relation isn’t ordered="true", so the collection order isn’t guaranteedDone by the database
Filtering by codesDone in Java, after loading everythingIN (?codes) in SQL
Remove by codeLoad all filters, then find the one to removeOne query that also checks the customer
  • Ownership is checked either way. Both versions start from the current customer.
  • Indexes still matter. getSearchFilters() runs WHERE p_searchitem = ?, so it needs the same index on the relation column as the DAO join (see section 6).
  • Fresh data. A FlexibleSearch query always reflects what’s in the database. A collection that’s already loaded on a model can, in principle, miss a row another request just added. Not checked for OCC’s current-user model.
  • When getters are fine: small collections, and code that already has the owner model. Getters go through the model cache and are easy to stub in unit tests.
  • Decision for Project: depends on situation to use the proper method.

Ordering the many side

Question (2026-10-17): how do you order the items of a relation’s “many” side by a specific criterion?

Hybris has no items.xml setting that sorts a relation by an attribute (such as code or name). The options:

1. ordered="true": keeps the order you save in, not a sort.

<targetElement type="SearchFilter" qualifier="searchFilters" cardinality="many"
               collectiontype="list" ordered="true"/>

  • items.xsd: “If ‘true’ an additional ordering attribute will be generated for maintaining ordering. Default is ‘false’.”
  • One-to-many: a position attribute on the “many” side, named <qualifier>POS (here it would be searchItemPOS). Platform examples in the generated models: classificationClassPOS, unitPOS.
  • Many-to-many: the position is stored in the link table.
  • The getter returns items in the order the list was saved. To change the order, reorder the list and save it.
  • Use it for orders people arrange by hand (a sequence, “move up/down”). It doesn’t keep items sorted by an attribute.
  • Adding it to an existing relation: update system adds the column, but existing rows have no position value, so you need to fill it in.

2. Sort in Java after calling the getter. This is the simplest option for small collections; all rows are still loaded.

searchItem.getSearchFilters().stream()
        .sorted(Comparator.comparing(SearchFilterModel::getCode))
        .toList();

3. A dynamic attribute that returns the sorted list. Callers get a getter without knowing how the sorting is done. The cost is a collection type, a handler class and a Spring bean.

<attribute qualifier="sortedSearchFilters" type="SearchFilterList">
    <persistence type="dynamic" attributeHandler="sortedSearchFiltersAttributeHandler"/>
    <modifiers write="false"/>
</attribute>

The DynamicAttributeHandler either sorts the relation collection or runs an ORDER BY query.

4. ORDER BY in a DAO query. The database sorts, and you can sort on any attribute, several attributes, or a joined type’s attribute. Uses this: ... ORDER BY {sf:code} ASC in DefaultSearchItemDao.

NeedUse
Order set by hand by users or adminsordered="true"
Small collection, sort only in one placeJava Comparator
Sorted getter reused in many placesDynamic attribute
Sort by data, larger collections, filtering at the same timeDAO ORDER BY

6. Indexes and constraints

<indexes>
    <index name="SearchItemCustomerIdx" unique="true">
        <key attribute="customer"/>
        <key attribute="dataType"/>
    </index>
</indexes>

  • Without unique, an index only speeds up lookups. Add indexes for columns used in WHERE clauses.
  • One-to-many relation columns get no index automatically. Declare one if you look up children by their parent; the platform does this itself (oeOrd on AbstractOrderEntry.order in core-items.xml). SearchFilterSearchItemIdx(searchItem), because the filter list query and getSearchFilters() both look up rows by p_searchitem.
  • With unique="true", the database enforces uniqueness.

Hybris has no real database foreign keys and no CHECK constraints. Data integrity comes from these layers:

LayerMechanismCatches
Service layeroptional="false", unique="true" (via interceptors such as UniqueAttributesInterceptor)Normal modelService.save() calls, with readable errors
DatabaseUnique <index>Everything, including concurrent saves and direct SQL/ImpEx
Custom codeValidateInterceptor / PrepareInterceptor Spring beansBusiness rules, for example startDate <= endDate
Data validationConstraints framework (AttributeConstraint, set up in Backoffice/ImpEx)Size/pattern/not-null rules, shown in Backoffice

When the service layer or the database rejects a duplicate, modelService.save() throws a ModelSavingException. For how to handle a concurrent duplicate save, see DefaultSearchItemService.getOrCreateSearchItem in SEARCH_ITEM_FILTERS.md.

Use both layers: the interceptor gives a readable error, and the database index covers concurrent saves that the interceptor can miss.


7. What happens when you change items.xml

  1. ant build regenerates the Model classes. If an attribute exists in XML but you get “cannot find symbol”, the build hasn’t run.
  2. Update system (HAC or ant updatesystem) adds new tables and columns.
    • It never drops or alters existing columns. Renaming an attribute creates a new empty column, and changing a type may silently do nothing. Plan renames with a data migration.
    • Index changes on existing tables may not apply. For example, turning an existing index unique may need a manual drop or rename. Delete duplicate rows first, or the unique index can’t be created.
  3. Initialize drops everything. Never run it on shared or production systems.

8. Common gotchas

  • Typecode collisions break the build. Check them with grep 'typecode="12' before you choose one.
  • <description> must come before <deployment>.
  • Don’t extend OOTB types with autocreate="true"; that tries to create the type again. Use autocreate="false".
  • partof="true" on the wrong side can delete more than you expect. Put it on the children side.
  • Every property attribute is a column, and wide tables like products hit database limits. Large text should be HYBRIS.LONG_STRING or a separate type.
  • To see the live result, open HAC → Platform → Type system and look up a type such as SearchItem. It shows attributes, modifiers and the deployment table.

9. Practice

Using the xml code that defined the Manufacturer item type on step 3 above to practice.

Step 1: add the XML code to the trainingcore-items.xml file

Step 2: Add the one-to-many relationship between the compose types.

Step 3: Build the Hybris system to see the new type and the relationship will be generated on each item type. $ ant clean all

Step 4. Start the server -> update the system

Step 5: Import sample data

Step 6: Verify the result in BO.

10. Best practice

https://help.sap.com/docs/SAP_COMMERCE/d0224eca81e249cb821f2cdf45a82ace/13874f67274c4f5189ce8138d55ea26f.html

https://help.sap.com/docs/SAP_COMMERCE/d0224eca81e249cb821f2cdf45a82ace/8bffa9cc86691014bb70ac2d012708bc.html

https://help.sap.com/docs/SAP_COMMERCE/129a68efcdaf43dc94243b57f9aba5ad/8ecae959b9bd46b8b426fa8dbde5cac4.html

Congratulation.! Good job. You have just finished the lesson. Thanks for your concentration. If this post is useful, please share and give me a heart up in the post.

That’s all for today. In the next post, we will learn about the Converter and Populator. Hope to see you in the next article.

Happy coding.!!! <3



<<< Cronjob | Task Schedule | Export Order Data to CSV

Converters and Populators | Unit 10 >>>



Leave a Reply

Your email address will not be published. Required fields are marked *