How to use SAP Commerce (Hybris) Region Cache

The process of cacheing is used to store data frequently demanded and rapidly retrieve it when needed.

The SAP Commerce Region Cache brings the possibility of splitting the content into so called regions. This approach allows as us to control the size allocated to chache specific objects and its eviction strategy.

As we can see in the official documentation SAP Commerce uses the following eviction types:
  1. Least Recently Used (LRU): The last used timestamp is updated when an element is put into the cache or an element is retrieved from the cache with a GET call.
  2. Least Frequently Used (LFU): For each GET call on the element, the number of hits is updated. When a PUT call is made for a new element, and assuming that the maximum limit is reached for the memory store, the element with least number of hits, the Less Frequently Used element, is evicted.
  3. First In, First Out (FIFO): Elements are evicted in the same order as they come in. When a PUT call is made for a new element, and assuming that the maximum limit is reached for the memory store, the element that was placed first (First-In) in the store is the candidate for eviction (First-Out).

More info here

In the further lines we will explore a real example of using a custom Hybris Region Cache.

  1. Extend EHCacheRegion and implement the methods that you need.
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
public class CacheAccessImpl extends EHCacheRegion implements CacheAccess{
    private static final Logger LOG = LoggerFactory.getLogger(CacheAccessImpl.class);

    // Insert Constructors

    @Override
    public Object get(Object key) {
        final GenericCacheKey genericCacheKey = generateGenericCacheKey(key);
        try {
            return super.get(genericCacheKey);
        } catch (final IllegalStateException e) {
            LOG.error("IllegalStateException occured", e);
            return null;
        }
    }

    @Override
    public void put(Object key, Object object) throws CacheAccessException {
        final GenericCacheKey genericCacheKey = generateGenericCacheKey(key);
        final DefaultCacheValueLoaderImpl<Object> loader = new DefaultCacheValueLoaderImpl<>();
        loader.setValue(object);
        remove(genericCacheKey);
        LOG.debug("Object with following key(s) {}  put into cache region {} ", genericCacheKey.toString(), this.getName());

        super.getWithLoader(genericCacheKey, loader);
    }

    @Override
    public void putIfAbsent(final Object key, final Object object) throws CacheAccessException {
        if (isAbsent(key)) {
            put(key, object);
        }
    }

    @Override
    public void remove(Object key) throws CacheAccessException {
        final GenericCacheKey genericCacheKey = generateGenericCacheKey(key);
        LOG.debug("Object with following key(s) {} removed from cache region {} ", genericCacheKey.toString(), this.getName());
        super.remove(genericCacheKey, false);
    }
    /**
     * Generates a {@link GenericCacheKey} from key object.
     *
     * @param key to be converted
     * @return {@link GenericCacheKey}
     */
    private GenericCacheKey generateGenericCacheKey(final Object key) {
        if (key instanceof GenericCacheKey) {
            return (GenericCacheKey) key;
        } else {
            return new GenericCacheKey(key, GenericCacheKey.DEFAULT_CACHE_TYPECODE);
        }
    }

    /**
     * Checks if the key is in cache.
     *
     * @param key the key for which the absence is checked
     * @return true if key is not in cache, false if it is in cache
     */
    private boolean isAbsent(final Object key) {
        final GenericCacheKey genericCacheKey = generateGenericCacheKey(key);
        return !super.containsKey(genericCacheKey);
    }

    /**
     * Inner implementation of the {@link CacheValueLoader} which is used for method <code>put</code>.
     *
     * @param <V>
     */
    private static class DefaultCacheValueLoaderImpl<V> implements CacheValueLoader<V> {

        private V obj;

        /**
         * Standard constructor.
         */
        public DefaultCacheValueLoaderImpl() {
            super();
        }

        @Override
        public V load(final CacheKey arg0) {
            return this.obj;
        }

        /**
         * Set value to be loaded from {@link CacheValueLoader}.
         *
         * @param obj value to be loaded.
         */
        @SuppressWarnings("unchecked")
        public void setValue(final Object obj) {
            this.obj = (V) obj;
        }

    }
}

  1. Configure your new Cache Region. Create a new spring cache configuration file. (ex: devistacore/resources/devistacore-spring-cache.xml)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">


    <alias alias="devistaPriceCacheRegion" name="defaultDevistaPriceCacheRegion"/>
    <bean id="defaultDevistaPriceCacheRegion" parent="priceCacheRegion">
        <constructor-arg name="name" value="devistaPriceCacheRegion"/>
        <constructor-arg name="maxEntries" value="1000"/>
        <constructor-arg name="evictionPolicy" value="FIFO"/>
        <constructor-arg name="statsEnabled" value="true"/>
        <constructor-arg name="exclusiveComputation" value="false"/>
        <property name="handledTypes">
            <array>
                <value></value>
            </array>
        </property>
    </bean>
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetObject" ref="cacheRegionsList"/>
        <property name="targetMethod" value="add"/>
        <property name="singleton" value="true"/>
        <property name="arguments">
            <ref bean="devistaPriceCacheRegion"/>
        </property>
    </bean>
    <alias alias="priceCacheRegion" name="priceDefaultCacheRegion"/>
    <bean abstract="true" class="com.devista.core.cache.CacheAccessImpl" id="priceDefaultCacheRegion">
        <constructor-arg name="evictionPolicy" value="LRU"/>
        <constructor-arg name="statsEnabled" value="true"/>
        <constructor-arg name="exclusiveComputation" value="false"/>
    </bean>

</beans>

After this point you should be able to see your new created cache Region file in HAC Console.

*Don’t forget to add your new created configuration file to the global application context:

devistacore.global-context=devistacore-spring-cache.xml

  1. Create your cache service
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class DevistaPriceCacheServiceImpl implements DevistaPriceCacheService {

    private CacheAccess cacheAccess;

    @Override
    public void add(String key, PriceInformation object) {
        try {
            cacheAccess.putIfAbsent(key,object);
        } catch (CacheAccessException e) {
            LOGGER.error("Unable to cache the price information",e);
        }
    }

    @Override
    public PriceInformation get(String key) {
        if (Objects.isNull(key)){
            return null;
        }else{
            PriceInformation chachedInfo = (PriceInformation)cacheAccess.get(key);
            return chachedInfo;
        }

    }
    public void setCacheAccess(CacheAccess cacheAccess) {
        this.cacheAccess = cacheAccess;
    }
}

  1. Use the sevice where needed
36
37
38
39
40
41
42
43
44
45
 public PriceInformation getWebPriceForProduct(ProductModel product) {
        validateParameterNotNull(product, "Product model cannot be null");
        
       PriceInformation priceInformation = devistaPriceCacheService.get(product.getCode());
        if (Objects.isNull(priceInformation)){
            // retrive PriceInformation
        }else{
            return priceInformation;
        }
    }

This is a brief example of using Hybris Region Cache. If you found it usefull make sure to follow us for more:

Devista – Your way into the future!