DEV Community

Aliaksandr Tsviatkou
Aliaksandr Tsviatkou

Posted on

The SAP Commerce Type System Deep Dive: From items.xml to Database and Beyond

The type system is the beating heart of SAP Commerce. It defines every data structure in the platform — from products and orders to customers and CMS components. Unlike traditional Java applications where you write JPA entities and manage database migrations with Flyway, SAP Commerce uses a metadata-driven approach: you declare your data model in XML, and the platform generates Java classes, database schemas, and runtime type metadata automatically.

This article is the definitive deep dive into that system. Whether you're a developer extending the OOTB model for the first time or a senior architect debugging type system internals, this guide covers everything from the XML syntax to the generated bytecode to the database tables underneath.

The Type Hierarchy

Every piece of data in SAP Commerce is an Item. The type hierarchy is:

                        ┌─────────────┐
                        │    Item      │  (abstract root — PK, creation/modification time)
                        └──────┬──────┘
                               │
              ┌────────────────┼────────────────┐
              │                │                │
     ┌────────▼───────┐ ┌─────▼──────┐  ┌──────▼──────┐
     │  GenericItem    │ │  CronJob   │  │  Type       │
     │  (no deployment │ │            │  │  (metadata) │
     │   needed)       │ │            │  │             │
     └────────┬───────┘ └────────────┘  └─────────────┘
              │
    ┌─────────┼──────────┬──────────────┐
    │         │          │              │
┌───▼──┐ ┌───▼───┐ ┌────▼────┐  ┌─────▼──────┐
│Product│ │ Order │ │Customer │  │Your Custom │
│      │ │       │ │(User)   │  │   Type     │
└──────┘ └───────┘ └─────────┘  └────────────┘
Enter fullscreen mode Exit fullscreen mode

Core Meta-Types

The type system itself is described by meta-types — types that describe other types:

Meta-Type Description Examples
ComposedType Describes an item type (like a Java class definition) Product, Order, Customer
AtomicType Wraps a Java primitive or simple type java.lang.String, java.lang.Integer, java.util.Date
CollectionType A typed collection of another type StringCollection (List of Strings)
MapType A typed key-value map localized:java.lang.String (language → string map)
EnumType An enumeration with fixed or dynamic values ArticleApprovalStatus, OrderStatus
RelationType Describes a relationship between two ComposedTypes Product2CategoryRelation

You can think of ComposedType as analogous to java.lang.Class — it's the runtime descriptor for an item type. Every ComposedType has:

  • A code (e.g., "Product")
  • A supertype (parent ComposedType)
  • A set of AttributeDescriptor instances (the type's attributes)
  • A deployment (database table mapping)
  • A jaloclass (legacy Jalo layer class)

Attribute Persistence Modes

Every attribute in items.xml has a persistence mode that determines how its value is stored:

property — Database Column

<persistence type="property"/>
Enter fullscreen mode Exit fullscreen mode

The standard mode. Creates a column in the type's deployment table. This is what you use 90% of the time. The column type is determined by the attribute's Java type:

Java Type Database Column (HANA) Database Column (MySQL)
java.lang.String NVARCHAR(255) VARCHAR(255)
java.lang.Integer INTEGER INT
java.lang.Long BIGINT BIGINT
java.lang.Double DOUBLE DOUBLE
java.lang.Boolean TINYINT TINYINT
java.util.Date TIMESTAMP DATETIME
java.math.BigDecimal DECIMAL(30,8) DECIMAL(30,8)

You can customize column size with:

<persistence type="property">
    <columntype database="mysql">
        <value>VARCHAR(1000)</value>
    </columntype>
    <columntype database="hana">
        <value>NVARCHAR(1000)</value>
    </columntype>
    <columntype>
        <value>java.math.BigDecimal</value>
    </columntype>
</persistence>
Enter fullscreen mode Exit fullscreen mode

dynamic — Computed at Runtime

<persistence type="dynamic" attributeHandler="myAttributeHandler"/>
Enter fullscreen mode Exit fullscreen mode

Dynamic attributes have no database column. Instead, a Spring bean (DynamicAttributeHandler) computes the value on every read:

public class LoyaltyScoreHandler implements DynamicAttributeHandler<Double, CustomerModel> {

    @Override
    public Double get(CustomerModel model) {
        Integer points = model.getLoyaltyPoints();
        if (points == null) return 0.0;
        // Complex calculation based on points, tier, and activity
        double multiplier = getMultiplierForTier(model.getLoyaltyTier());
        return points * multiplier;
    }

    @Override
    public void set(CustomerModel model, Double value) {
        // Read-only attribute — throw exception or ignore
        throw new UnsupportedOperationException("loyaltyScore is read-only");
    }

    private double getMultiplierForTier(LoyaltyTier tier) {
        if (tier == null) return 1.0;
        switch (tier.getCode()) {
            case "PLATINUM": return 2.0;
            case "GOLD": return 1.5;
            case "SILVER": return 1.25;
            default: return 1.0;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Register in Spring:

<bean id="loyaltyScoreHandler"
      class="com.mycompany.handlers.LoyaltyScoreHandler"/>
Enter fullscreen mode Exit fullscreen mode

Use dynamic attributes when:

  • The value is computed from other attributes
  • The value comes from an external system (API call, cache lookup)
  • You want a virtual attribute without database storage overhead
  • The value changes frequently and caching would be stale

Avoid dynamic attributes when:

  • You need to query/filter by this attribute in FlexibleSearch (dynamic attributes can't be used in WHERE clauses)
  • The computation is expensive and the attribute is read frequently
  • You need the value to persist across restarts

jalo — Legacy Mode

<persistence type="jalo"/>
Enter fullscreen mode Exit fullscreen mode

Jalo persistence means the attribute getter/setter is implemented in the Jalo layer class. This is deprecated — never use this for new attributes. It exists only for backward compatibility with very old extensions.

cmp — Container Managed Persistence

An internal persistence mode used by the platform for certain system attributes. You should never use cmp for custom attributes.

Type Deployment: How Types Map to Database Tables

Understanding how the type system maps to relational storage is critical for debugging and performance.

Dedicated Table Deployment

When you specify <deployment table="my_table" typecode="25000"/>, the type gets its own table:

loyalty_tx table:
┌──────────────┬─────────────┬────────┬──────────┬─────────────────┬───────────┐
│ PK (BIGINT)  │ TypePkString│ p_code │ p_points │ p_txdate        │OwnerPkStr │
│              │             │        │          │                 │           │
│ 8796093087744│ 8796...     │ LT-001 │ 500      │ 2025-01-15 10:30│ null      │
└──────────────┴─────────────┴────────┴──────────┴─────────────────┴───────────┘
Enter fullscreen mode Exit fullscreen mode

Key columns automatically added by the platform:

Column Purpose
PK Primary key (BIGINT) — generated by the PK generator
TypePkString The PK of the ComposedType record (enables polymorphic queries)
p_* Attribute columns (prefixed with p_ by convention)
OwnerPkString Owner reference (used for partof relationships)
createdTS Creation timestamp
modifiedTS Last modification timestamp
aCLTS Access control timestamp
propTS Properties timestamp

Single-Table Inheritance

If a type does not have a <deployment>, its attributes are stored in the parent type's table. For example, Customer extends User, and if Customer has no separate deployment, its custom attributes go into the users table:

users table:
┌────────┬──────────┬──────────┬────────────────┬───────────────┐
│  PK    │  p_uid   │  p_name  │ p_loyaltytier  │ p_loyaltypts  │
│        │          │          │ (from Customer)│ (from Customer)│
└────────┴──────────┴──────────┴────────────────┴───────────────┘
Enter fullscreen mode Exit fullscreen mode

This is efficient for small additions but can bloat the parent table if many subtypes add many attributes.

The props Table (Overflow)

When an attribute cannot fit in the type's deployment table (e.g., the type has no dedicated deployment, or the attribute is added to a type after initialization without running updatesystem), it goes to the props table:

props table:
┌────────────┬───────────┬────────────────┬──────────────┐
│  ITEMPK    │  NAME     │  LANGPK        │  VALUE1      │
│            │           │                │              │
│ 8796...    │ myattr    │ null           │ some_value   │
└────────────┴───────────┴────────────────┴──────────────┘
Enter fullscreen mode Exit fullscreen mode

The props table is essentially a key-value store. It's flexible but terrible for performance — every attribute access requires a separate lookup. This is why you should:

  1. Always define a <deployment> for new types
  2. Run ant updatesystem after adding attributes to existing types
  3. Monitor props table usage with:
SELECT COUNT(*) FROM props;
-- If this is large (millions+), investigate which attributes are overflowing
Enter fullscreen mode Exit fullscreen mode

The ydeployments Table

The platform maintains a ydeployments table that maps typecodes to table names:

SELECT * FROM ydeployments;
Enter fullscreen mode Exit fullscreen mode
┌───────────┬─────────────────┬────────────────────┐
│ Typecode  │ TableName       │ ExtensionName      │
├───────────┼─────────────────┼────────────────────┤
│ 1         │ items           │ core               │
│ 23        │ products        │ catalog            │
│ 25000     │ loyalty_tx      │ myextension        │
└───────────┴─────────────────┴────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Localized Attributes

Localization is a first-class citizen in the type system. Any attribute can be made localizable.

Defining Localized Attributes

<attribute qualifier="name" type="localized:java.lang.String">
    <modifiers optional="true"/>
    <persistence type="property"/>
</attribute>

<attribute qualifier="description" type="localized:java.lang.String">
    <modifiers optional="true"/>
    <persistence type="property">
        <columntype>
            <value>HYBRIS.LONG_STRING</value>
        </columntype>
    </persistence>
</attribute>
Enter fullscreen mode Exit fullscreen mode

Database Storage

Localized attributes are stored in a separate *lp table (localized properties):

products table:                    productslp table:
┌────────┬──────────┐             ┌───────────┬──────────┬──────────────┬───────────────┐
│  PK    │  p_code  │             │  ITEMPK   │  LANGPK  │  p_name      │ p_description │
├────────┼──────────┤             ├───────────┼──────────┼──────────────┼───────────────┤
│  123   │  PROD001 │             │  123      │  en      │  Blue Shirt  │ A nice shirt  │
│        │          │             │  123      │  de      │  Blaues Hemd │ Ein Hemd      │
│        │          │             │  123      │  fr      │  Chemise Bleu│ Une chemise   │
└────────┴──────────┘             └───────────┴──────────┴──────────────┴───────────────┘
Enter fullscreen mode Exit fullscreen mode

Using Localized Attributes in Code

// Get in current session language
String name = product.getName();

// Get in specific locale
String germanName = product.getName(Locale.GERMAN);

// Set in current session language
product.setName("Blue Shirt");

// Set in specific locale
product.setName("Blaues Hemd", Locale.GERMAN);

// Get all localizations as a Map
Map<Locale, String> allNames = product.getAllName();
Enter fullscreen mode Exit fullscreen mode

Querying Localized Attributes

-- Query in a specific language
SELECT {pk}, {name[en]} FROM {Product} WHERE {name[de]} LIKE '%Hemd%'

-- Query in the current session language (no language qualifier)
SELECT {pk}, {name} FROM {Product} WHERE {name} LIKE '%Shirt%'
Enter fullscreen mode Exit fullscreen mode

Type System and FlexibleSearch

FlexibleSearch is the query language for the type system. Understanding how type system concepts map to queries is essential.

Curly Brace Syntax Resolution

When you write:

SELECT {pk}, {code}, {name} FROM {Product}
Enter fullscreen mode Exit fullscreen mode

The platform resolves:

  • {Product} → looks up the ComposedType for "Product", finds its deployment table (products)
  • {pk} → maps to the PK column
  • {code} → finds the AttributeDescriptor for "code", maps to p_code column
  • {name} → finds it's localized, joins the productslp table filtered by session language

The generated SQL (simplified) is:

SELECT item.PK, item.p_code, lp.p_name 
FROM products item 
LEFT JOIN productslp lp ON item.PK = lp.ITEMPK AND lp.LANGPK = ?sessionLanguagePK
Enter fullscreen mode Exit fullscreen mode

Polymorphic Queries

Because the type system tracks inheritance, FlexibleSearch supports polymorphic queries:

-- Returns Products AND all subtypes (like ApparelProduct, ElectronicsProduct)
SELECT {pk} FROM {Product}

-- Returns ONLY exact Product instances (no subtypes)
SELECT {pk} FROM {Product!}

-- The ! suffix restricts to the exact type
Enter fullscreen mode Exit fullscreen mode

Type-Aware Joins

-- Join using relation definitions
SELECT {o.pk}, {o.code}, {c.uid}
FROM {Order AS o JOIN Customer AS c ON {o.customer} = {c.pk}}
WHERE {c.loyaltyTier} = ?tier

-- The platform resolves {o.customer} to the FK column automatically
Enter fullscreen mode Exit fullscreen mode

Querying Enums

-- Enum values are referenced by their enumeration code
SELECT {pk} FROM {Customer} WHERE {loyaltyTier} =
    ({{SELECT {pk} FROM {EnumerationValue} WHERE {code} = 'GOLD'
       AND {Type} = ({{SELECT {pk} FROM {ComposedType} WHERE {code} = 'LoyaltyTier'}})}})

-- Or using the simpler HybrisEnumValue approach with parameters
FlexibleSearchQuery query = new FlexibleSearchQuery(
    "SELECT {pk} FROM {Customer} WHERE {loyaltyTier} = ?tier");
query.addQueryParameter("tier", LoyaltyTier.valueOf("GOLD"));
Enter fullscreen mode Exit fullscreen mode

Indexes

Indexes in items.xml map directly to database indexes:

<indexes>
    <!-- Simple index -->
    <index name="codeIdx">
        <key attribute="code"/>
    </index>

    <!-- Unique index (also creates a unique constraint) -->
    <index name="uniqueCodeIdx" unique="true">
        <key attribute="code"/>
    </index>

    <!-- Composite index -->
    <index name="customerDateIdx">
        <key attribute="customer"/>
        <key attribute="transactionDate"/>
    </index>
</indexes>
Enter fullscreen mode Exit fullscreen mode

Generated DDL:

CREATE INDEX codeIdx ON loyalty_tx (p_code);
CREATE UNIQUE INDEX uniqueCodeIdx ON loyalty_tx (p_code);
CREATE INDEX customerDateIdx ON loyalty_tx (p_customer, p_txdate);
Enter fullscreen mode Exit fullscreen mode

Index Best Practices

  1. Index attributes used in WHERE clauses of your FlexibleSearch queries
  2. Composite indexes should follow the leftmost prefix rule — put the most selective column first
  3. Don't over-index — each index slows down writes
  4. Unique indexes on code or uid attributes are almost always a good idea
  5. Monitor slow queries in HAC and add indexes as needed

Best Practices

Naming Conventions

Element Convention Example
Type codes PascalCase, descriptive LoyaltyTransaction, CustomProduct
Attribute qualifiers camelCase loyaltyPoints, transactionDate
Relation codes Source2TargetRelation Customer2LoyaltyTransactionRelation
Enum codes PascalCase LoyaltyTier, TransactionType
Table names lowercase, underscore loyalty_tx, custom_products
Index names camelCase + Idx suffix codeIdx, customerDateIdx

Typecode Management

  • Maintain a typecode registry document in your project
  • Assign ranges per extension (e.g., mycore: 25000–25099, myfacades: 25100–25199)
  • Never reuse a typecode that was previously assigned to a different type
  • In multi-team projects, agree on typecode ranges to avoid collisions

When to Create New Types vs. Extend Existing

Create a new type when:

  • The entity represents a new domain concept (e.g., LoyaltyTransaction)
  • You need dedicated table storage for performance
  • The entity has its own lifecycle (CRUD operations)

Extend an existing type when:

  • You're adding attributes to OOTB types (e.g., adding loyaltyTier to Customer)
  • The additional data logically belongs to the existing type
  • You don't need a separate table

Performance Considerations

  1. Keep type tables focused: Don't add 50 attributes to a single type — consider splitting into related types
  2. Use dedicated deployments: Types without deployments use single-table inheritance or the props table — both have performance costs at scale
  3. Watch collection-type attributes: They're serialized into a single column — terrible for large collections. Use relations instead
  4. Localized attributes add JOIN overhead: Every localized attribute requires a JOIN to the *lp table. Only localize what truly needs multiple languages
  5. Dynamic attributes are computed per access: Cache-intensive code shouldn't call dynamic attribute getters in tight loops

Real-World Modeling Patterns

Product Hierarchy with Variants

<!-- Base product type (often already exists in OOTB) -->
<itemtype code="ApparelProduct" extends="Product" autocreate="true" generate="true"
          jaloclass="com.mycompany.jalo.ApparelProduct">
    <deployment table="apparel_products" typecode="25010"/>
    <attributes>
        <attribute qualifier="genderList" type="GenderList">
            <modifiers optional="true"/>
            <persistence type="property"/>
        </attribute>
    </attributes>
</itemtype>

<!-- Style variant (color) -->
<itemtype code="ApparelStyleVariantProduct" extends="VariantProduct" autocreate="true" generate="true"
          jaloclass="com.mycompany.jalo.ApparelStyleVariantProduct">
    <deployment table="apparel_style_variants" typecode="25011"/>
    <attributes>
        <attribute qualifier="color" type="localized:java.lang.String">
            <modifiers optional="true"/>
            <persistence type="property"/>
        </attribute>
        <attribute qualifier="swatchImage" type="Media">
            <modifiers optional="true"/>
            <persistence type="property"/>
        </attribute>
    </attributes>
</itemtype>

<!-- Size variant -->
<itemtype code="ApparelSizeVariantProduct" extends="ApparelStyleVariantProduct" autocreate="true" generate="true"
          jaloclass="com.mycompany.jalo.ApparelSizeVariantProduct">
    <deployment table="apparel_size_variants" typecode="25012"/>
    <attributes>
        <attribute qualifier="size" type="localized:java.lang.String">
            <modifiers optional="true"/>
            <persistence type="property"/>
        </attribute>
    </attributes>
</itemtype>
Enter fullscreen mode Exit fullscreen mode

Customer Extension for B2B

<itemtype code="B2BCustomer" autocreate="false" generate="false">
    <attributes>
        <attribute qualifier="creditLimit" type="java.math.BigDecimal">
            <modifiers optional="true"/>
            <persistence type="property"/>
        </attribute>
        <attribute qualifier="approvalThreshold" type="java.math.BigDecimal">
            <modifiers optional="true"/>
            <persistence type="property"/>
        </attribute>
        <attribute qualifier="requiresApproval" type="java.lang.Boolean">
            <defaultvalue>Boolean.TRUE</defaultvalue>
            <modifiers optional="true"/>
            <persistence type="property"/>
        </attribute>
    </attributes>
</itemtype>
Enter fullscreen mode Exit fullscreen mode

Custom Order Attributes

<itemtype code="Order" autocreate="false" generate="false">
    <attributes>
        <attribute qualifier="giftMessage" type="localized:java.lang.String">
            <modifiers optional="true"/>
            <persistence type="property"/>
        </attribute>
        <attribute qualifier="preferredDeliveryDate" type="java.util.Date">
            <modifiers optional="true"/>
            <persistence type="property"/>
        </attribute>
        <attribute qualifier="loyaltyPointsEarned" type="java.lang.Integer">
            <defaultvalue>Integer.valueOf(0)</defaultvalue>
            <modifiers optional="true"/>
            <persistence type="property"/>
        </attribute>
    </attributes>
</itemtype>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)