Tag: Hibernate

Hibernate4 and Coherence

There are two ways to integrate Hibernate and Coherence:

  • Coherence can be used as a second level cache provider for Hibernate.
  • Hibernate can be used as a CacheStore provider for Coherence.

In the case Coherence is used as a second level cache provider for Hibernate, Coherence caching is fully controlled by Hibernate; thus a good understanding of Hibernate caching mechanisms is required. This scenario is a good fit for applications that:

  • Use Hibernate APIs for the data access management.
  • Have complex object models.
  • Have fine-grained transactional requirements.
  • Have an application server cluster that run the Hibernate application.

The set-up using Hibernate3 can be found in the post Hibernate and Coherence. Note that in the case of Hibernate3 we provided implementations for the interfaces org.hibernate.cache.Cache and org.hibernate.cache.CacheProvider. In Hibernate4 this concepts has been changed to a more fine grained implementation structure, for example,

  • At the top we have an implementation for the RegionFactory interface. This implementation defines how to start and stop the cache, sets some defaults (such as if minimal puts are enabled and the access type) and defines the region implementation. It is now possible to define separate implementation for entities and collections etcetera.
    • How entities or collections are cached are defined by implementing regions, such as, EntityRegion – Defines the contract for a cache region which will specifically be used to store entity data. CollectionRegion – Defines the contract for a cache region which will specifically be used to store collection data. The region contracts define an access strategy for the requested access type.
      • To define how the caching is handled we implement the following three interfaces:
        • TransactionalDataRegion – Defines the contract for regions which hold transactionally-managed data.
        • GeneralDataRegion – Contract for general-purpose cache regions.
        • Region – Defines a contract for accessing a particular named region within the underlying cache implementation.

Let us look at an example of how to implement the various interfaces in the case we are dealing with an entity. Our entity looks as follows:

package model.entities;

import java.io.Serializable;

public class Department implements Serializable {

    private Integer departmentNumber;
    private String departmentName;
    private String location;

    public Department() {
    }

    public void setDepartmentNumber(Integer departmentNumber) {
        this.departmentNumber = departmentNumber;
    }

    public Integer getDepartmentNumber() {
        return departmentNumber;
    }

    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }

    public String getDepartmentName() {
        return departmentName;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getLocation() {
        return location;
    }

    @Override
    public boolean equals(Object object) {
        System.out.println("DEPARTMENT EQUALS METHOD CALLED " + object);
        if (this == object) {
            return true;
        }

        if (object == null) {
            return false;
        }

        if (!(object instanceof Department)) {
            return false;
        }

        Department department = (Department) object;
        return departmentNumber.equals(department.getDepartmentNumber());
    }

    @Override
    public int hashCode() {
        if (departmentNumber != null) {
            return 31 * departmentNumber.hashCode();
        } else {
            return System.identityHashCode(this);
        }
    }

    @Override
    public String toString() {
        return departmentName + ", " + location;
    }
}

which has the following mapping

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="model.entities">
    <class name="Department" table="DEPT">
        <id name="departmentNumber" type="integer" column="DEPTNO">
            <generator class="assigned"/>
        </id>
        <property name="departmentName" type="string" column="DNAME"/>
        <property name="location" type="string" column="LOC"/>
    </class>
</hibernate-mapping>

We start with defining the contract for accessing a particular named region within the underlying cache implementation, by providing an implementation for the org.hibernate.cache.spi.Region interface

package cache.regions;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.Region;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class CoherenceRegion implements Region {

    private final NamedCache cache;

    public CoherenceRegion(NamedCache cache) {
        this.cache = cache;
    }

    public NamedCache getCache() {
        return cache;
    }

    public String getName() {
        return cache.getCacheName();
    }

    public void destroy() throws CacheException {
        cache.release();
    }

    public boolean contains(Object key) {
        System.out.println("LOOK IF DATA IS IN THE CACHE " + key);
        return cache.containsKey(key);
    }

    public long getSizeInMemory() {
        return -1;
    }

    public long getElementCountInMemory() {
        long size = 0;
        try {
            size = cache.size();
        } catch (CacheException ex) {
            throw new CacheException(ex);
        }
        return size;
    }

    public long getElementCountOnDisk() {
        return -1;
    }

    public Map toMap() {
        Map result = new HashMap();
        try {
            Iterator iterator = cache.keySet().iterator();
            while (iterator.hasNext()) {
                Object key = iterator.next();
                result.put(key, cache.get((Serializable) key));
            }
        } catch (Exception ex) {
            throw new CacheException(ex);
        }
        return result;
    }

    public long nextTimestamp() {
        return CacheFactory.getCluster().getTimeMillis();
    }

    public int getTimeout() {
        return 10000;
    }
}

Next, we provide the contract for general-purpose cache regions (i.e. how to get, put and remove data from the cache), by implementing the org.hibernate.cache.spi.GeneralDataRegion interface

package cache.regions;

import com.tangosol.net.NamedCache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.GeneralDataRegion;

public class CoherenceGeneralDataRegion extends CoherenceRegion implements GeneralDataRegion {

    public CoherenceGeneralDataRegion(NamedCache cache) {
        super(cache);
    }

    public Object get(Object key) throws CacheException {
        System.out.println("GET DATA FROM THE CACHE " + key);
        return getCache().get(key);
    }

    public void put(Object key, Object value) throws CacheException {
        System.out.println("PUT DATA INTO THE CACHE " + key + ", " + value);
        getCache().put(key, value);
    }

    public void evict(Object key) throws CacheException {
        System.out.println("REMOVE DATA FROM THE CACHE " + key);
        getCache().remove(key);
    }

    public void evictAll() throws CacheException {
        getCache().clear();
    }
}

Subsequently, we define the contract for regions which hold transactionally-managed data, by implementing the org.hibernate.cache.spi.TransactionalDataRegion interface

package cache.regions;

import com.tangosol.net.NamedCache;
import org.hibernate.cache.spi.CacheDataDescription;
import org.hibernate.cache.spi.TransactionalDataRegion;

public class CoherenceTransactionalDataRegion extends CoherenceGeneralDataRegion implements TransactionalDataRegion {

    private final CacheDataDescription metadata;

    public CoherenceTransactionalDataRegion(NamedCache cache, CacheDataDescription metadata) {
        super(cache);
        this.metadata = metadata;
    }

    public boolean isTransactionAware() {
        return false;
    }

    public CacheDataDescription getCacheDataDescription() {
        return metadata;
    }
}

As a final step we need to build the contract for a cache region which will specifically be used to store entity data (and define which access type to use), by implementing the org.hibernate.cache.spi.EntityRegion interface

package cache.regions;

import cache.strategy.CoherenceNonStrictReadWriteEntityRegionAccessStrategy;
import com.tangosol.net.NamedCache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.CacheDataDescription;
import org.hibernate.cache.spi.EntityRegion;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;

public class CoherenceEntityRegion extends CoherenceTransactionalDataRegion implements EntityRegion {

    public CoherenceEntityRegion(NamedCache cache, CacheDataDescription metadata) {
        super(cache, metadata);
    }

    public EntityRegionAccessStrategy buildAccessStrategy(AccessType accessType) throws CacheException {
        switch (accessType) {
            case NONSTRICT_READ_WRITE:
                return new CoherenceNonStrictReadWriteEntityRegionAccessStrategy(this);
            default:
                throw new IllegalArgumentException("unrecognized access strategy type [" + accessType + "]");
        }
    }
}

In the implementation above, we assume the access strategy always to be non-strict read-write. There are four access strategies that can be used:

  • Read only – If the application needs to read, but not modify, instances of a persistent class, a read-only cache can be used. This is the simplest and optimal performing strategy. It is even safe for use in a cluster.
  • Read/write – If the application needs to update data, a read-write cache might be appropriate. This cache strategy should never be used if serializable transaction isolation level is required. If the cache is used in a JTA environment, you must specify the property hibernate.transaction.manager_lookup_class and naming a strategy for obtaining the JTA TransactionManager. In other environments, you should ensure that the transaction is completed when Session.close() or Session.disconnect() is called. If you want to use this strategy in a cluster, you should ensure that the underlying cache implementation supports locking.
  • Non-strict read/write – If the application only occasionally needs to update data (i.e. if it is extremely unlikely that two transactions would try to update the same item simultaneously), and strict transaction isolation is not required, a non-strict-read-write cache might be appropriate. If the cache is used in a JTA environment, you must specify hibernate.transaction.manager_lookup_class. In other environments, you should ensure that the transaction is completed when Session.close() or Session.disconnect() is called.
  • Transactional – The transactional cache strategy provides support for fully transactional cache providers. Such a cache can only be used in a JTA environment and you must specify hibernate.transaction.manager_lookup_class.

To define the contract for transactional and concurrent access to cached entity (and collection) data, we start by implementing the RegionAccessStrategy interface

package cache.strategy;

import cache.regions.CoherenceEntityRegion;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.access.RegionAccessStrategy;
import org.hibernate.cache.spi.access.SoftLock;

public class CoherenceEntityRegionAccessStrategy implements RegionAccessStrategy {

    private final CoherenceEntityRegion coherenceEntityRegion;

    public CoherenceEntityRegionAccessStrategy(CoherenceEntityRegion coherenceEntityRegion) {
        this.coherenceEntityRegion = coherenceEntityRegion;
    }

    public CoherenceEntityRegion getCoherenceEntityRegion() {
        return coherenceEntityRegion;
    }

    public Object get(Object key, long transactionTimeStamp) throws CacheException {
        return getCoherenceEntityRegion().get(key);
    }

    public boolean putFromLoad(Object key, Object value, long transactionTimeStamp, Object version) throws CacheException {
        return putFromLoad(key, value, transactionTimeStamp, version, true);
    }

    public boolean putFromLoad(Object key, Object value, long transationTimeStamp, Object version, boolean minimalPutsOverride) throws CacheException {
        if (key == null || value == null) {
            return false;
        }

        // check if item is already in the cache
        if (minimalPutsOverride && getCoherenceEntityRegion().contains(key)) {
            return false;
        }

        // key is cached
        getCoherenceEntityRegion().put(key, value);
        return true;
    }

    public SoftLock lockItem(Object key, Object version) throws CacheException {
        getCoherenceEntityRegion().getCache().lock(key);
        return null;
    }

    public SoftLock lockRegion() throws CacheException {
        return null;
    }

    public void unlockItem(Object key, SoftLock softLock) throws CacheException {
        getCoherenceEntityRegion().getCache().unlock(key);
    }

    public void unlockRegion(SoftLock softLock) throws CacheException {
        evictAll();
    }

    public void remove(Object key) throws CacheException {
        evict(key);
    }

    public void removeAll() throws CacheException {
        evictAll();
    }

    public void evict(Object key) throws CacheException {
        getCoherenceEntityRegion().evict(key);
    }

    public void evictAll() throws CacheException {
        getCoherenceEntityRegion().evictAll();
    }
}

Finally, we define the contract for managing transactional and concurrent access to cached entity data, by implementing the EntityRegionAccessStrategy interface

package cache.strategy;

import cache.regions.CoherenceEntityRegion;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.EntityRegion;
import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;
import org.hibernate.cache.spi.access.SoftLock;

public class CoherenceNonStrictReadWriteEntityRegionAccessStrategy extends CoherenceEntityRegionAccessStrategy implements EntityRegionAccessStrategy {

    public CoherenceNonStrictReadWriteEntityRegionAccessStrategy(CoherenceEntityRegion region) {
        super(region);
    }

    public EntityRegion getRegion() {
        return getCoherenceEntityRegion();
    }

    public boolean insert(Object key, Object value, Object version) throws CacheException {
        return false;
    }

    public boolean afterInsert(Object key, Object value, Object version) throws CacheException {
        return false;
    }

    public boolean update(Object key, Object value, Object currentVersion, Object previousVersion) throws CacheException {
        return false;
    }

    public boolean afterUpdate(Object key, Object value, Object currentVersion, Object previousVersion, SoftLock softLock) throws CacheException {
        return false;
    }
}

To make sure Hibernate uses a second-level cache, we use the following configuration

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="hibernate.connection.driver_class">oracle.jdbc.OracleDriver</property>
        <property name="hibernate.connection.url">jdbc:oracle:thin:@192.168.1.60:1521:orcl11</property>
        <property name="hibernate.connection.username">example</property>
        <property name="hibernate.connection.password">example</property>
        <!-- SQL dialect -->
        <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
        <!-- Enable Hibernate's automatic session context management -->
        <property name="hibernate.current_session_context_class">thread</property>
        <!-- Second-level cache config -->
        <property name="hibernate.cache.region.factory_class">cache.CoherenceRegionFactory</property>
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <!-- Echo all executed SQL to stdout -->
        <property name="hibernate.show_sql">false</property>
        <property name="hibernate.format_sql">false</property>
        <!-- Configure SessionFactory -->
        <mapping resource="model/entities/Department.hbm.xml"/>
        <!-- Cache configuration-->
        <class-cache class="model.entities.Department" usage="nonstrict-read-write"/>
    </session-factory>
</hibernate-configuration>

For the Coherence cache configuration we have used the following

<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">
    <caching-scheme-mapping>
        <cache-mapping>
            <cache-name>*</cache-name>
            <scheme-name>hibernate-replicated</scheme-name>
            <init-params>
                <init-param>
                    <param-name>size-limit</param-name>
                    <param-value>1000</param-value>
                </init-param>
            </init-params>
        </cache-mapping>
    </caching-scheme-mapping>
    <caching-schemes>
        <replicated-scheme>
            <scheme-name>hibernate-replicated</scheme-name>
            <service-name>HibernateReplicatedCache</service-name>
            <serializer>
                <instance>
                    <class-name>com.tangosol.io.DefaultSerializer</class-name>
                </instance>
            </serializer>
            <backing-map-scheme>
                <local-scheme>
                    <high-units>{size-limit 0}</high-units>
                </local-scheme>
            </backing-map-scheme>
            <autostart>true</autostart>
        </replicated-scheme>
    </caching-schemes>
</cache-config>

Now let us test the set-up, by using the following

package model.test;

import model.entities.Department;
import model.logic.DepartmentDAO;
import model.logic.DepartmentDAOBean;

import java.util.List;
import java.util.Random;

public class Test {

    public static void main(String[] args) {
        DepartmentDAO departmentDAO = new DepartmentDAOBean();
        //doRandomTest(departmentDAO);
        doPersistenceTest(departmentDAO);
    }

    private static void doRandomTest(DepartmentDAO departmentDAO) {
        Integer[] integers = {10, 20, 30, 40};
        Random random = new Random();
        while (true) {
            departmentDAO.findEntity(integers[random.nextInt(4)]);
        }
    }

    private static void doPersistenceTest(DepartmentDAO departmentDAO) {
        Integer departmentNumber = 12;
        Department newDepartment = createDepartment(departmentNumber, "SOMEDEPARTMENT", "SOMEWHERE");

        System.out.println("ADD DEPARTMENT ENTITY");
        departmentDAO.addEntity(newDepartment);

        System.out.println("FIND DEPARTMENT ENTITY");
        Department department = departmentDAO.findEntity(20);
        System.out.println("FIND ENTITY " + department);

        System.out.println("FIND DEPARTMENT ENTITIES");
        List<Department> departments = departmentDAO.findEntities();
        System.out.println("FIND ENTITIES " + departments);

        System.out.println("UPDATE DEPARTMENT ENTITY");
        newDepartment.setDepartmentName("Else");
        departmentDAO.updateEntity(newDepartment);

        System.out.println("FIND DEPARTMENT ENTITIES");
        departments = departmentDAO.findEntities();
        System.out.println("FIND ENTITIES " + departments);

        System.out.println("REMOVE DEPARTMENT ENTITY");
        departmentDAO.removeEntity(departmentNumber);

        System.out.println("FIND DEPARTMENT ENTITIES");
        departments = departmentDAO.findEntities();
        System.out.println("FIND ENTITIES " + departments);
    }

    private static Department createDepartment(Integer departmentNumber, String departmentName, String location) {
        Department department = new Department();
        department.setDepartmentNumber(departmentNumber);
        department.setDepartmentName(departmentName);
        department.setLocation(location);
        return department;
    }
}

When we run the test (with also the JVM parameters -Dtangosol.coherence.cacheconfig=hibernate-cache-config.xml -Dtangosol.coherence.management=all -Dtangosol.coherence.management.remote=true) the following is observed

ADD DEPARTMENT ENTITY
Jun 21, 2012 2:01:45 PM org.hibernate.annotations.common.Version <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
Jun 21, 2012 2:01:45 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.1.2.Final}
Jun 21, 2012 2:01:45 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
Jun 21, 2012 2:01:45 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Jun 21, 2012 2:01:45 PM org.hibernate.cfg.Configuration configure
INFO: HHH000043: Configuring from resource: /hibernate.cfg.xml
Jun 21, 2012 2:01:45 PM org.hibernate.cfg.Configuration getConfigurationInputStream
INFO: HHH000040: Configuration resource: /hibernate.cfg.xml
Jun 21, 2012 2:01:45 PM org.hibernate.internal.util.xml.DTDEntityResolver resolveEntity
WARN: HHH000223: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/. Use namespace http://www.hibernate.org/dtd/ instead. Refer to Hibernate 3.6 Migration Guide!
Jun 21, 2012 2:01:45 PM org.hibernate.cfg.Configuration addResource
INFO: HHH000221: Reading mappings from resource: model/entities/Department.hbm.xml
Jun 21, 2012 2:01:45 PM org.hibernate.internal.util.xml.DTDEntityResolver resolveEntity
WARN: HHH000223: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/. Use namespace http://www.hibernate.org/dtd/ instead. Refer to Hibernate 3.6 Migration Guide!
Jun 21, 2012 2:01:45 PM org.hibernate.cfg.Configuration doConfigure
INFO: HHH000041: Configured SessionFactory: null
Jun 21, 2012 2:01:45 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000402: Using Hibernate built-in connection pool (not for production use!)
Jun 21, 2012 2:01:45 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000115: Hibernate connection pool size: 20
Jun 21, 2012 2:01:45 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000006: Autocommit mode: false
Jun 21, 2012 2:01:45 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000401: using driver [oracle.jdbc.OracleDriver] at URL [jdbc:oracle:thin:@192.168.1.60:1521:orcl11]
Jun 21, 2012 2:01:45 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000046: Connection properties: {user=example, password=****}
Jun 21, 2012 2:01:45 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.Oracle10gDialect
Jun 21, 2012 2:01:45 PM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000399: Using default transaction strategy (direct JDBC transactions)
Jun 21, 2012 2:01:45 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
2012-06-21 14:01:45.830/0.864 Oracle Coherence 3.7.1.0 <Info> (thread=main, member=n/a): Loaded operational configuration from "jar:file:/C:/temp/frameworks/VoorbeeldSecondLevelCacheHibernate4/lib/coherence/coherence.jar!/tangosol-coherence.xml"
2012-06-21 14:01:45.861/0.895 Oracle Coherence 3.7.1.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "jar:file:/C:/temp/frameworks/VoorbeeldSecondLevelCacheHibernate4/lib/coherence/coherence.jar!/tangosol-coherence-override-dev.xml"
2012-06-21 14:01:45.861/0.895 Oracle Coherence 3.7.1.0 <D5> (thread=main, member=n/a): Optional configuration override "/tangosol-coherence-override.xml" is not specified
2012-06-21 14:01:45.863/0.897 Oracle Coherence 3.7.1.0 <D5> (thread=main, member=n/a): Optional configuration override "/custom-mbeans.xml" is not specified

Oracle Coherence Version 3.7.1.0 Build 27797
 Grid Edition: Development mode
Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.

2012-06-21 14:01:45.996/1.030 Oracle Coherence GE 3.7.1.0 <Info> (thread=main, member=n/a): Loaded Reporter configuration from "jar:file:/C:/temp/frameworks/VoorbeeldSecondLevelCacheHibernate4/lib/coherence/coherence.jar!/reports/report-group.xml"
2012-06-21 14:01:46.408/1.442 Oracle Coherence GE 3.7.1.0 <D4> (thread=main, member=n/a): TCMP bound to /192.168.238.1:8088 using SystemSocketProvider
2012-06-21 14:01:49.759/4.794 Oracle Coherence GE 3.7.1.0 <Info> (thread=Cluster, member=n/a): Created a new cluster "cluster:0xFCDB" with Member(Id=1, Timestamp=2012-06-21 14:01:46.513, Address=192.168.238.1:8088, MachineId=949, Location=site:,machine:r-PC,process:200, Role=IntellijRtExecutionAppMain, Edition=Grid Edition, Mode=Development, CpuCount=8, SocketCount=8) UID=0xC0A8EE01000001380EEB6E1103B51F98
2012-06-21 14:01:49.767/4.801 Oracle Coherence GE 3.7.1.0 <Info> (thread=main, member=n/a): Started cluster Name=cluster:0xFCDB

Group{Address=224.3.7.0, Port=37000, TTL=4}

MasterMemberSet(
  ThisMember=Member(Id=1, Timestamp=2012-06-21 14:01:46.513, Address=192.168.238.1:8088, MachineId=949, Location=site:,machine:r-PC,process:200, Role=IntellijRtExecutionAppMain)
  OldestMember=Member(Id=1, Timestamp=2012-06-21 14:01:46.513, Address=192.168.238.1:8088, MachineId=949, Location=site:,machine:r-PC,process:200, Role=IntellijRtExecutionAppMain)
  ActualMemberSet=MemberSet(Size=1
    Member(Id=1, Timestamp=2012-06-21 14:01:46.513, Address=192.168.238.1:8088, MachineId=949, Location=site:,machine:r-PC,process:200, Role=IntellijRtExecutionAppMain)
    )
  MemberId|ServiceVersion|ServiceJoined|MemberState
    1|3.7.1|2012-06-21 14:01:49.76|JOINED
  RecycleMillis=1200000
  RecycleSet=MemberSet(Size=0
    )
  )

TcpRing{Connections=[]}
IpMonitor{AddressListSize=0}

2012-06-21 14:01:49.826/4.860 Oracle Coherence GE 3.7.1.0 <D5> (thread=Invocation:Management, member=1): Service Management joined the cluster with senior service member 1
2012-06-21 14:01:49.906/4.940 Oracle Coherence GE 3.7.1.0 <Info> (thread=main, member=1): Loaded cache configuration from "file:/C:/temp/frameworks/VoorbeeldSecondLevelCacheHibernate4/out/production/VoorbeeldSecondLevelCacheHibernate4/hibernate-cache-config.xml"
2012-06-21 14:01:49.943/4.977 Oracle Coherence GE 3.7.1.0 <D5> (thread=ReplicatedCache:HibernateReplicatedCache, member=1): Service HibernateReplicatedCache joined the cluster with senior service member 1
FIND DEPARTMENT ENTITY
GET DATA FROM THE CACHE model.entities.Department#20
LOOK IF DATA IS IN THE CACHE model.entities.Department#20
PUT DATA INTO THE CACHE model.entities.Department#20, CacheEntry(model.entities.Department)[RESEARCH,DALLAS]
FIND ENTITY RESEARCH, DALLAS
FIND DEPARTMENT ENTITIES
LOOK IF DATA IS IN THE CACHE model.entities.Department#10
PUT DATA INTO THE CACHE model.entities.Department#10, CacheEntry(model.entities.Department)[ACCOUNTING,NEW YORK]
LOOK IF DATA IS IN THE CACHE model.entities.Department#20
LOOK IF DATA IS IN THE CACHE model.entities.Department#30
PUT DATA INTO THE CACHE model.entities.Department#30, CacheEntry(model.entities.Department)[SALES,CHICAGO]
LOOK IF DATA IS IN THE CACHE model.entities.Department#40
PUT DATA INTO THE CACHE model.entities.Department#40, CacheEntry(model.entities.Department)[OPERATIONS,BOSTON]
LOOK IF DATA IS IN THE CACHE model.entities.Department#12
PUT DATA INTO THE CACHE model.entities.Department#12, CacheEntry(model.entities.Department)[SOMEDEPARTMENT,SOMEWHERE]
FIND ENTITIES [ACCOUNTING, NEW YORK, RESEARCH, DALLAS, SALES, CHICAGO, OPERATIONS, BOSTON, SOMEDEPARTMENT, SOMEWHERE]
UPDATE DEPARTMENT ENTITY
FIND DEPARTMENT ENTITIES
LOOK IF DATA IS IN THE CACHE model.entities.Department#10
LOOK IF DATA IS IN THE CACHE model.entities.Department#20
LOOK IF DATA IS IN THE CACHE model.entities.Department#30
LOOK IF DATA IS IN THE CACHE model.entities.Department#40
LOOK IF DATA IS IN THE CACHE model.entities.Department#12
FIND ENTITIES [ACCOUNTING, NEW YORK, RESEARCH, DALLAS, SALES, CHICAGO, OPERATIONS, BOSTON, Else, SOMEWHERE]
REMOVE DEPARTMENT ENTITY
GET DATA FROM THE CACHE model.entities.Department#12
REMOVE DATA FROM THE CACHE model.entities.Department#12
FIND DEPARTMENT ENTITIES
LOOK IF DATA IS IN THE CACHE model.entities.Department#10
LOOK IF DATA IS IN THE CACHE model.entities.Department#20
LOOK IF DATA IS IN THE CACHE model.entities.Department#30
LOOK IF DATA IS IN THE CACHE model.entities.Department#40
FIND ENTITIES [ACCOUNTING, NEW YORK, RESEARCH, DALLAS, SALES, CHICAGO, OPERATIONS, BOSTON]
2012-06-21 14:01:50.346/5.380 Oracle Coherence GE 3.7.1.0 <D4> (thread=ShutdownHook, member=1): ShutdownHook: stopping cluster node

When we really want a performance boost it would be better to set-up Coherence as write-behind. This means the application no longer uses the Hibernate API’s, but will typically use the Coherence API’s. In this case Hibernate will be used as a CacheStore implementation for Coherence, we typically have the following application characteristics:

  • Use Coherence APIs for the data access management.
  • Have simple object models appropriate for the CacheStore class.
  • Have simple transactional requirements.
  • Require a very high performance, which can be achieved by the use of the Coherence APIs, such as write behind.

Examples of the write-behind set-up can be found in the posts:

References

[1] Hibernate Reference Documentation.
[2] Hibernate JavaDocs.


Wicket Spring in Hibernate on WebLogic

In this post we will look at how to set-up an application that uses Wicket, Spring and Hibernate and runs on WebLogic.

We will use the following versions:
- Wicket-1.5.1 (can be downloaded here).
- Spring-3.0.6 (can be downloaded here).
- Hibernate-3.6.7 (can be downloaded here).
- WebLogic-10.3.5 (can be downloaded here).

First, we will create shared libaries. The hibernate-3.6.war shared library has the following contents:

hibernate-3.6
	META-INF
		MANIFEST.MF
	WEB-INF
		lib
			antlr-2.7.6.jar
			commons-collections-3.1.jar
			dom4j-1.6.1.jar
			hibernate3.jar
			hibernate-jpa-2.0-api-1.0.1.Final.jar
			javassist-3.12.0.GA.jar
			jta-1.1.jar
			slf4j-api-1.6.1.jar

in which the MANIFEST.MF contains:

Manifest-Version: 1.0
Created-By: 1.6.0_05 (BEA Systems, Inc.)
Extension-Name: hibernate
Specification-Title: Hibernate Library
Specification-Version: 3.6
Specification-Vendor: Middleware Magic
Implementation-Title: Hibernate Library
Implementation-Version: 3.6.7
Implementation-Vendor: Middleware Magic

The spring-3.0.war shared library has the following contents:

spring-3.0
	META-INF
		MANIFEST.MF
	WEB-INF
		lib
			aopalliance-1.0.jar
			commons-logging-1.0.4.jar
			org.springframework.aop-3.0.6.RELEASE.jar
			org.springframework.asm-3.0.6.RELEASE.jar
			org.springframework.beans-3.0.6.RELEASE.jar
			org.springframework.context-3.0.6.RELEASE.jar
			org.springframework.core-3.0.6.RELEASE.jar
			org.springframework.expression-3.0.6.RELEASE.jar
			org.springframework.jdbc-3.0.6.RELEASE.jar
			org.springframework.orm-3.0.6.RELEASE.jar
			org.springframework.transaction-3.0.6.RELEASE.jar
			org.springframework.web-3.0.6.RELEASE.jar

in which the MANIFEST.MF contains:

Manifest-Version: 1.0
Created-By: 1.6.0_05 (BEA Systems, Inc.)
Extension-Name: spring
Specification-Title: Spring Library
Specification-Version: 3.0
Specification-Vendor: Middleware Magic
Implementation-Title: Spring Library
Implementation-Version: 3.0.6
Implementation-Vendor: Middleware Magic

The wicket-1.5.war shared library has the following contents:

wicket-1.5
	META-INF
		MANIFEST.MF
	WEB-INF
		lib
			log4j-1.2.16.jar
			slf4j-api-1.6.1.jar
			slf4j-log4j12-1.6.1.jar
			wicket-core-1.5.1.jar
			wicket-request-1.5.1.jar
			wicket-spring-1.5.1.jar
			wicket-util-1.5.1.jar

in which the MANIFEST.MF contains:

Manifest-Version: 1.0
Created-By: 1.6.0_05 (BEA Systems, Inc.)
Extension-Name: wicket
Specification-Title: Wicket Library
Specification-Version: 1.5
Specification-Vendor: Middleware Magic
Implementation-Title: Wicket Library
Implementation-Version: 1.5.1
Implementation-Vendor: Middleware Magic

More information about shared libraries is presented here.

The following interface defines the basic basic persistence operations:

package model.logic;

import java.io.Serializable;
import java.util.List;

public interface GenericDAO<T, ID extends Serializable> {
    public T addEntity(T entity);

    public void removeEntity(ID id);

    public void updateEntity(T entity);

    public T findEntity(ID id);

    public List<T> findEntities();
}

When using Spring and Hibernate the interface can be implemented as follows:

package model.logic;

import org.hibernate.criterion.DetachedCriteria;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public abstract class GenericHibernateSpringDAO<T, ID extends Serializable> implements GenericDAO<T, ID> {

    private Class<T> persistentClass;
    private HibernateTemplate hibernateTemplate;

    public GenericHibernateSpringDAO() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            System.out.println(parameterizedType.getActualTypeArguments()[0]);
            setPersistentClass((Class<T>) parameterizedType.getActualTypeArguments()[0]);
        } else {
            System.out.println("Not an instance of parameterized type: " + type);
        }
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    public void setPersistentClass(Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }

    public HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    public T addEntity(T entity) {
        getHibernateTemplate().save(entity);
        return entity;
    }

    public void removeEntity(ID id) {
        getHibernateTemplate().delete(findEntity(id));
    }

    public void updateEntity(T entity) {
        getHibernateTemplate().update(entity);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public T findEntity(ID id) {
        return (T) getHibernateTemplate().get(getPersistentClass(), id);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public List<T> findEntities() {
        DetachedCriteria detachedCriteria = DetachedCriteria.forClass(getPersistentClass());
        return getHibernateTemplate().findByCriteria(detachedCriteria);
    }
}

The interface can be extended for particular entities, for example,

package model.logic;

import model.entities.Department;
import model.utils.RunTimeInfo;

public interface DepartmentDAO extends GenericDAO<Department, Integer> {
    public RunTimeInfo getRuntimeInfo();
}

with the corresponding implementation:

package model.logic;

import model.entities.Department;
import model.utils.JmxUtils;
import model.utils.RunTimeInfo;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class DepartmentDAOBean extends GenericHibernateSpringDAO<Department, Integer> implements DepartmentDAO {

    private JmxUtils jmxUtils;

    public DepartmentDAOBean() {
    }

    public JmxUtils getJmxUtils() {
        return jmxUtils;
    }

    public void setJmxUtils(JmxUtils jmxUtils) {
        this.jmxUtils = jmxUtils;
    }

    @Transactional(propagation = Propagation.NEVER)
    public RunTimeInfo getRuntimeInfo() {
        return getJmxUtils().getRunTimeInfomation();
    }
}

The Spring configuration has the following contents:

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
    <bean id="someApplication" class="userinterface.wicket.application.SomeApplication">
        <property name="departmentDAO" ref="departmentDAO"/>
        <property name="employeeDAO" ref="employeeDAO"/>
    </bean>
    <bean id="departmentDAO" class="model.logic.DepartmentDAOBean">
        <property name="hibernateTemplate" ref="hibernateTemplate"/>
        <property name="jmxUtils" ref="jmxUtils"/>
    </bean>
    <bean id="employeeDAO" class="model.logic.EmployeeDAOBean">
        <property name="hibernateTemplate" ref="hibernateTemplate"/>
    </bean>
    <bean id="jmxUtils" class="model.utils.JmxUtils"/>
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>model/entities/Department.hbm.xml</value>
                <value>model/entities/Employee.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
            </props>
        </property>
    </bean>
    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/exampleDS" resource-ref="true"/>
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:spring.properties</value>
            </list>
        </property>
    </bean>
    <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
    <tx:annotation-driven transaction-manager="jtaTransactionManager"/>
</beans>

Here SomeApplication is our global application object for the Wicket application. This global application object is created once per application and remains the same when clustered. These qualities make it a good candidate to act as a service locator for the application. Wicket allows us to provide a custom factory for creating the global application object. One such factory is SpringWebApplicationFactory, that obtains a global application object instance from the spring application context. Wicket keeps the instance of the global application object in a threadlocal variable and provides helper methods in components to obtain an instance. The following shows an example web.xml that configures Wicket:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-config.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>TestServlet</servlet-name>
        <servlet-class>userinterface.servlets.TestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>TestServlet</servlet-name>
        <url-pattern>/testservlet</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <filter>
        <filter-name>wicket.hello</filter-name>
        <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
        <init-param>
            <param-name>applicationFactoryClassName</param-name>
            <param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value>
        </init-param>
        <init-param>
            <param-name>applicationClassName</param-name>
            <param-value>userinterface.wicket.application.SomeApplication</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>wicket.hello</filter-name>
        <url-pattern>/somepage/*</url-pattern>
    </filter-mapping>
    <resource-ref>
        <res-ref-name>jdbc/exampleDS</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
</web-app>

Here the TestServlet tests the persistence functionality (and also accesses some WebLogic RunTime information, which will be discussed later) and looks as follows:

package userinterface.servlets;

import model.entities.Department;
import model.entities.Employee;
import model.logic.DepartmentDAO;
import model.logic.EmployeeDAO;
import model.utils.*;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

public class TestServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        DepartmentDAO departmentDAO = Utilities.getDepartmentDAO();

        Department newDepartment = new Department();
        newDepartment.setDepartmentNumber(80);
        newDepartment.setDepartmentName("SOMETHING");
        newDepartment.setLocation("SOMEWHERE");
        departmentDAO.addEntity(newDepartment);

        List<Department> departments = departmentDAO.findEntities();

        departmentDAO.removeEntity(80);

        RunTimeInfo runTimeInfo = departmentDAO.getRuntimeInfo();

        EmployeeDAO employeeDAO = Utilities.getEmployeeDAO();
        Employee employee = employeeDAO.findEntity(7100);
        employee.setEmployeeName("SOMEONE");
        employeeDAO.updateEntity(employee);

        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head><title>GenericSpringHibernateServlet</title></head>");
        out.println("<body>");
        if (departments != null) {
            for (Department department : departments) {
                out.println("<p>" + department.toString() + "</p>");
            }
        } else {
            out.println("<p> no departments </p>");
        }

        if (runTimeInfo != null) {
            List<ServerRunTimeInfo> serverRunTimeInfos = runTimeInfo.getServerRunTimeInfos();
            if (serverRunTimeInfos != null && !serverRunTimeInfos.isEmpty()) {
                for (ServerRunTimeInfo serverRunTimeInfo : serverRunTimeInfos) {
                    out.println("<p>" + serverRunTimeInfo.toString() + "</p>");
                }
            }

            List<ApplicationRunTimeInfo> applicationRunTimeInfos = runTimeInfo.getApplicationRunTimeInfos();
            if (applicationRunTimeInfos != null && !applicationRunTimeInfos.isEmpty()) {
                for (ApplicationRunTimeInfo applicationRunTimeInfo : applicationRunTimeInfos) {
                    out.println("<p>" + applicationRunTimeInfo.toString() + "</p>");
                    List<ComponentRunTimeInfo> componentRunTimeInfos = applicationRunTimeInfo.getComponentRunTimeInfos();
                    if (componentRunTimeInfos != null && !componentRunTimeInfos.isEmpty()) {
                        for (ComponentRunTimeInfo componentRunTimeInfo : componentRunTimeInfos) {
                            out.println("<p> - " + componentRunTimeInfo.toString() + "</p>");
                        }
                    }
                }
            }
        }

        if (employee != null) {
            out.println("<p>" + employee.toString() + "</p>");
        } else {
            out.println("<p> no employee </p>");
        }
        out.println("</body></html>");
        out.close();
    }
}

The global application class (for the Wicket application) has the following contents:

package userinterface.wicket.application;

import model.logic.DepartmentDAO;
import model.logic.EmployeeDAO;
import org.apache.wicket.protocol.http.WebApplication;
import userinterface.wicket.pages.SomePage;

public class SomeApplication extends WebApplication {

    private DepartmentDAO departmentDAO;
    private EmployeeDAO employeeDAO;

    public DepartmentDAO getDepartmentDAO() {
        return departmentDAO;
    }

    public void setDepartmentDAO(DepartmentDAO departmentDAO) {
        this.departmentDAO = departmentDAO;
    }

    public EmployeeDAO getEmployeeDAO() {
        return employeeDAO;
    }

    public void setEmployeeDAO(EmployeeDAO employeeDAO) {
        this.employeeDAO = employeeDAO;
    }

    @Override
    public Class getHomePage() {
        return SomePage.class;
    }
}

Here the Wicket home page class looks as follows:

package userinterface.wicket.pages;

import model.entities.Department;
import model.logic.DepartmentDAO;
import model.utils.Utilities;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import userinterface.wicket.application.SomeApplication;

import java.util.List;

public class SomePage extends WebPage {
    public SomePage() {
        DepartmentDAO departmentDAO = ((SomeApplication) getApplication()).getDepartmentDAO();

        List<Department> departments = departmentDAO.findEntities();

        add(new Label("message", "DEPARTMENTS " + departments));
    }
}

with the corresponding html page:

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
	<head>
		<title>SomePage</title>
	</head>
	<body>
		<span wicket:id="message">some page</span>
	</body>
</html>

To deploy the application to WebLogic, package the application as a war. Note that we do not need to package the jars as these are deployed separately as shared libraries. To deploy the application we create the following directory structure on the server where WebLogic runs:

wicketspringhibernate
	app
		wicket_spring_hibernate.war
	plan

When we deploy wicket_spring_hibernate.war, WebLogic will automatically create a deployment plan (Plan.xml) and a WEB-INF directory containing the weblogic.xml deployment override, in the plan directory. Open the weblogic.xml and add the following contents:

<weblogic-web-app xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.2/weblogic-web-app.xsd">
	<session-descriptor></session-descriptor>
	<jsp-descriptor></jsp-descriptor>
	<container-descriptor>
		<prefer-application-packages>
			<package-name>antlr.*</package-name>
		</prefer-application-packages>
	</container-descriptor>
	<context-root>/wsh</context-root>
	<library-ref>
		<library-name>hibernate</library-name>
		<specification-version>3.6</specification-version>
		<implementation-version>3.6.7</implementation-version>
		<exact-match>true</exact-match>
	</library-ref>
	<library-ref>
		<library-name>spring</library-name>
		<specification-version>3.0</specification-version>
		<implementation-version>3.0.6</implementation-version>
		<exact-match>true</exact-match>
	</library-ref>
	<library-ref>
		<library-name>wicket</library-name>
		<specification-version>1.5</specification-version>
		<implementation-version>1.5.1</implementation-version>
		<exact-match>true</exact-match>
	</library-ref>
</weblogic-web-app>

Here, we tell WebLogic to prefer the antlr package that is part of the application and tell WebLogic to merge the three deployed libraries with the application. Do not forget to update the deployment. The application can be tested by entering the following URL’s:

  • TestServlet – http://hostname:port/wsh/testservlet.
  • Wicket – http://hostname:port/wsh/somepage.

Sometimes it is necessary to obtain some run-time information from the WebLogic server, such as, the current sessions. In order to do this we can use JMX, for example,

package model.utils;

import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.naming.Context;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;

public class JmxUtils {

    public static final String HOSTNAME = "localhost";
    public static final Integer PORT = 7001;
    public static final String USERNAME = "weblogic";
    public static final String PASSWORD = "magic11g";

    public static final String PROTOCOL = "t3";
    public static final String JNDI_ROOT = "/jndi/";

    public static final String MBEAN_SERVER = "weblogic.management.mbeanservers.domainruntime";
    public static final String SERVICE_NAME = "com.bea:Name=DomainRuntimeService,Type=weblogic.management.mbeanservers.domainruntime.DomainRuntimeServiceMBean";

    private JMXConnector connector;

    public JmxUtils() {
    }

    public RunTimeInfo getRunTimeInfomation() {
        return createRunTimeInfo();
    }

    private RunTimeInfo createRunTimeInfo() {
        RunTimeInfo runTimeInfo = null;

        try {
            MBeanServerConnection connection = getMBeanServerConnection();
            ObjectName service = new ObjectName(SERVICE_NAME);
            ObjectName[] serverRunTimes = (ObjectName[]) connection.getAttribute(service, "ServerRuntimes");

            runTimeInfo = new RunTimeInfo();
            runTimeInfo.setServerRunTimeInfos(new ArrayList<ServerRunTimeInfo>());

            for (int i = 0; i &lt; serverRunTimes.length; i++) {
                ServerRunTimeInfo serverRunTimeInfo = new ServerRunTimeInfo();
                serverRunTimeInfo.setServerName((String) connection.getAttribute(serverRunTimes[i], "Name"));
                serverRunTimeInfo.setServerVersion((String) connection.getAttribute(serverRunTimes[i], "WeblogicVersion"));
                serverRunTimeInfo.setServerState((String) connection.getAttribute(serverRunTimes[i], "State"));

                runTimeInfo.getServerRunTimeInfos().add(serverRunTimeInfo);
            }

            for (int i = 0; i &lt; serverRunTimes.length; i++) {
                ObjectName[] applicationRuntimes = (ObjectName[]) connection.getAttribute(serverRunTimes[i], "ApplicationRuntimes");
                runTimeInfo.setApplicationRunTimeInfos(new ArrayList<ApplicationRunTimeInfo>());

                for (int j = 0; j &lt; applicationRuntimes.length; j++) {
                    ApplicationRunTimeInfo applicationRunTimeInfo = new ApplicationRunTimeInfo();
                    applicationRunTimeInfo.setApplicationName((String) connection.getAttribute(applicationRuntimes[j], "Name"));

                    ObjectName[] componentRuntimes = (ObjectName[]) connection.getAttribute(applicationRuntimes[j], "ComponentRuntimes");

                    applicationRunTimeInfo.setComponentRunTimeInfos(new ArrayList<ComponentRunTimeInfo>());

                    for (int k = 0; k &lt; componentRuntimes.length; k++) {
                        if (connection.getAttribute(componentRuntimes[k], "Type").equals("WebAppComponentRuntime")) {
                            ComponentRunTimeInfo componentRunTimeInfo = new ComponentRunTimeInfo();
                            componentRunTimeInfo.setComponentName((String) connection.getAttribute(componentRuntimes[k], "Name"));
                            componentRunTimeInfo.setSessionsCurrent((Integer) connection.getAttribute(componentRuntimes[k], "OpenSessionsCurrentCount"));
                            componentRunTimeInfo.setSessionsHigh((Integer) connection.getAttribute(componentRuntimes[k], "OpenSessionsHighCount"));

                            applicationRunTimeInfo.getComponentRunTimeInfos().add(componentRunTimeInfo);
                        }
                    }
                    runTimeInfo.getApplicationRunTimeInfos().add(applicationRunTimeInfo);
                }
            }

            closeJmxConnector();
        } catch (MalformedObjectNameException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ReflectionException e) {
            e.printStackTrace();
        } catch (InstanceNotFoundException e) {
            e.printStackTrace();
        } catch (AttributeNotFoundException e) {
            e.printStackTrace();
        } catch (MBeanException e) {
            e.printStackTrace();
        }

        return runTimeInfo;
    }

    private MBeanServerConnection getMBeanServerConnection() throws IOException {
        return getJmxConnector().getMBeanServerConnection();
    }

    private JMXConnector getJmxConnector() throws IOException {
        JMXServiceURL serviceURL = new JMXServiceURL(PROTOCOL, HOSTNAME, PORT, JNDI_ROOT + MBEAN_SERVER);

        Hashtable hashtable = new Hashtable();
        hashtable.put(Context.SECURITY_PRINCIPAL, USERNAME);
        hashtable.put(Context.SECURITY_CREDENTIALS, PASSWORD);
        hashtable.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, "weblogic.management.remote");

        connector = JMXConnectorFactory.connect(serviceURL, hashtable);
        return connector;
    }

    private void closeJmxConnector() throws IOException {
        connector.close();
    }
}

In the example above, we obtain server run-time information and application run-time information. If the application component is an instance of WebAppComponentRuntime we can access information, such as, the number of current sessions. More information regarding run-time MBeans can be found here. For example to obtain information about the ServerRuntimeMBean: open the tree Runtime MBeans, ServerRuntimeMBean and click Attributes to see the available attributes (such as Name, WeblogicVersion, State etcetera).

The following shows an example of the JMX output:

Server Name: AdminServer, Server Version: WebLogic Server 10.3.5.0 Fri Apr 1 20:20:06 PDT 2011 1398638 , Server State: RUNNING
Application Name: wicketspringhibernate
- Component Name: AdminServer_/wsh, Sessions Current: 1, Sessions High: 1
Application Name: bea_wls9_async_response
- Component Name: AdminServer_/_async, Sessions Current: 0, Sessions High: 0
Application Name: mejb
Application Name: consoleapp
- Component Name: AdminServer_/console, Sessions Current: 1, Sessions High: 2
- Component Name: AdminServer_/consolehelp, Sessions Current: 0, Sessions High: 0
Application Name: bea_wls_internal
- Component Name: AdminServer_/bea_wls_internal, Sessions Current: 0, Sessions High: 0
Application Name: ExampleDataSource

References

[1] Spring Framework Reference Documentation.
[2] Hibernate Reference Documentation.
[3] Apache Wicket.


Fun with Spring

In this post we are going to have a little fun with Spring. We show how dependency injection works, how to provide aspects to beans, see how to integrate Hibernate, how to obtain resources such as data sources, JMS connection factories and JMS queues from JNDI, how to create message-driven pojos, how to use commonj WorkManagers on which the thread model of WebLogic is based such that Spring does not spawn unmanaged threads, and if that was not enough see how to use Spring MVC. How good can it get, I love this stuff, hope you do too!

What is Spring?

Spring is a container/framework for dependence injection and aspect oriented programming (AOP), i.e.,

  • Dependency injection – objects get their dependencies in a passive manner, instead of creating objects by hand.
  • Aspect oriented – cohesive development by separating application logic from system services, such as transactions.
  • Container – contains and manages the lifecycle and configuration of the application objects.
  • Framework – configure and compose complex applications by using simple components.

Dependency injection

In a Spring-based application, the objects live in the Spring container. The container creates the objects, wires the objects, configures the objects and manages the objects lifecycle. Two Spring containers are available:

  • Bean factories (BeanFactory provide the basis for dependency injection.
  • Application contexts (ApplicationContext built upon bean factories by providing application services.

The following gives an example of how bean wiring (creating assocations between application components) works:

<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.xsd">
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>model/entities/Department.hbm.xml</value>
                <value>model/entities/Employee.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
            </props>
        </property>
    </bean>
    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" value="jdbc/exampleDS"/>
        <property name="resourceRef" value="true"/>
    </bean>
</beans>

Note that in the example above properties are referenced by using ${hibernate.dialect}, in order for this to work we need to add the following bean:

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	<property name="locations">
		<list>
			<value>classpath:spring.properties</value>
		</list>
	</property>
</bean>

in which spring.properties is a custom file that is placed in the root of the classpath. This information is provided to Spring by using classpath:. Note that the properties file is nothing more than a basic key/value pair file, i.e.,

hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
hibernate.current_session_context_class=thread
hibernate.show_sql=false
hibernate.format_sql=false

As we are talking about properties. When configuring we sometimes run into the issue that we need to configure a bean that contains a java.util.Date. By default, we can configure bean properties by using basic Java objects, such as Strings. To be able to set properties of type java.util.Date as a String we have to configure the following:

<bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
	<property name="customEditors">
		<map>
			<entry key="java.util.Date" value-ref="customDateEditor"/>
		</map>
	</property>
</bean>
<bean id="customDateEditor" class="org.springframework.beans.propertyeditors.CustomDateEditor">
	<constructor-arg ref="simpleDateFormat"/>
	<constructor-arg value="false"/>
</bean>
<bean id="simpleDateFormat" class="java.text.SimpleDateFormat">
	<constructor-arg value="dd-MM-yyyy"/>
</bean>

Spring now automatically converts a String to a Date object. Note that this is not handled by Spring itself but by a JavaBeans API feature. The interface PropertyEditor provides the possibility to map String values to other types. A handy implementation of the PropertyEditor interface is PropertyEditorSupport that contains two useful methods:

  • getAsText – gets the property value as a String.
  • setAsText(String value) – sets the property value by parsing a given String.

When we try to map a String value to another type, the setAsText method is called to execute the conversion. Spring provides a couple of editors which are based on PropertyEditorSupport, such as CustomDateEditor(DateFormat dateFormat, boolean allowEmpty). This class is used to convert a String to a Date and vice versa. The CustomEditorConfigurer is a so-called BeanFactoryPostProcessor which load editors in the BeanFactory by calling the registerCustomEditor method.

Aspect oriented programming

Systems typically consist of a number of components that are each responsible for a specific piece of functionality. These components usually use some kind of system service, such as transactions. System services are used by multiple components. Now we could duplicate code into each of the components that use system services or we could configure it and Spring handle it. Of course we are going for the latter option as we do not want our code to be obfuscated by code that is not part of the core functionality.

It may help to think of aspects as blankets that cover many components of an application. At its core, an application consists of modules that implement business functionality. With AOP, we can cover the core application with layers of functionality. These layers can be applied declarative throughout the application in a manner without the core application even knowing they exist. This is a powerful concept, as it keeps the transaction (and, for example, security) concerns from littering the application’s core business logic.

Aspects help to modularize cross-cutting concerns. In short, a cross-cutting concern can be described as any functionality that affects multiple points of an application. Security, for example, is a cross-cutting concern in that many methods in an application can have security rules applied to them. A common object-oriented technique for reusing common functionality is to apply inheritance or delegation. But inheritance can lead to a brittle object hierarchy if the same base class is used throughout an application, and delegation can be cumbersome because complicated calls to the delegate object may be required. Aspects offer an alternative to inheritance and delegation that can be cleaner in many circumstances. With AOP, we still define the common functionality in one place, but we can declarative define how and where this functionality is applied without having to modify the class to which we are applying the new feature. Cross-cutting concerns can now be modularized into special objects called aspects. This has two benefits:

  • First, the logic for each concern is now in one place, as opposed to being scattered all over the code base.
  • Second, our service modules are now cleaner since they only contain code for their primary concern (or core functionality) and secondary concerns have been moved to aspects.

Aspects are often described in terms of advice, pointcuts, and joinpoints. Aspects have a purpose – a job that they are meant to do. In AOP terms, the job of an aspect is called advice. Advice defines both the what and the when of an aspect. In addition to describing the job that an aspect will perform, advice addresses the question of when to perform the job. Should it be applied before a method is invoked? After the method is invoked? Both before and after method invocation? Or should it only be applied if a method throws an exception?

An application has opportunities for an advice to be applied. These opportunities are known as joinpoints. A joinpoint is a point in the execution of the application where an aspect can be plugged in. This point could be a method being called, an exception being thrown, or a field being modified. These are the points where the aspect’s code can be inserted into the normal flow of the application to add new behavior.

An aspect does not necessarily advise all joinpoints in an application. Pointcuts help narrow down the joinpoints advised by an aspect. If an advice defines the what and when of aspects then pointcuts define the where. A pointcut definition matches one or more joinpoints at which advice should be woven. Often we specify these pointcuts using explicit class and method names or through regular expressions that define matching class and method name patterns.

An aspect is the merger of advice and pointcuts. Taken together, advice and pointcuts define everything there is to know about an aspect – what it does and where and when it does it. Spring employs AOP to provide enterprise services such as declarative transactions. Transactions allow us to group several operations into a single unit of work that either fully happens or fully does not happen. When writing to a database, we must ensure that the integrity of the data is maintained by performing the updates within a transaction.

A transaction can be described by the acronym ACID. In short, ACID stands for:

  • Atomic – Transactions are made up of one or more activities bundled together as a single unit of work. Atomicity ensures that all the operations in the transaction happen or that none of them happen. If all the activities succeed, the transaction is a success. If any of the activities fail, the entire transaction fails and is rolled back.
  • Consistent – Once a transaction ends, the system is left in a state that is consistent with the business that it models. The data should not be corrupted with respect to reality.
  • Isolated – Transactions should allow multiple users to work with the same data, without each user’s work getting tangled up with the others. Therefore, transactions should be isolated from each other, preventing concurrent reads and writes to the same data from occurring. (Note that isolation typically involves locking rows and/or tables in a database.)
  • Durable – Once the transaction has completed, the results of the transaction should be made permanent so that they will survive any sort of system crash. This typically involves storing the results in a database or some other form of persistent storage.

Spring provides support for both programmatic and declarative transaction management support. While programmatic transaction management affords flexibility in precisely defining transaction boundaries in the code, declarative transactions help to decouple an operation from its transaction rules. Choosing between programmatic and declarative transaction management is largely a decision of fine-grained control versus convenience. Spring does not directly manage transactions. Instead, it comes with a selection of transaction managers that delegate responsibility for transaction management to a platform-specific transaction implementation provided by either JTA or the persistence mechanism. To use a transaction manager, we must declare it in the application context, for example,

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
	<property name="sessionFactory" ref="sessionFactory"/>
</bean>

Spring’s support for declarative transaction management is implemented through Spring’s AOP framework. A Spring transaction is sort of an aspect that ‘wraps’ a method with transactional boundaries. In Spring, declarative transactions are defined with transaction attributes. A transaction attribute is a description of how transaction policies should be applied to a method. To declare transactions we must set five parameters, which govern how transaction policies are administered:

  • Propagation behavior – Defines the boundaries of the transaction with respect to the client and to the method being called.
  • Isolation level – Defines how much a transaction may be impacted by the activities of other concurrent transactions. Realizing that perfect isolation can impact performance and because not all applications will require perfect isolation, sometimes it is desirable to be flexible with regard to transaction isolation.
  • Read-only – If a transaction performs only read operations against the underlying data store, the data store may be able to apply certain optimizations that take advantage of the read-only nature of the transaction. By declaring a transaction as read-only, we give the underlying data store the opportunity to apply those optimizations as it sees fit.
  • Transaction timeout – For an application to perform well, its transactions can not carry on for a long time. Suppose that the transaction becomes unexpectedly long-running. Because transactions may involve locks on the underlying data store, long-running transactions can tie up database resources unnecessarily. Instead of waiting it out, we can declare a transaction to automatically roll back after a certain number of seconds.
  • Rollback rules – A set of rules that define what exceptions prompt a rollback and which ones do not. By default, transactions are rolled back only on runtime exceptions and not on checked exceptions. However, we can declare that a transaction be rolled back on specific checked exceptions as well as runtime exceptions. Likewise, we can declare that a transaction not roll back on specified exceptions, even if those exceptions are runtime exceptions.

Declarative transaction management is accomplished by proxying classes with Spring’s TransactionProxyFactoryBean, for example,

<bean id="departmentDAOTarget" class="model.logic.DepartmentDAOBean">
	<property name="hibernateTemplate" ref="hibernateTemplate"/>
</bean>
<bean id="departmentDAO" parent="transactionTemplate">
	<property name="target" ref="departmentDAOTarget"/>
	<property name="proxyInterfaces" value="model.logic.DepartmentDAO"/>
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
	<property name="transactionManager" ref="transactionManager"/>
	<property name="transactionAttributes">
		<props>
			<prop key="add*">PROPAGATION_REQUIRED</prop>
			<prop key="remove*">PROPAGATION_REQUIRED</prop>
			<prop key="update*">PROPAGATION_REQUIRED</prop>
			<prop key="find*">PROPAGATION_SUPPORTS,readOnly</prop>
		</props>
	</property>
</bean>

The second bean entry (transactionTemplate) is an abstract declaration which is used as a template. From this abstract declaration, we can make any number of beans transactional by using transactionTemplate as the parent declaration of the bean.

The DepartmentDAOBean has no idea that its methods are being called within the context of a transaction. If any object makes calls directly to the DepartmentDAOBean, those calls will not be transactional. Instead, collaborating objects should invoke methods on the proxy that is produced by TransactionProxyFactoryBean. The proxy will ensure that transactional rules are applied and then proxy the call to the real DepartmentDAOBean. Therefore, rather than inject the service directly into those objects that use it, we will inject the DepartmentDAOBean proxy into those objects. This means that the proxy produced by TransactionProxyFactoryBean must pretend to be a DepartmentDAOBean. That is the purpose of the proxyInterfaces property. Here we are telling TransactionProxyFactoryBean to produce a proxy that implements the DepartmentDAO interface. The transactionManager property supplies the appropriate transaction manager bean.

TransactionProxyFactoryBean will use the transaction manager to start, suspend, commit, and roll back transactions based on the transaction attributes defined in the transactionAttributes property of TransactionProxyFactoryBean. The transactionAttributes property declares which methods are to be run within a transaction and what the transaction attributes are to be. This property is given a props collection where the key of each prop is a method name pattern and the value defines the transaction attributes for the method(s) selected. The value of each prop given to the transactionAttributes property is a comma-separated value (Propagation Behavior, Isolation Level – Optional, Is the transaction read only? – Optional, Rollback Rules (-Exception, +Exception) – Optional). Note that a ‘-’ prefix forces a rollback while a ‘+’ prefix specifies commit (this allows commit on unchecked exceptions). In the case of the DepartmentDAOBean, we are declaring that all methods whose name starts with add should be run within a transaction. Methods starting with find support transactions (but do not necessarily require a transaction) and are read-only.

We can also accomplish this by using annotations, the only thing we need to add to the Spring application context is:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
	...
	<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
	<tx:annotation-driven/>
</beans>

Note that the transaction-manager attribute of tx:annotation-driven defaults to transactionManager and needs to be specified when an other name is used. The tx:annotation-driven configuration element tells Spring to examine the beans in the application context and to look for beans that are annotated with Transactional. For every bean that is Transactional, tx:annotation-driven will automatically advise it with transaction advice. The transaction attributes of the advice will be defined by parameters of the Transactional annotation. In our code we can use the following:

package model.logic;

import org.hibernate.criterion.DetachedCriteria;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public abstract class GenericHibernateSpringDAO<T, ID extends Serializable> implements GenericDAO<T, ID> {

    private Class<T> persistentClass;
    private HibernateTemplate hibernateTemplate;

    public GenericHibernateSpringDAO() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            setPersistentClass((Class<T>) parameterizedType.getActualTypeArguments()[0]);
        } else {
            System.out.println("Not an instance of parameterized type: " + type);
        }
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    public void setPersistentClass(Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }

    public HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    public T addEntity(T entity) {
        getHibernateTemplate().save(entity);
        return entity;
    }

    public void removeEntity(ID id) {
        getHibernateTemplate().delete(findEntity(id));
    }

    public void updateEntity(T entity) {
        getHibernateTemplate().update(entity);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public T findEntity(ID id) {
        return (T) getHibernateTemplate().get(getPersistentClass(), id);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public List<T> findEntities() {
        DetachedCriteria detachedCriteria = DetachedCriteria.forClass(getPersistentClass());
        return getHibernateTemplate().findByCriteria(detachedCriteria);
    }
}

Integrating Hibernate

Hibernate (an object/relational mapping framework) defines a technique to map an object oriented data model to a relational data model.

Even though there are many steps to the data access process, we are only actively involved in a couple of those steps. The carrier itself is responsible for driving the process. We are only involved when we need to be; the rest is just ‘taken care of’. This mirrors a powerful design pattern: the Template Method pattern. A template method defines the skeleton of a process. The process itself is fixed; it never changes. At certain points, however, the process delegates its work to a subclass to fill in some implementation-specific details. In software terms, a template method delegates the implementation-specific portions of the process to an interface. Different implementations of this interface define specific implementations of this portion of the process.

This is the same pattern that Spring applies to data access. No matter what technology we are using, certain data access steps are required. For example, we always need to obtain a connection to our data store and clean up resources when we are done. These are the fixed steps in a data access process. But each data access method we write is slightly different. We query for different objects and update the data in different ways. These are the variable steps in the data access process. Spring separates the fixed and variable parts of the data access process into two distinct classes, templates and callbacks:

  • Templates manage the fixed part of the process.
  • Callbacks handle the variable part, such as custom data access code.

Spring’s template classes handle the fixed parts of data access – controlling transactions, managing resources, and handling exceptions. Meanwhile, the specifics of data access as they pertain to the application – creating statements, binding parameters, and marshaling result sets – are handled in the callback implementation. In practice, this makes for an elegant framework because all we have to worry about is the data access logic. Spring comes with several templates to choose from, depending on the persistence platform choice. When using Hibernate then we can use HibernateTemplate as was done in the example above.

Applications that have a persistent state must in one way or another have interaction with the persistence provider, when a certain in-memory state must be persisted to the database (or vice versa). In the case of Hibernate this is the interface Session. Each Session is associated with a persistence context. A persistence context is some sort of cache that tracks object changes in a certain transaction. Note that Spring’s HibernateTemplate provides an abstract layer for the Session interface.

The complete Spring configuration looks as follows:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
    <bean id="departmentDAO" class="model.logic.DepartmentDAOBean">
        <property name="hibernateTemplate" ref="hibernateTemplate"/>
    </bean>
    <bean id="employeeDAO" class="model.logic.EmployeeDAOBean">
        <property name="hibernateTemplate" ref="hibernateTemplate"/>
    </bean>
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>model/entities/Department.hbm.xml</value>
                <value>model/entities/Employee.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
            </props>
        </property>
    </bean>
    <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
        <property name="driverType" value="${jdbc.driverClassName}"/>
        <property name="URL" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:spring.properties</value>
            </list>
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <tx:annotation-driven/>
</beans>

Obtaining beans

Now, that we have the configuration in place we need a way to obtain the configured beans. In Spring, objects are not responsible for finding or creating other objects that they need to do their job. Instead, they are given references to the objects that they collaborate with by the container. The act of creating these associations between application objects is the essence of dependency injection and is commonly referred to as wiring. Spring comes with several container implementations that can be categorized into two distinct types:

  • Bean factories provide basic dependency injection support. A bean factory is an implementation of the Factory design pattern, i.e., it is a class whose responsibility it is to create and dispense beans and is able to create associations between collaborating objects as they are instantiated. A bean factory also takes part in the lifecycle of a bean, such as making calls to initialization and destruction methods, if those methods are defined.
  • Application contexts provide application framework services, such as the ability to resolve textual messages from a properties file and the ability to publish application events to interested event listeners.

Before we can retrieve a bean from the application context, it needs to be created. This can be accomplished by using ClassPathXmlApplicationContext (Loads a context definition from an XML file located in the classpath, treating context definition files as classpath resources). Once we have an ApplicationContext instance we can use the getBean method to retrieve a bean, for example,

package model.utils;

import model.logic.DepartmentDAO;
import model.logic.EmployeeDAO;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Utilities {

    private static ApplicationContext context;

    private Utilities() {
    }

    static {
        context = new ClassPathXmlApplicationContext("spring-config.xml");
    }

    public static DepartmentDAO getDepartmentDAO() {
        return (DepartmentDAO) context.getBean("departmentDAO");
    }

    public static EmployeeDAO getEmployeeDAO() {
        return (EmployeeDAO) context.getBean("employeeDAO");
    }
}

A difference between an application context and a bean factory is how singleton beans are loaded. A bean factory lazily loads all beans, deferring bean creation until the getBean method is called. An application context is a bit smarter and preloads all singleton beans upon context start up. To take advantage of the opportunities offered by Spring to customize bean creation we have to understand the lifecycle:

  1. Instantiate – instantiate the bean.
  2. Populate properties – inject the bean’s properties.
  3. Set bean name – if the bean implements BeanNameAware, the bean’s ID is passed to setBeanName.
  4. Set bean factory – if the bean implements BeanFactoryAware, the bean factory is passed to setBeanFactory. If the bean implements ApplicationContextAware interface, the setApplicationContext method is called.
  5. Postprocess – if there are any BeanPostProcessors, Spring calls the postProcessBeforeInitialization method.
  6. Initialize beans – if the bean implements InitializingBean, the afterPropertiesSet method will be called. If the bean has an init-method declared, the specified method will be called.
  7. Postprocess – if there are any BeanPostProcessors, the postProcessAfterInitialization method will be called.
  8. At this point the bean is ready to be used by the application and will remain in the bean factory until it is no longer needed.
  9. Destroy bean – if the bean implements DisposableBean, the destroy method will be called. If the bean has a destroy-method declared, the specified method will be called.

By default, all Spring beans are singletons, i.e., when the container dispenses a bean it will always hand out the exact same instance of the bean. When declaring a bean in Spring, we have the option of declaring a scope for the bean. For example, to let Spring produce a new bean instance each time one is needed, we must declare the bean’s scope attribute to be prototype, for example

<bean id="cacheStore" class="com.tangosol.coherence.hibernate.HibernateCacheStore" scope="prototype">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

Spring offers a handful scoping options:

  • singleton – scopes the bean definition to a single instance per container (default).
  • prototype – allows a bean to be instantiated any number of times (once per use).
  • request – scopes a bean definition to an HTTP request. Only valid when used with a web capable Spring context (such as with Spring MVC).
  • session – scopes a bean definition to an HTTP session. Only valid when used with a web capable Spring context (such as with Spring MVC).
  • global-session – scopes a bean definition to a global HTTP session. Only valid when used in a portlet context.

Usually, the scope will be singleton, sometimes the prototype scope is useful, for example, when other programs need to manage the life cycle.

Obtain objects from JNDI

In distributed applications some components need to access resources, such as, database servers and messaging systems. Resources are typically identified by a unique name – a JNDI name and are in general configured on an application server. When we need objects that are not configured as beans in Spring but in JNDI we can use JndiObjectFactoryBean.

When the application is deployed on the server where the resources, such as data sources, are configured we can proceed as follows. To create a data source bean we add the following entry:

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiName" value="jdbc/exampleDS"/>
	<property name="resourceRef" value="true"/>
</bean>

In order for this work we need to add the following entry in the web.xml file

<resource-ref>
	<res-ref-name>jdbc/exampleDS</res-ref-name>
	<res-type>javax.sql.DataSource</res-type>
	<res-auth>Container</res-auth>
</resource-ref>

The resource-ref makes the resource known to the (Web) container. The container makes the resources available in the local java:comp/env/ environment.

To access resources remotely, we first have to define a JNDI template, for example,

<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
	<property name="environment">
		<props>
			<prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
			<prop key="java.naming.provider.url">t3://hostname:portnumber</prop>
		</props>
	</property>
</bean>

To create beans of remote resources, we can use the following

<bean id="connectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiTemplate" ref="jndiTemplate"/>
	<property name="jndiName" value="jms/ConnectionFactory"/>
</bean>
<bean id="destination" class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiTemplate" ref="jndiTemplate"/>
	<property name="jndiName" value="jms/Queue"/>
</bean>

Now that we know how to retrieve resources, let us use this in an example that uses messaging.

Messaging

JMS can be divided into two functional areas, i.e., production and consumption of messages. The building blocks of a JMS application constist of:

  • Administrative objects (usually configured in an application server)
    • Connection factories – @Resource(name = "jms/ConnectionFactory", type = ConnectionFactory.class) ConnectionFactory connectionFactory;
    • Destinations (Queues or Topics) – @Resource(name = "jms/CompanyQueue", type = Queue.class) Queue destination;
  • Connections – Connection connection = connectionFactory.createConnection();
  • Sessions – Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
  • Message producers – MessageProducer messageProducer = session.createProducer(destination);
  • Message consumers – MessageConsumer messageConsumer = session.createConsumer(otherDestination);
  • Messages – ObjectMessage message = session.createObjectMessage(); message.setObject(person); messageProducer.send(message);

Just as that Spring offers templates for data access it also provides a JMSTemplate. The JMSTemplate can be used for message production and synchronous message consumption. For asynchronous consumption (such as message-driven beans), a message listener container can be used. To create a JMSTemplate we can use the following:

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
	<property name="connectionFactory" ref="connectionFactory"/>
	<property name="defaultDestination" ref="destination"/>
</bean>

Code that uses a JMSTemplate only needs to implement a callback interface. For example, MessageCreator creates a message using a Session that is provided by the JMSTemplate:

package model.logic;

import model.entities.Department;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

import javax.jms.*;

public class JMSSender {

    private JmsTemplate jmsTemplate;

    public JmsTemplate getJmsTemplate() {
        return jmsTemplate;
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void sendTextMessage() {
        getJmsTemplate().send(new MessageCreator(){
            public Message createMessage(Session session) throws JMSException {
                TextMessage message = session.createTextMessage();
                message.setText("Testing the JMS set-up one-two, one-two test");
                return message;
            }
        });
    }

    public void sendMessage(final Department department) {
        getJmsTemplate().send(new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {
                ObjectMessage message = session.createObjectMessage();
                message.setObject(department);
                return message;
            }
        });
    }
}

To inject the JMSTemplate into the JMSSender we can use the following:

<bean id="sender" class="model.logic.JMSSender">
	<property name="jmsTemplate" ref="jmsTemplate"/>
</bean>

To receive messages asynchronously we can use message listener containers (Spring’s equivalent to message-driven beans). A message listener container is used to receive messages from a queue and drive the MessageListener that is injected into it. The listener container is responsible for all the threading of message consumption and dispatches to the listener for processing. Three message listeners are available:

The following shows an example message listener container configuration:

<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
	<property name="connectionFactory" ref="connectionFactory"/>
	<property name="destination" ref="destination"/>
	<property name="messageListener" ref="receiver"/>
</bean>
<bean id="receiver" class="model.logic.JMSReceiver"/>

in which the JMSReceiver looks as follows:

package model.logic;

import model.entities.Department;
import javax.jms.*;

public class JMSReceiver implements MessageListener {

    public void onMessage(Message message) {
        if (message instanceof TextMessage) {
            TextMessage text = (TextMessage) message;
            try {
                message.acknowledge();
                System.out.println("received the following message: " + text.getText());
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }

        if (message instanceof ObjectMessage) {
            ObjectMessage objectMessage = (ObjectMessage) message;
            try {
                message.acknowledge();
                Department department = (Department) objectMessage.getObject();
                System.out.println("received the following message: " + department);
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }
    }
}

When we choose the run Spring JMS on an application server, for example WebLogic, we run into the fact that the threads spawned by Spring are unmanaged. To make sure the thread are managed we can couple the message listener container to a work manager. First we add the following entry to the web.xml file

<resource-ref>
	<res-ref-name>default</res-ref-name>
	<res-type>commonj.work.WorkManager</res-type>
	<res-auth>Container</res-auth>
	<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>

Now, we are able to create the following TaskExecutor:

<bean id="taskExecutor" class="org.springframework.scheduling.commonj.WorkManagerTaskExecutor">
	<property name="workManagerName" value="java:comp/env/default"/>
	<property name="resourceRef" value="true"/>
</bean>

To inject the TaskExecutor into the message listener container we can use:

<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
	<property name="connectionFactory" ref="connectionFactory"/>
	<property name="destination" ref="destination"/>
	<property name="messageListener" ref="receiver"/>
	<property name="taskExecutor" ref="taskExecutor"/>
</bean>

Spring MVC

Spring MVC is designed around a DispatcherServlet that dispatches requests to handlers. The default handler is a Controller interface. In general, application controllers will subclass controllers such as AbstractController. Basically, the following classes will be involved in processing a request:

  • The DispatcherServlet reads the configuration file (<dispatcher-servlet-name>-servlet.xml and creates a respository with configuration objects.
  • The DispatcherServlet acts as a front controller and delegates requests to other components (Controllers).
  • Based on handler mappings it is determined which controller should process the request. The handler mapping uses the URL to choose the appropriate controller.
  • The Controller processes the request that results in information that should be returned to the requester:
    • The information is made available as a Model.
    • The information is handed to a View (for example, a JSP), such that the Model can be formatted as HTML.
  • The controller packages the Model data and the name of the View into a ModelAndView object and gives this to the DispatcherServlet.
  • The DispatherServlet uses a ViewResolver to determine the View.
  • The DispatherServlet passes the Model data to the View.

Let us just give an example of how to set this up. First, we have to configure the DispatcherServlet in the web.xml file, for example,

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-config.xml</param-value>
   </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/spring/*</url-pattern>
    </servlet-mapping>
</web-app>

Note that based on the servlet-name a certain Spring configuration file is loaded. In this case the name of the configuration file must be spring-servlet.xml. By using the context parameter contextConfigLocation we can control which other configuration files must be loaded from the classpath.

To create a Controller, we subclass the AbstractController. In this case we need to override the handleRequestInternal method, for example,

package userinterface.controllers;

import model.entities.Department;
import model.logic.DepartmentDAO;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

public class DepartmentController extends AbstractController {

    private DepartmentDAO departmentDAO;

    public DepartmentController() {
    }

    public DepartmentDAO getDepartmentDAO() {
        return departmentDAO;
    }

    public void setDepartmentDAO(DepartmentDAO departmentDAO) {
        this.departmentDAO = departmentDAO;
    }

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        List<Department> departments = getDepartmentDAO().findEntities();
        if (departments != null) {
            for (Department department: departments) {
                System.out.println(department);
            }
        }

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("departments", departments);
        modelAndView.setViewName("departments");
        return modelAndView;
    }
}

To inject the DepartmentDAO instance into the Controller we add the following in the spring-servlet.xml file:

<bean name="departmentController" class="userinterface.controllers.DepartmentController">
	<property name="departmentDAO" ref="departmentDAO"/>
</bean>

Note that the departmentDAO bean is configured in the spring-config.xml file.

The HandlerMapping is set-up as follows. By using a handler mapping we map incoming requests to appropriate handlers. When the request comes in, the DispatcherServlet will hand it over to the handler mapping to let it inspect the request and come up with the appropriate HandlerExecutionChain. Then the DispatcherServlet will execute the handler. A very handy handler mapping is the SimpleUrlHandlerMapping, for example,

<bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
	<property name="mappings">
		<props>
			<prop key="/departments.html">departmentController</prop>
		</props>
	</property>
</bean>

This handler mapping routes requests for departments.html to the departmentController.

Next, we need to resolve the view. To this end, Spring provides view resolvers, which enable us to render models in a browser without tying them to a specific view technology. The ViewResolver interface provides a mapping between view names and actual views. When using JSP as the view technology we can use the InternalResourceViewResolver, for example,

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="prefix" value="/"/>
	<property name="suffix" value=".jsp"/>
</bean>

The following shows an example of a view in this case a JSP page (departments.jsp):

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
	<head><title>Departments</title></head>
	<body>
		<h1>Hello</h1>
		<table>
			<c:forEach items="${departments}" var="department">
				<tr>
					<td><c:out value="${department.departmentName}"/></td>
				</tr>
			</c:forEach>
		</table>
	</body>
</html>

Note that the departments variable is set by the departmentController through the returned ModelAndView instance. When we put a request in the form of http://hostname:port/<context-root>/spring/departments.html the following happens:

  • As we configured an url-mapping in the web.xml file, the /spring/* part of the URL makes sure that the DispatcherServlet handles the request.
  • The DispatcherServlet receives a request with the following URL /departments.html.
  • By using the SimpleUrlHandlerMapping the DispatcherServlet determines which controller to use. In this case it finds a mapping to the DepartmentController.
  • The DispatcherServlet passes the request to the DepartmentController.
  • The DepartmentController creates a ModelAndView object, in which
    • The Model contains a List with all the Department objects and maps this to a property named departments.
    • The View contains a logical name, in this case departments.
  • The DispatcherServlet uses the InternalResourceViewResolver to determine which JSP the Model has to render.
  • The DispatcherServlet passes the request to the departments.jsp page.

References

[1] Walls, “Spring in Action”, Manning, 2008. All you ever wanted to know about Spring (also a good reference when you are going for your Spring certification).
[2] The Spring Framework – Reference Documentation.


Implementing Coherence Cache Stores

In this post we look at how to create cache stores that need to ‘communicate’ with a database and JMS. Spring will be used to configure the necessary resources which will be injected into a Hibernate template and a JMS template that will be used by the cache store. We start with a basic set-up. Subsequently, we will discuss how to integrate Spring with Coherence and show an example which uses the HibernateCacheStore that ships with Coherence. After which we show how to use Spring’s HibernateTemplate to create a custom cache store. Finally, we present an example that integrates JMS as well by using Spring’s JMSTemplate.

Basic set-up

We have one entity

package model.entities;

import com.tangosol.io.ExternalizableLite;
import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;
import com.tangosol.util.ExternalizableHelper;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.Serializable;

public class Klant implements PortableObject {

    private Integer klantnummer;
    private String naam;
    private String adres;
    private String stad;
    private String provincie;
    private String postcode;
    private Integer gebied;
    private String telefoonnummer;
    private Integer reputatieNummer;
    private Double kredietlimiet;
    private String commentaar;

    public Klant() {
    }

    public Integer getKlantnummer() {
        return klantnummer;
    }

    public void setKlantnummer(Integer klantnummer) {
        this.klantnummer = klantnummer;
    }

    public String getNaam() {
        return naam;
    }

    public void setNaam(String naam) {
        this.naam = naam;
    }

    public String getAdres() {
        return adres;
    }

    public void setAdres(String adres) {
        this.adres = adres;
    }

    public String getStad() {
        return stad;
    }

    public void setStad(String stad) {
        this.stad = stad;
    }

    public String getProvincie() {
        return provincie;
    }

    public void setProvincie(String provincie) {
        this.provincie = provincie;
    }

    public String getPostcode() {
        return postcode;
    }

    public void setPostcode(String postcode) {
        this.postcode = postcode;
    }

    public Integer getGebied() {
        return gebied;
    }

    public void setGebied(Integer gebied) {
        this.gebied = gebied;
    }

    public String getTelefoonnummer() {
        return telefoonnummer;
    }

    public void setTelefoonnummer(String telefoonnummer) {
        this.telefoonnummer = telefoonnummer;
    }

    public Integer getReputatieNummer() {
        return reputatieNummer;
    }

    public void setReputatieNummer(Integer reputatieNummer) {
        this.reputatieNummer = reputatieNummer;
    }

    public Double getKredietlimiet() {
        return kredietlimiet;
    }

    public void setKredietlimiet(Double kredietlimiet) {
        this.kredietlimiet = kredietlimiet;
    }

    public String getCommentaar() {
        return commentaar;
    }

    public void setCommentaar(String commentaar) {
        this.commentaar = commentaar;
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }

        if (object == null) {
            return false;
        }

        if (!(object instanceof Klant)) {
            return false;
        }

        Klant klant = (Klant) object;
        return klantnummer.equals(klant.getKlantnummer());
    }

    @Override
    public int hashCode() {
        if (klantnummer != null) {
            return 37 * klantnummer.hashCode();
        } else {
            return System.identityHashCode(this);
        }
    }

    @Override
    public String toString() {
        return naam + ", " + adres;
    }

    public void readExternal(PofReader reader) throws IOException {
        setKlantnummer(reader.readInt(0));
        setNaam(reader.readString(1));
        setAdres(reader.readString(2));
        setStad(reader.readString(3));
        setProvincie(reader.readString(4));
        setPostcode(reader.readString(5));
        setGebied(reader.readInt(6));
        setTelefoonnummer(reader.readString(7));
        setReputatieNummer(reader.readInt(8));
        setKredietlimiet(reader.readDouble(9));
        setCommentaar(reader.readString(10));
    }

    public void writeExternal(PofWriter writer) throws IOException {
        if (getKlantnummer() != null) {
            writer.writeInt(0, getKlantnummer());
        }
        if (getNaam() != null) {
            writer.writeString(1, getNaam());
        }
        if (getAdres() != null) {
            writer.writeString(2, getAdres());
        }
        if (getStad() != null) {
            writer.writeString(3, getStad());
        }
        if (getProvincie() != null) {
            writer.writeString(4, getProvincie());
        }
        if (getPostcode() != null) {
            writer.writeString(5, getPostcode());
        }
        if (getGebied() != null) {
            writer.writeInt(6, getGebied());
        }
        if (getTelefoonnummer() != null) {
            writer.writeString(7, getTelefoonnummer());
        }
        if (getReputatieNummer() != null) {
            writer.writeInt(8, getReputatieNummer());
        }
        if (getKredietlimiet() != null) {
            writer.writeDouble(9, getKredietlimiet());
        }
        if (getCommentaar() != null) {
            writer.writeString(10, getCommentaar());
        }
    }

    public void readExternal(DataInput dataInput) throws IOException {
        setKlantnummer(dataInput.readInt());
        setNaam(ExternalizableHelper.readSafeUTF(dataInput));
        setAdres(ExternalizableHelper.readSafeUTF(dataInput));
        setStad(ExternalizableHelper.readSafeUTF(dataInput));
        setProvincie(ExternalizableHelper.readSafeUTF(dataInput));
        setPostcode(ExternalizableHelper.readSafeUTF(dataInput));
        setGebied(dataInput.readInt());
        setTelefoonnummer(ExternalizableHelper.readSafeUTF(dataInput));
        setReputatieNummer(dataInput.readInt());
        setKredietlimiet(dataInput.readDouble());
        setCommentaar(ExternalizableHelper.readSafeUTF(dataInput));
    }

    public void writeExternal(DataOutput dataOutput) throws IOException {
        if (getKlantnummer() != null) {
            dataOutput.writeInt(getKlantnummer());
        }
        if (getNaam() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getNaam());
        }
        if (getAdres() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getAdres());
        }
        if (getStad() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getStad());
        }
        if (getProvincie() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getProvincie());
        }
        if (getPostcode() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getPostcode());
        }
        if (getGebied() != null) {
            dataOutput.writeInt(getGebied());
        }
        if (getTelefoonnummer() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getTelefoonnummer());
        }
        if (getReputatieNummer() != null) {
            dataOutput.writeInt(getReputatieNummer());
        }
        if (getKredietlimiet() != null) {
            dataOutput.writeDouble(getKredietlimiet());
        }
        if (getCommentaar() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getCommentaar());
        }
    }
}

Here, we have implemented PortableObject and ExternalizableLite. We use Hibernate as the persistence framework and have to following mapping for the entity

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="model.entities">
    <class name="Klant" table="CUSTOMER">
        <id name="klantnummer" type="integer" column="CUSTID">
            <generator class="assigned"/>
        </id>
        <property name="naam" type="string" column="NAME"/>
        <property name="adres" type="string" column="ADDRESS"/>
        <property name="stad" type="string" column="CITY"/>
        <property name="provincie" type="string" column="STATE"/>
        <property name="postcode" type="string" column="ZIP"/>
        <property name="gebied" type="integer" column="AREA"/>
        <property name="telefoonnummer" type="string" column="PHONE"/>
        <property name="reputatieNummer" type="integer" column="REPID"/>
        <property name="kredietlimiet" type="double" column="CREDITLIMIT"/>
        <property name="commentaar" type="string" column="COMMENTS"/>
    </class>
</hibernate-mapping>

We perform the basic persistence operations (insert, update, delete and select) for which we define the following interface

package model.logic;

import java.io.Serializable;
import java.util.List;

public interface GenericDAO<T, ID extends Serializable> {
    public void addEntity(ID id, T entity);

    public void removeEntity(ID id);

    public void updateEntity(ID id, T entity);

    public T findEntity(ID id);

    public List<T> findEntities();
}

We will use Coherence with the write-behind pattern for updating data through the cache. Thus the client only communicates with Coherence and we can implement the interface as follows:

package model.logic;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.util.MapTriggerListener;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.filter.EqualsFilter;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public abstract class GenericCoherenceDAO<T, ID extends Serializable> implements GenericDAO<T, ID> {

    private Class<T> persistentClass;
    private NamedCache namedCache;

    public GenericCoherenceDAO() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            setPersistentClass((Class<T>) parameterizedType.getActualTypeArguments()[0]);
        } else {
            System.out.println("Not an instance of parameterized type: " + type);
        }

        setNamedCache(CacheFactory.getCache(getPersistentClass().getName()));
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    public void setPersistentClass(Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }

    public NamedCache getNamedCache() {
        return namedCache;
    }

    public void setNamedCache(NamedCache namedCache) {
        this.namedCache = namedCache;
    }

    public void addEntity(ID id, T entity) {
        getNamedCache().put(id, entity);
    }

    public void removeEntity(ID id) {
        getNamedCache().remove(id);
    }

    public void updateEntity(ID id, T entity) {
        getNamedCache().put(id, entity);
    }

    public T findEntity(ID id) {
        return (T) getNamedCache().get(id);
    }

    public List<T> findEntities() {
        ValueExtractor extractor = new ChainedExtractor("getClass.getName");
        Set keys = getNamedCache().keySet(new EqualsFilter(extractor, getPersistentClass().getName()));
        return new ArrayList<T>(getNamedCache().getAll(keys).values());
    }
}

By using a CacheStore we will tell Coherence how to communicate with certain resources, such as a database and JMS.

Specific entity functionality is implemented in entity specific DAOs, for example,

package model.logic;

import model.entities.Klant;

import java.util.Iterator;
import java.util.Map;

public interface KlantDAO extends GenericDAO<Klant, Integer> {
    public void preload();

    public Iterator<Map.Entry<Integer, Klant>> getKlantData();
}

Which is implemented as follows

package model.logic;

import com.tangosol.net.cache.ContinuousQueryCache;
import com.tangosol.util.Filter;
import com.tangosol.util.QueryHelper;
import model.entities.Klant;
import model.util.events.ContinuousCacheListener;
import model.util.loader.*;

import java.util.Iterator;
import java.util.Map;

public class KlantDAOBean extends GenericCoherenceDAO<Klant, Integer> implements KlantDAO {

    private ContinuousQueryCache continuousQueryCache;

    public KlantDAOBean() {
        Filter filter = QueryHelper.createFilter("kredietlimiet between 4000.0 and 12000.0");
        continuousQueryCache = new ContinuousQueryCache(getNamedCache(), filter, new ContinuousCacheListener());
    }

    public void preload() {
        Source source = new HibernateSource(super.getNamedCache(), super.getPersistentClass());
        Target target = new CoherenceTarget(super.getNamedCache(), super.getPersistentClass(), "klantnummer");
        Loader loader = new Loader(source, target);
        loader.load();
    }

    public Iterator<Map.Entry<Integer, Klant>> getKlantData() {
        return continuousQueryCache.entrySet().iterator();
    }
}

This provides preloading and continuous query functionality for the Klant entity. To test the environment will use the following

package model.test;

import model.entities.Klant;
import model.logic.KlantDAO;
import model.logic.KlantDAOBean;

import java.util.Random;

public class Test {

    private Random generator = new Random();

    public static void main(String[] args) {
        Test test = new Test();

        KlantDAO klantDAO = new KlantDAOBean();

        test.doRandomReadWriteTest(klantDAO);
    }

    private void doRandomReadWriteTest(KlantDAO klantDAO) {
        klantDAO.preload();

        while (true) {
            // generate a random client
            Klant klant = createKlant();
            // insert or update a client - an update is issued when the client already exists
            klantDAO.addEntity(klant.getKlantnummer(), klant);
            if (generator.nextDouble() &lt; 0.001) {
                System.out.println("JACKPOT");
                // remove a client
                klantDAO.removeEntity(generateKlantNummer());
                // get all client data
                klantDAO.findEntities();
                // get data from the continuous query cache
                klantDAO.getKlantData();
            } else {
                // find a client by ID
                klantDAO.findEntity(generateKlantNummer());
            }
        }
    }

    private Klant createKlant() {
        int klantnummer = generateKlantNummer();

        Klant klant = new Klant();
        klant.setKlantnummer(klantnummer);
        klant.setNaam("Person" + klantnummer);
        klant.setAdres("Someware");
        klant.setStad("Else");
        klant.setProvincie("NL");
        klant.setPostcode("1234AB");
        klant.setGebied(1);
        klant.setTelefoonnummer("123-4567");
        klant.setReputatieNummer(1);
        klant.setKredietlimiet(Math.rint(generator.nextDouble() * 5000.0));
        klant.setCommentaar(Long.toString(Math.abs(generator.nextLong()), 36));

        return klant;
    }

    private Integer generateKlantNummer() {
        int klantnummer = generator.nextInt(10000);
        if (klantnummer == 0 || (klantnummer &gt;= 100 && klantnummer &lt;= 109)) {
            return 42;
        } else {
            return klantnummer;
        }
    }
}

Coherence and Spring

Access to Coherence caches and services are through the static factory methods provided by the CacheFactory class. These methods delegate to the ConfigurableCacheFactory interface. An implemention of this interface can be plugged-in by using the tangosol-coherence-override.xml file.

When configuring Coherence, we can provide our own CacheStore implementations. CacheStore implementations usually use some sort of resource, such as a data source. Spring provides the perfect way to configure all kinds of resources and manage the lifecycle of these resources. Thus it would be handy to use Spring constructs, such as transaction managers, data sources, etcetera. The SpringAwareCacheFactory provides, next to the facility to access caches in a Coherence configuration file, the ability to reference beans configured using Spring. The SpringAwareCacheFactory can be obtained here:

package com.tangosol.coherence.spring;

import com.tangosol.net.BackingMapManagerContext;
import com.tangosol.net.DefaultConfigurableCacheFactory;

import com.tangosol.run.xml.SimpleElement;
import com.tangosol.run.xml.XmlElement;
import com.tangosol.run.xml.XmlHelper;

import com.tangosol.util.ClassHelper;

import java.util.Iterator;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * SpringAwareCacheFactory provides a facility to access caches declared in a "cache-config.dtd" compliant
 * configuration file, similar to its super class {@link DefaultConfigurableCacheFactory}.  In addition,
 * this factory provides the ability to reference beans in a Spring application context through the use
 * of a class-scheme element.
 * <p/>
 * This factory can be configured to start its own Spring application context from which to retrieve these
 * beans.  This can be useful for standalone JVMs such as cache servers.  It can also be configured at run
 * time with a preconfigured Spring bean factory.  This can be useful for Coherence applications running
 * in an environment that is itself responsible for starting the Spring bean factory, such as a web container.
 *
 * @see #instantiateAny(CacheInfo, XmlElement, BackingMapManagerContext, ClassLoader)
 */
public class SpringAwareCacheFactory extends DefaultConfigurableCacheFactory implements BeanFactoryAware {

    /**
     * Spring BeanFactory used by this CacheFactory
     */
    private BeanFactory m_beanFactory;

    /**
     * Prefix used in cache configuration "class-name" element to indicate this bean is in Spring.
     */
    private static final String SPRING_BEAN_PREFIX = "spring-bean:";

    /**
     * Construct a default DefaultConfigurableCacheFactory using the default configuration file name.
     */
    public SpringAwareCacheFactory() {
        super();
    }

    /**
     * Construct a SpringAwareCacheFactory using the specified path to a "cache-config.dtd" compliant configuration
     * file or resource.  This will also create a Spring ApplicationContext based on the supplied path to a Spring
     * compliant configuration file or resource.
     *
     * @param sCacheConfig location of a cache configuration
     * @param sAppContext  location of a Spring application context
     */
    public SpringAwareCacheFactory(String sCacheConfig, String sAppContext) {
        super(sCacheConfig);

        azzert(sAppContext != null && sAppContext.length() > 0, "Application context location required");

        m_beanFactory = sCacheConfig.startsWith("file:") ? (BeanFactory) new FileSystemXmlApplicationContext(sAppContext)
                : new ClassPathXmlApplicationContext(sAppContext);

        // register a shutdown hook so the bean factory cleans up upon JVM exit
        ((AbstractApplicationContext) m_beanFactory).registerShutdownHook();
    }

    /**
     * Construct a SpringAwareCacheFactory using the specified path to a "cache-config.dtd" compliant configuration
     * file or resource and the supplied Spring BeanFactory.
     *
     * @param sPath       the configuration resource name or file path
     * @param beanFactory Spring BeanFactory used to load Spring beans
     */
    public SpringAwareCacheFactory(String sPath, BeanFactory beanFactory) {
        super(sPath);

        m_beanFactory = beanFactory;
    }

    /**
     * Create an Object using the "class-scheme" element.
     * <p/>
     * In addition to the functionality provided by the super class, this will retreive an object from the
     * configured Spring BeanFactory for class names that use the following format:
     * <p/>
     * &lt;class-name&gt;spring-bean:sampleCacheStore&lt;/class-name&gt;
     * <p/>
     * Parameters may be passed to these beans through setter injection as well:
     * <p/>
     * &lt;init-params&gt;
     * &lt;init-param&gt;
     * &lt;param-name&gt;setEntityName&lt;/param-name&gt;
     * &lt;param-value&gt;{cache-name}&lt;/param-value&gt;
     * &lt;/init-param&gt;
     * &lt;/init-params&gt;
     * <p/>
     * Note that Coherence will manage the lifecycle of the instantiated Spring bean, therefore any beans that
     * are retrieved using this method should be scoped as a prototype in the Spring configuration file, for example:
     * <p/>
     * &lt;bean id="sampleCacheStore" class="com.company.SampleCacheStore" scope="prototype"/&gt;
     *
     * @param info     the cache info
     * @param xmlClass "class-scheme" element.
     * @param context  BackingMapManagerContext to be used
     * @param loader   the ClassLoader to instantiate necessary classes
     * @return a newly instantiated Object
     * @see DefaultConfigurableCacheFactory#instantiateAny(CacheInfo, XmlElement, BackingMapManagerContext, ClassLoader)
     */
    @Override
    public Object instantiateAny(CacheInfo info, XmlElement xmlClass, BackingMapManagerContext context, ClassLoader loader) {
        if (translateSchemeType(xmlClass.getName()) != SCHEME_CLASS) {
            throw new IllegalArgumentException("Invalid class definition: " + xmlClass);
        }

        String sClass = xmlClass.getSafeElement("class-name").getString();

        if (sClass.startsWith(SPRING_BEAN_PREFIX)) {
            String sBeanName = sClass.substring(SPRING_BEAN_PREFIX.length());

            azzert(sBeanName != null && sBeanName.length() > 0, "Bean name required");

            XmlElement xmlParams = xmlClass.getElement("init-params");
            XmlElement xmlConfig = null;
            if (xmlParams != null) {
                xmlConfig = new SimpleElement("config");
                XmlHelper.transformInitParams(xmlConfig, xmlParams);
            }

            Object oBean = getBeanFactory().getBean(sBeanName);

            if (xmlConfig != null) {
                for (Iterator iter = xmlConfig.getElementList().iterator(); iter.hasNext();) {
                    XmlElement xmlElement = (XmlElement) iter.next();

                    String sMethod = xmlElement.getName();
                    String sParam = xmlElement.getString();
                    try {
                        ClassHelper.invoke(oBean, sMethod, new Object[]{sParam});
                    } catch (Exception e) {
                        ensureRuntimeException(e, "Could not invoke " + sMethod + "(" + sParam + ") on bean " + oBean);
                    }
                }
            }
            return oBean;
        } else {
            return super.instantiateAny(info, xmlClass, context, loader);
        }
    }

    /**
     * Get the Spring BeanFactory used by this CacheFactory.
     *
     * @return the Spring {@link BeanFactory} used by this CacheFactory
     */
    public BeanFactory getBeanFactory() {
        azzert(m_beanFactory != null, "Spring BeanFactory == null");
        return m_beanFactory;
    }

    /**
     * Set the Spring BeanFactory used by this CacheFactory.
     *
     * @param beanFactory the Spring {@link BeanFactory} used by this CacheFactory
     */
    public void setBeanFactory(BeanFactory beanFactory) {
        m_beanFactory = beanFactory;
    }
}

HibernateCacheStore

To set-up the HibernateCacheStore, which ships with the Coherence distribution we proceed as follows. We start with configuring Spring

<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.xsd">
    <bean id="cacheStore" class="com.tangosol.coherence.hibernate.HibernateCacheStore" scope="prototype">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>model/entities/Klant.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.c3p0.min_size">${connectionpool.minimum.size}</prop>
                <prop key="hibernate.c3p0.max_size">${connectionpool.maximum.size}</prop>
                <prop key="hibernate.c3p0.timeout">${connectionpool.timeout}</prop>
                <prop key="hibernate.c3p0.max_statements">${connectionpool.maximum.statements}</prop>
                <prop key="hibernate.c3p0.idle_test_period">${connectionpool.idle.period}</prop>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
            </props>
        </property>
    </bean>
    <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
        <property name="driverType" value="${jdbc.driverClassName}"/>
        <property name="URL" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:spring-config.properties</value>
            </list>
        </property>
    </bean>
</beans>

Here we have configured a dataSource that is injected into a Hibernate sessionFactory, which is used by the cacheStore. Note that the cacheStore bean has protoType as its scope. This forces Spring to produce a new bean instance each time one is needed and lets Coherence manage its life cycle when we refer to it in the Coherence configuration, for example,

<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">
    <defaults>
        <serializer>
            <instance>
                <class-name>com.tangosol.io.pof.ConfigurablePofContext</class-name>
                <init-params>
                    <init-param>
                        <param-type>String</param-type>
                        <param-value>pof-config.xml</param-value>
                    </init-param>
                </init-params>
            </instance>
        </serializer>
    </defaults>
    <caching-scheme-mapping>
        <cache-mapping>
            <cache-name>*</cache-name>
            <scheme-name>read-write-distributed</scheme-name>
            <init-params>
                <init-param>
                    <param-name>back-size-limit</param-name>
                    <param-value>250M</param-value>
                </init-param>
            </init-params>
        </cache-mapping>
    </caching-scheme-mapping>
    <caching-schemes>
        <distributed-scheme>
            <scheme-name>read-write-distributed</scheme-name>
            <service-name>ReadWriteDistributedCache</service-name>
            <thread-count>5</thread-count>
            <backup-count>1</backup-count>
            <backup-count-after-writebehind>0</backup-count-after-writebehind>
            <backing-map-scheme>
                <read-write-backing-map-scheme>
                    <scheme-ref>read-write-backing-map</scheme-ref>
                </read-write-backing-map-scheme>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>
        <read-write-backing-map-scheme>
            <scheme-name>read-write-backing-map</scheme-name>
            <internal-cache-scheme>
                <local-scheme>
                    <scheme-ref>backing-map</scheme-ref>
                </local-scheme>
            </internal-cache-scheme>
			<write-max-batch-size>512</write-max-batch-size>
            <cachestore-scheme>
                <class-scheme>
                    <class-name>spring-bean:cacheStore</class-name>
                    <init-params>
                        <init-param>
                            <param-name>setEntityName</param-name>
                            <param-value>{cache-name}</param-value>
                        </init-param>
                    </init-params>
                </class-scheme>
            </cachestore-scheme>
            <write-delay>{write-delay 10s}</write-delay>
            <write-batch-factor>{write-batch-factor 0.75}</write-batch-factor>
            <write-requeue-threshold>{write-requeue-threshold 512}</write-requeue-threshold>
            <refresh-ahead-factor>{refresh-ahead-factor 0.1}</refresh-ahead-factor>
        </read-write-backing-map-scheme>
        <local-scheme>
            <scheme-name>backing-map</scheme-name>
            <eviction-policy>hybrid</eviction-policy>
            <high-units>{back-size-limit 0}</high-units>
            <unit-calculator>binary</unit-calculator>
            <expiry-delay>{back-expiry-delay 1h}</expiry-delay>
        </local-scheme>
    </caching-schemes>
</cache-config>

Configured Spring beans can be referenced in the Coherence cache configuration by using spring-bean:<bean-id>. In our case this will be spring-bean:cacheStore. By specifying init-param elements, we can specify setters that will be injected into the bean with appropriate values. In this case, we inject the entityName attribute of the HibernateCacheStore with the name of the cache, which will be model.entities.Klant when we use KlantDAO klantDAO = new KlantDAOBean().

Now that we have the Spring and Coherence configuration in place, we need to tell Coherence about them. This can be accomplished by using the tangosol-coherence-override.xml operational override file, i.e.,

<coherence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns="http://xmlns.oracle.com/coherence/coherence-operational-config"
           xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-operational-config coherence-operational-config.xsd">
    <configurable-cache-factory-config>
        <class-name system-property="tangosol.coherence.cachefactory">com.tangosol.coherence.spring.SpringAwareCacheFactory</class-name>
        <init-params>
            <init-param>
                <param-type>java.lang.String</param-type>
                <param-value system-property="tangosol.coherence.cacheconfig">cache-config.xml</param-value>
            </init-param>
            <init-param id="1">
                <param-type>java.lang.String</param-type>
                <param-value system-property="tangosol.coherence.springconfig">spring-config.xml</param-value>
            </init-param>
        </init-params>
    </configurable-cache-factory-config>
</coherence>

This tells Coherence to use com.tangosol.coherence.spring.SpringAwareCacheFactory as the CacheFactory implementation, which is invoked by using the
SpringAwareCacheFactory(String sCacheConfig, String sAppContext)
constructor.

CustomCacheStore

Before we implement our own cache store implementation, we will discuss some of the cache configurations we have made in the set-up described above. To make sure that entries are requeued upon a store operation failure, we have to set the write-requeue-threshold to the maximum number of entries that can exist in the queue. The number of entries sent to the storeAll implementation is controlled by the write-max-batch-size setting which defaults to 128. If storeAll throws an exception, the read-write backing map requeues all entries still present in mapEntries.

The write-batch-factor is used to calculate the soft-ripe time for the entries in the queue. An entry in the queue is considered ripe when it has been in the queue for at least the time configured by the write-behind element. Conceptually, the write-behind thread uses the following logic:

  • The thread waits for a queued entry to become ripe.
  • The thread dequeues all ripe and soft-ripe entries in the queue.
  • The thread writes the entries by using store, if there is one entry, or storeAll, if there are multiple entries.

The store and storeAll implementations must be idempotent, which means that the operation can be invoked again without any side effects. Another thing is that there is no ordering guarantee, thus it is not advised to have store and storeAll implementations that have dependencies, such as referential integrity and validation on the database that is not performed in the application.

When using Spring and Hibernate, we can use the HibernateTemplate, to implement our cache store implementation, for example,

package model.util.readwrite;

import com.tangosol.net.cache.AbstractCacheStore;
import org.springframework.orm.hibernate3.HibernateTemplate;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class CustomCacheStore extends AbstractCacheStore {

    private Integer batchSize = 128;
    private HibernateTemplate hibernateTemplate;
    private String entityName;

    public Integer getBatchSize() {
        return batchSize;
    }

    public void setBatchSize(Integer batchSize) {
        this.batchSize = batchSize;
    }

    public HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    public String getEntityName() {
        return entityName;
    }

    public void setEntityName(String entityName) {
        this.entityName = entityName;
    }

    @Override
    public Object load(Object key) {
        return findEntity(key);
    }

    @Override
    public void store(Object key, Object value) {
        getHibernateTemplate().saveOrUpdate(value);
    }

    @Override
    public void storeAll(Map mapEntries) {
        if (getBatchSize() == 0 || mapEntries.size() &lt; getBatchSize()) {
            storeBatch(mapEntries);
        } else {
            Map batch = new HashMap(getBatchSize());

            while (!mapEntries.isEmpty()) {
                Iterator iterator = mapEntries.entrySet().iterator();
                while (iterator.hasNext() && batch.size() &lt; getBatchSize()) {
                    Map.Entry entry = (Map.Entry) iterator.next();
                    batch.put(entry.getKey(), entry.getValue());
                }
                storeBatch(batch);
                mapEntries.keySet().removeAll(batch.keySet());
                batch.clear();
            }
        }
    }

    @Override
    public void erase(Object key) {
        Object find = findEntity(key);
        if (find != null) {
            getHibernateTemplate().delete(find);
        }
    }

    private void storeBatch(Map batch) {
        getHibernateTemplate().saveOrUpdateAll(batch.values());
    }

    private Object findEntity(Object key) {
        Object object = null;
        if (key instanceof Integer) {
            object = getHibernateTemplate().get(getEntityName(), (Integer) key);
        }
        return object;
    }
}

Note that next to the write-max-batch-size setting, we can chunk the map passed by the write-behind thread by setting the batch-size. Note that smaller batches have a lighter load on the system, i.e., writing large batches to a database requires a larger transaction / rollback log in the database, more memory consumption by the JDBC driver and will also cause Coherence to consume more memory in the storage member. Data in the write-behind thread is stored in binary format and the map passed to storeAll is deserialized lazily, with the result that if a very large map is passed and deserialized, the garbage collector can introduce very long pauses.

An example Spring configuration looks as follows:

<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.xsd">
    <bean id="cacheStore" class="model.util.readwrite.CustomCacheStore" scope="prototype">
        <property name="batchSize" value="256"/>
        <property name="hibernateTemplate" ref="hibernateTemplate"/>
    </bean>
    <bean id="cacheStoreProxy" parent="transactionTemplate">
        <property name="target" ref="cacheStore"/>
        <property name="proxyInterfaces" value="com.tangosol.net.cache.CacheStore"/>
    </bean>
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>model/entities/Klant.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.c3p0.min_size">${connectionpool.minimum.size}</prop>
                <prop key="hibernate.c3p0.max_size">${connectionpool.maximum.size}</prop>
                <prop key="hibernate.c3p0.timeout">${connectionpool.timeout}</prop>
                <prop key="hibernate.c3p0.max_statements">${connectionpool.maximum.statements}</prop>
                <prop key="hibernate.c3p0.idle_test_period">${connectionpool.idle.period}</prop>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
            </props>
        </property>
    </bean>
    <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
        <property name="driverType" value="${jdbc.driverClassName}"/>
        <property name="URL" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:spring-config.properties</value>
            </list>
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="transactionTemplate" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
          abstract="true">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="store*">PROPAGATION_REQUIRED</prop>
                <prop key="erase*">PROPAGATION_REQUIRED</prop>
                <prop key="load*">PROPAGATION_SUPPORTS,readOnly</prop>
            </props>
        </property>
    </bean>
</beans>

To let Spring handle the transactions we have configured a cacheStoreProxy that uses a transactionTemplate and target this proxy to our cache store implementation. A target is an object that is being advised. Note that without AOP (aspect oriented programming) the target must handle its own transaction logic. A proxy is an object that is created after applying an advice to the target object. From a client viewpoint, the target object (pre-AOP) and the proxy object (post-AOP) are the same, i.e., the proxy class poses as the target bean, intercepting advised method calls and forwarding those calls to the target bean.

Integrating JMS

JMS resources are typically configured in an application server. Such resources are identified by using a unique name, a so-called JDNI name. In this case we need to retrieve resources that are not configured as Spring beans, but as ‘JNDI’ resources. We can use Spring’s JndiObjectFactoryBean for this purpose. The JndiObjectFactoryBean provides an abstraction layer to map JNDI objects to a bean.

If we were to retrieve the resources locally, i.e., the application is deployed on the application server where the resources are configured we can proceed as follows. First, we define a reference in, for example, the web.xml file

<resource-ref>
	<res-ref-name>jdbc/exampleDS</res-ref-name>
	<res-type>javax.sql.DataSource</res-type>
	<res-auth>Container</res-auth>
</resource-ref>

Subsequently, the Spring bean can be created as follows:

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiName" value="jdbc/exampleDS"/>
	<property name="resourceRef" value="true"/>
</bean>

When the resources have to be retrieved remotely, as in our case, we have to proceed as follows. First, we configure a JNDI template

<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
	<property name="environment">
		<props>
			<prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
			<prop key="java.naming.provider.url">t3://172.31.0.112:7002,172.31.0.112:7003</prop>
		</props>
	</property>
</bean>

Here, we connect to a WebLogic cluster. In this case we also need the weblogic.jndi.WLInitialContextFactory class in the classpath, which is provided by the wlclient.jar file. As we are planning to use WebLogic JMS, we also need the wljmsclient.jar in the classpath. To obtain the configured resources we can use the following

<bean id="connectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiTemplate" ref="jndiTemplate"/>
	<property name="jndiName" value="jms/ConnectionFactory"/>
</bean>
<bean id="destination" class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiTemplate" ref="jndiTemplate"/>
	<property name="jndiName" value="jms/CompanyQueue"/>
</bean>

The WebLogic JMS configuration is the same as the one presented in the post Managing Failure Conditions in a WebLogic Environment. To use JMS in conjunction with Spring, we can resort to the JMSTemplate. To use JMSTemplate we need to configure it, i.e.,

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
	<property name="connectionFactory" ref="connectionFactory"/>
	<property name="defaultDestination" ref="destination"/>
</bean>

Before, we add JMS to the cache store let us first test the set-up. In order to do this we create a sender

package model.util.jms;

import model.entities.Klant;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

import javax.jms.*;

public class JMSSender {

    private JmsTemplate jmsTemplate;

    public JmsTemplate getJmsTemplate() {
        return jmsTemplate;
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void sendTextMessage() {
        getJmsTemplate().send(new MessageCreator(){
            public Message createMessage(Session session) throws JMSException {
                TextMessage message = session.createTextMessage();
                message.setText("Testing the JMS set-up one-two, one-two test");
                return message;
            }
        });
    }

    public void sendMessage(final Klant klant) {
        getJmsTemplate().send(new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {
                ObjectMessage message = session.createObjectMessage();
                message.setObject(klant);
                return message;
            }
        });
    }
}

and a receiver

package model.util.jms;

import model.entities.Klant;
import javax.jms.*;

public class JMSReceiver implements MessageListener {

    public void onMessage(Message message) {
        if (message instanceof TextMessage) {
            TextMessage text = (TextMessage) message;
            try {
                message.acknowledge();
                System.out.println("received the following message: " + text.getText());
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }

        if (message instanceof ObjectMessage) {
            ObjectMessage objectMessage = (ObjectMessage) message;
            try {
                message.acknowledge();
                Klant person = (Klant) objectMessage.getObject();
                System.out.println("RECEIVED MESSAGE " + person);
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }
    }
}

Note that the receiver looks very much like an equivalent of a message-driven bean, except for the MessageDriven annotation. In order for this to work we need to wire the receiver into a message listener container, i.e.,

<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
	<property name="connectionFactory" ref="connectionFactory"/>
	<property name="destination" ref="destination"/>
	<property name="messageListener" ref="receiver"/>
</bean>
<bean id="sender" class="model.util.jms.JMSSender">
	<property name="jmsTemplate" ref="jmsTemplate"/>
</bean>
<bean id="receiver" class="model.util.jms.JMSReceiver"/>

Here, we also configured beans for the sender and the receiver. The test class looks as follows

package model.util.jms;

import model.entities.Klant;
import model.util.spring.SpringUtil;

import java.util.Random;

public class JMSTest {

    private Random generator = new Random();

    public static void main(String[] args) {
        JMSTest test = new JMSTest();

        JMSSender jmsSender = SpringUtil.getJMSSender();
        jmsSender.sendMessage(test.createKlant());
        System.out.println("done sending message");
    }

    private Klant createKlant() {
        int klantnummer = generateKlantNummer();

        Klant klant = new Klant();
        klant.setKlantnummer(klantnummer);
        klant.setNaam("Person" + klantnummer);
        klant.setAdres("Someware");
        klant.setStad("Else");
        klant.setProvincie("NL");
        klant.setPostcode("1234AB");
        klant.setGebied(1);
        klant.setTelefoonnummer("123-4567");
        klant.setReputatieNummer(1);
        klant.setKredietlimiet(Math.rint(generator.nextDouble() * 5000.0));
        klant.setCommentaar(Long.toString(Math.abs(generator.nextLong()), 36));

        return klant;
    }

    private Integer generateKlantNummer() {
        int klantnummer = generator.nextInt(10000);
        if (klantnummer == 0 || (klantnummer &gt;= 100 && klantnummer &lt;= 109)) {
            return 42;
        } else {
            return klantnummer;
        }
    }
}

When the JMS set-up is succesfully tested, we can continue with integrating JMS into the CustomCacheStore. We alter the CustomCacheStore such that a message is send when a customer is deleted. In this case the CustomCacheStore looks as follows

package model.util.readwrite;

import com.tangosol.net.cache.AbstractCacheStore;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.orm.hibernate3.HibernateTemplate;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class CustomCacheStore extends AbstractCacheStore {

    private Integer batchSize = 128;
    private HibernateTemplate hibernateTemplate;
    private JmsTemplate jmsTemplate;
    private String entityName;

    public Integer getBatchSize() {
        return batchSize;
    }

    public void setBatchSize(Integer batchSize) {
        this.batchSize = batchSize;
    }

    public HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    public JmsTemplate getJmsTemplate() {
        return jmsTemplate;
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public String getEntityName() {
        return entityName;
    }

    public void setEntityName(String entityName) {
        this.entityName = entityName;
    }

    @Override
    public Object load(Object key) {
        return findEntity(key);
    }

    @Override
    public void store(Object key, Object value) {
        getHibernateTemplate().saveOrUpdate(value);
    }

    @Override
    public void storeAll(Map mapEntries) {
        if (getBatchSize() == 0 || mapEntries.size() &lt; getBatchSize()) {
            storeBatch(mapEntries);
        } else {
            Map batch = new HashMap(getBatchSize());

            while (!mapEntries.isEmpty()) {
                Iterator iterator = mapEntries.entrySet().iterator();
                while (iterator.hasNext() && batch.size() &lt; getBatchSize()) {
                    Map.Entry entry = (Map.Entry) iterator.next();
                    batch.put(entry.getKey(), entry.getValue());
                }
                storeBatch(batch);
                mapEntries.keySet().removeAll(batch.keySet());
                batch.clear();
            }
        }
    }

    @Override
    public void erase(Object key) {
        final Object find = findEntity(key);
        if (find != null) {
            getHibernateTemplate().delete(find);

            getJmsTemplate().send(new MessageCreator() {
                public Message createMessage(Session session) throws JMSException {
                    TextMessage message = session.createTextMessage();
                    message.setText("Deleted " + find.toString());
                    return message;
                }
            });
        }
    }

    private void storeBatch(Map batch) {
        getHibernateTemplate().saveOrUpdateAll(batch.values());
    }

    private Object findEntity(Object key) {
        Object object = null;
        if (key instanceof Integer) {
            object = getHibernateTemplate().get(getEntityName(), (Integer) key);
        }
        return object;
    }
}

The complete Spring config file looks as follows:

<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.xsd">
    <bean id="cacheStore" class="model.util.readwrite.CustomCacheStore" scope="prototype">
        <property name="batchSize" value="256"/>
        <property name="hibernateTemplate" ref="hibernateTemplate"/>
        <property name="jmsTemplate" ref="jmsTemplate"/>
    </bean>
    <bean id="cacheStoreProxy" parent="transactionTemplate">
        <property name="target" ref="cacheStore"/>
        <property name="proxyInterfaces" value="com.tangosol.net.cache.CacheStore"/>
    </bean>
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>model/entities/Klant.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.c3p0.min_size">${connectionpool.minimum.size}</prop>
                <prop key="hibernate.c3p0.max_size">${connectionpool.maximum.size}</prop>
                <prop key="hibernate.c3p0.timeout">${connectionpool.timeout}</prop>
                <prop key="hibernate.c3p0.max_statements">${connectionpool.maximum.statements}</prop>
                <prop key="hibernate.c3p0.idle_test_period">${connectionpool.idle.period}</prop>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
            </props>
        </property>
    </bean>
    <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
        <property name="driverType" value="${jdbc.driverClassName}"/>
        <property name="URL" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:spring-config.properties</value>
            </list>
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="transactionTemplate" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
          abstract="true">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="store*">PROPAGATION_REQUIRED</prop>
                <prop key="erase*">PROPAGATION_REQUIRED</prop>
                <prop key="load*">PROPAGATION_SUPPORTS,readOnly</prop>
            </props>
        </property>
    </bean>
    <!-- JMS configuration -->
    <bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
        <property name="environment">
            <props>
                <prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
                <prop key="java.naming.provider.url">t3://172.31.0.112:7002,172.31.0.112:7003</prop>
            </props>
        </property>
    </bean>
    <bean id="connectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="jndiTemplate"/>
        <property name="jndiName" value="jms/ConnectionFactory"/>
    </bean>
    <bean id="destination" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="jndiTemplate"/>
        <property name="jndiName" value="jms/CompanyQueue"/>
    </bean>
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="defaultDestination" ref="destination"/>
    </bean>
    <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="destination"/>
        <property name="messageListener" ref="receiver"/>
    </bean>
    <bean id="sender" class="model.util.jms.JMSSender">
        <property name="jmsTemplate" ref="jmsTemplate"/>
    </bean>
    <bean id="receiver" class="model.util.jms.JMSReceiver"/>
</beans>

Test the set-up by using the Test class presented in the ‘basic set-up’ section.

References

[1] Coherence Integration Guide.


Coherence and Hibernate: Decoupling the Database

There are two ways to integrate Hibernate and Coherence:

  • Coherence can be used as a second level cache provider for Hibernate.
  • Hibernate can be used as a CacheStore provider for Coherence.

The first we have already discussed in an earlier post which can be found here. In that post we used Coherence as a second level cache provider for Hibernate. Coherence caching in this case is fully controlled by Hibernate; thus a good understanding of Hibernate caching mechanisms is required. This scenario is a good fit for applications that:

  • Use Hibernate APIs for the data access management.
  • Have complex object models.
  • Have fine-grained transactional requirements.
  • Have an application server cluster that run the Hibernate application.

If we were to use Hibernate as a CacheStore implementation for Coherence, we typically have the following application characteristics:

  • Use Coherence APIs for the data access management.
  • Have simple object models appropriate for the HibernateCacheStore class which ships with the Coherence distribution.
  • Have simple transactional requirements.
  • Require a very high performance, which can be achieved by the use of the Coherence APIs, such as write behind.

The example in this post sets up an application which uses Hibernate as a CacheStore implementation for Coherence.

Setting-up the Coherence cache

When we use the Coherence API, we first obtain a reference to a NamedCache instance. The NamedCache can then be used in a Map-like way to get data from the cache and to put data into the cache. To use Coherence in an effective way we have to understand what is going on under hood.

Whenever a NamedCache method is invoked, the method call is delegated to the cache service that the NamedCache instance belongs to. The cache service is responsible for distributing the data on cache writes, and for the retrieval of the data on cache reads. The cache service is not responsible for the data storage, this is handled by a backing map.

Cache services

There are two base cache topologies in Coherence:

Replicated – which is implemented by the replicated cache service. When the replicated cache service is used each cache item is replicated to all the nodes in the data grid, i.e., every node has thus the full dataset within a backing map for the cache. Some remarks are in order:

  • Read performance – because all data is local to each node we have zero-latency, i.e., an application can get data at in-memory speed. This makes the replicated cache an excellent choice for read-intensive applications.
  • Write performance – as the replicated cache needs to distribute the data to all nodes and to receive a succeed confirmation, we have an increase of network traffic and latency, which makes the replicated cache poorly suited for write-intensive applications.
  • Data set size – as each node holds all the data, we have to make sure there is enough memory available on each node. Of course the amount of memory cannot be extended indefinitely, i.e., we have to take into account garbage collection pauses. If you have a very large heap size you have to pay the price that the garbage collection time may become too long to be tolerated by users who are waiting for a response. A heap size of 1024M ensures that garbage collection times are short enough to be unnoticeable.
  • Fault tolerance – as there are as many copies of the data available as there are nodes, replicated caches are very resilient to failures. All that a replicted cache service needs to do is to redirect read operations to other nodes and ignore writes to the failed node.

Partitioned – which is implemented by the partitioned cache service. A partitioned cache service partitions the data across all the nodes. Each node is responsible for a subset of the data. The partitioned cache service uses an entry key to determine the node to which the data belongs to. Some remarks are in order:

  • Read performance – as the data is partitioned across the nodes, the likelihood that reads will require one additional network call is high. Objects in a partitioned cache are stored in a serialized binary form, which means that read requests also have to deserialize the object leading to an additional latency.
  • Write performance – basically the write performance is the same as the read performance.
  • Data set size – as each node only stores a small portion (1/numberOfNodes) of the data set, the size is limited by the total amount of space available to all nodes.
  • Fault tolerance – in addition to the backing map, a partioned cache service also manages a so-called backup storage, which is used to store backup copies of the data. Coherence ensures that backup copies are stored in a different cache and if possible on a different server. When a node fails the partitioned cache service will notify all other nodes to promote backup copies of the data for which the failed node was responsible.

You can also get the best of both worlds, i.e., the zero-latency of a replicated cache and the linear scalability of a partitioned cache, by using a near cache. A near cache is has a two-tier caching topology that uses a combination of a local cache in the front tier and a partitioned cache in the back tier. A near cache caches a subset of the data locally to handle read requests directly, without asking the partitioned cache service to handle the request. Cache writes are delegated to the partitioned cache. On a cache write, entries in the front cache are invalidated which leads to extra overhead.

Backing maps

Recall that the backing map is where the cache data is stored. A commonly used backing map is the local cache. The local cache stores all the data on the heap, which means that it provides very fast access speed for both read and writes. The local cache can be size-limited and will automatically prune itself when the limit is reached.

To decouple the database from the application we are going to use the read-write backing map, which enables to use a write-behind pattern. The read-write backing map is a composite backing map implementation and has a single internal cache (usually a local cache). To load data from the database on cache misses a cache loader is used. A cache store also enables the ability to update data in the database if we put data in the cache. The read-write backing map enables a read-through/write-through architecture that puts Coherence as an intermediary between the application and the database and thus decouples the two. The Coherence distribution provides a Hibernate cache loader implemention and a Hibernate cache store implementation which are located in the coherence-hibernate.jar.

Patterns

Read-through – the cache is the only interface the application deals with when reading data. If the requested data is present in the cache it will be returned and we avoid a database hit. If the data is not present, the cache will fetch the data from the database by using a CacheLoader. The CacheLoader interface defines two methods load and loadAll. The load method is responsible for retrieving the value from the database based on a specified key. The loadAll method accepts a collection of keys and returns a corresponding map. An example implementation when using Hibernate could be the following

package datamodel.cache;

import com.tangosol.net.cache.AbstractCacheLoader;
import datamodel.entities.Department;
import datamodel.util.HibernateUtil;
import org.hibernate.Session;

public class DepartmentCacheLoader extends AbstractCacheLoader {
    public DepartmentCacheLoader() {
        // the Hibernate sessionfactory gets loaded when Coherence instantiates this class
		HibernateUtil.getSessionFactory();
    }

    @Override
    public Object load(Object key) {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        Department department = (Department) session.get(Department.class, (Integer)key);
        session.getTransaction().commit();
        return department;
    }
}

Note that the example extends the AbstractCacheLoader class, which provides an implementation of the loadAll method that delegates to load.

Write-through – the read-through pattern can be extended so that writes are possible as well. To enable writes to the database, we must implement the CacheStore interface, which extends the CacheLoader interface. The CacheStore interface defines four methods store, storeAll, erase and eraseAll. An example implementation when using Hibernate could be the following

package datamodel.cache;

import com.tangosol.net.cache.AbstractCacheStore;
import datamodel.entities.Department;
import datamodel.util.HibernateUtil;
import org.hibernate.Session;

public class DepartmentCacheStore extends AbstractCacheStore {
    public DepartmentCacheStore() {
        HibernateUtil.getSessionFactory();
    }

    @Override
    public Object load(Object key) {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        Department department = (Department) session.get(Department.class, (Integer) key);
        session.getTransaction().commit();
        return department;
    }

    @Override
    public void store(java.lang.Object key, java.lang.Object value) {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        Department findDepartment = (Department) session.get(Department.class, (Integer) key);
        if (findDepartment != null) {
            session.getTransaction().rollback();
        } else {
            session.save(value);
            session.getTransaction().commit();
        }
    }

    @Override
    public void erase(java.lang.Object key) {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        Department findDepartment = (Department) session.get(Department.class, (Integer) key);
        if (findDepartment != null) {
            if (findDepartment.getEmployees() != null && !findDepartment.getEmployees().isEmpty()) {
                session.getTransaction().rollback();
            } else {
                session.delete(findDepartment);
                session.getTransaction().commit();
            }
        }
    }
}

Note that the AbstractCacheStore class provides implementations for the bulk operation methods.

Write behind – the asynchronous cousin of write-through, i.e., modified cache entries are asynchronously written to the datasbase after a configurable delay. A scenario to use write-behind is when the transaction volume is so high that the database cannot cope with it. To increase the throughput we use write-behind, which means we will write updates into the cache and allow the database to catch up in the background. Some reasons to use write-behind are:

  • Objects might be updated several times. Instead of writing each update to the database, write behind coalesces them and performs a single database update for all the cache updates.
  • Perform bulk inserts by batching many transactions into a single database call.

Both features allow us the significantly reduce the database load, while at the same time we reduce the latency and improve the throughput.

An example configuration of the read-write backing map could be the following (a complete example of a Coherence cache configuration is given later)

<read-write-backing-map-scheme>
	<internal-cache-scheme>...</internal-cache-scheme>
	<cachestore-scheme>
		<class-scheme>
			<class-name>datamodel.cache.DepartmentCacheStore</class-name>
		</class-scheme>
	</cachestore-scheme>
	<write-delay>10s</write-delay>
</read-write-backing-map-scheme>

To test our CacheStore we can use

package datamodel.test;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import datamodel.entities.Department;

public class TestCache {
    public TestCache() {
    }

    public static void main(String[] args) {
        NamedCache cache = CacheFactory.getCache("departments");

        Department department = (Department) cache.get(10);
        System.out.println(department);

        Department newDepartment = createDepartment();
        cache.put(newDepartment.getDepartmentNumber(), newDepartment);

        try {
            Thread.sleep(15000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        cache.remove(newDepartment.getDepartmentNumber());

        try {
            Thread.sleep(15000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

As you can see we are just using the Coherence API, in order to put data in the cache and get data from the cache. To test the write-behind feature we wait for 15 seconds and than continue with a remove. All the database acces is handled by the DepartmentCacheStore class; the load method is called when the requested data is not in the cache, and when the time is ripe to update data in the database the store and/or erase methods are called. Note that Coherence ships with a ready-to-use HibernateCacheLoader class and a ready-to-use HibernateCacheStore class, which extends the HibernateCacheLoader class. We are going to use the HibernateCacheStore class in the example below.

Cache configuration

In the example we use the following cache configuration

<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
    <caching-scheme-mapping>
        <cache-mapping>
            <cache-name>*</cache-name>
            <scheme-name>hibernate-distributed</scheme-name>
            <init-params>
                <init-param>
                    <param-name>size-limit</param-name>
                    <param-value>250M</param-value>
                </init-param>
                <init-param>
                    <param-name>expiry-delay</param-name>
                    <param-value>1m</param-value>
                </init-param>
                <init-param>
                    <param-name>write-delay</param-name>
                    <param-value>10s</param-value>
                </init-param>
                <init-param>
                    <param-name>write-batch-factor</param-name>
                    <param-value>.25</param-value>
                </init-param>
                <init-param>
                    <param-name>write-requeue-threshold</param-name>
                    <param-value>128</param-value>
                </init-param>
                <init-param>
                    <param-name>refresh-ahead-factor</param-name>
                    <param-value>.75</param-value>
                </init-param>
            </init-params>
        </cache-mapping>
    </caching-scheme-mapping>
    <caching-schemes>
        <distributed-scheme>
            <scheme-name>hibernate-distributed</scheme-name>
            <service-name>HibernateDistributedCache</service-name>
            <serializer>
                <class-name>com.tangosol.io.pof.ConfigurablePofContext</class-name>
                <init-params>
                    <init-param>
                        <param-type>String</param-type>
                        <param-value>hibernate-pof-config.xml</param-value>
                    </init-param>
                </init-params>
            </serializer>
            <thread-count>5</thread-count>
            <backup-count>1</backup-count>
            <backup-count-after-writebehind>0</backup-count-after-writebehind>
            <backing-map-scheme>
                <read-write-backing-map-scheme>
                    <internal-cache-scheme>
                        <local-scheme>
                            <high-units>{size-limit 0}</high-units>
                            <unit-calculator>binary</unit-calculator>
                            <expiry-delay>{expiry-delay 0}</expiry-delay>
                        </local-scheme>
                    </internal-cache-scheme>
                    <miss-cache-scheme>
                        <local-scheme>
                            <expiry-delay>{expiry-delay 0}</expiry-delay>
                        </local-scheme>
                    </miss-cache-scheme>
                    <cachestore-scheme>
                        <class-scheme>
                            <!-- Use with test.TestCache -->
                            <!--class-name>datamodel.cache.DepartmentCacheStore</class-name-->
                            <!-- Use with test.Test -->
                            <class-name>com.tangosol.coherence.hibernate.HibernateCacheStore</class-name>
                            <init-params>
                                <init-param>
                                    <param-type>String</param-type>
                                    <param-value>{cache-name}</param-value>
                                </init-param>
                            </init-params>
                        </class-scheme>
                    </cachestore-scheme>
                    <write-delay>{write-delay 0}</write-delay>
                    <write-batch-factor>{write-batch-factor 0}</write-batch-factor>
                    <write-requeue-threshold>{write-requeue-threshold 0}</write-requeue-threshold>
                    <refresh-ahead-factor>{refresh-ahead-factor 0}</refresh-ahead-factor>
                </read-write-backing-map-scheme>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>
    </caching-schemes>
</cache-config>

In the following we will explain the configuration. Some first points to note are:

  • The internal-cache-scheme element indicates the data structure that will used for managing the data – usually this will be a local-scheme.
  • To configure a CacheLoader or a CacheStore implementation, we use the class-scheme element of cachestore-scheme.
  • The {cache-name} configuration macro parameter is used to pass the cache name into the CacheStore implementation. Note that {cache-name} is a built-in macro parameter that is handled automatically by Coherence.
  • When the backing map for the cache is instantiated a HibernateCacheStore instance will be created by Coherence. We instantiate the HibernateCacheStore by using a single parameter in this case the name of the cache. The Hibernate configuration file (hibernate.cfg.xml) will automatically be picked up in the root of the classpath.

Coherence caches can be configured to expire after a specified amount of time to limit stale data in the cache. A drawback can be that some requests for cache entries can have higher response times, as some requests require a read-through to the database. By adding a refresh-ahead-factor to the read-write backing map, we enable it to fetch items from the database in a background thread before these items expire from the cache. As we have set the refresh-ahead-factor to 0.75 we ensure that requests for entries that have been in the cache between 45 and 60 seconds will trigger an automatic refresh by invoking the load method of the HibernateCacheStore class. Note that the load method is implemented by the HibernateCacheLoader class.

We also configured the local cache to have a high-units element. This ensures that we do not run out-of-memory. We have also configured a thread pool for the partitioned cache service. The service thread is the preservation of life of the partitioned cache, i.e., it should be kept as free as possible so it can service requests from other nodes. The thread-count is set in this case to be equal to the number of connections in the database connection pool. Note that the database connection pool is configured using Hibernate’s c3p0. We will present the Hibernate configuration file later.

It is also desirable, when using read-through, to configure a miss-cache-scheme. This will be used to register cache misses. The cache miss information can be used to avoid a read-through to the HibernateCacheLoader, if the likelihood of the entry existing in the cache immediately after a cache miss is high.

To configure write behind, modify the write-behind setting to a value higher than zero. Upon insertion an entry will be queued until it is time to write it to the CacheStore, which is done by a dedicated write-behind thread. To set a threshold on the write-behind time we can use the write-batch-factor element and set this to a value between 0.0 and 1.0.

If a store operation fails, the entry will not be requeued by default. Requeuing can be enabled by setting the write-requeue-threshold to the maximum number of expected entries that will exist in the queue when it is time to write the data to the database. Two remarks are in order. To handle failures correctly a store implementation must be idempotent, which means that the same result is obtained if the method is called once as if the method is called multiple times. Another important thing to note is that there are no guarantees with respect to database writes ordening, which means we have to be careful with referential integrity in the database and that data validation must happen on the application tier.

By default one backup copy of each entry will be made. If we were just using the read-through pattern we could disable the backups by setting the backup-count element to zero. If something goes wrong we can always retrieve the value from the database. As we are using the write-behind pattern disabling backup is not a good idea, as this can result in data loss if a node fails while entries are in the write-behind queue. Once the entries are written to the database we can remove the backup copy. To enable this feature we set the backup-count-after-writebehind element to zero.

Now that we have our configuration in order, let us see how to implement our data access layer.

Implementating the data access layer

We start with creating our domain objects. An important choice to make is how they will be serialized. Coherence works fine with objects that implement java.io.Serializable. However, this is also the slowest way to serialize your objects as it puts a lot of overhead into the object’s serialized binary form. Serialization performance will impact operations against a Coherence cache. Note that the size of the binary form will not only affect network throughput it will also determine how much data we can store in the cache. Coherence comes with several serialization mechanisms and the one that we are going to use is the Portable Object Format or POF for short. POF is a compact binary serialization format. It can be used to serialize objects into a POF value. A POF value is a binary structure containing a type identifier and a value. Let us see how to create a domain object which can be POF-serialized.

To assign POF identifiers to user types a POF context must be used. There are two implementations: a SimplePofContext and a ConfigurablePofContext. The latter allows us to define user types by using a configuration file, for example,

<!DOCTYPE pof-config SYSTEM "pof-config.dtd">
<pof-config>
    <user-type-list>
        <include>coherence-pof-config.xml</include>
        <user-type>
            <type-id>1001</type-id>
            <class-name>model.entities.Department</class-name>
        </user-type>
        <user-type>
            <type-id>1002</type-id>
            <class-name>model.entities.Employee</class-name>
        </user-type>
    </user-type-list>
    <allow-interfaces>true</allow-interfaces>
    <allow-subclasses>true</allow-subclasses>
</pof-config>

Next, we have to implement the actual serialization code that writes and reads object attributes to and from a POF stream. A straightforward way to accomplish this, is to implement the PortableObject interface, for example,

package datamodel.entities;

import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;

import java.io.IOException;
import java.util.Set;

public class Department implements PortableObject {
    private Integer departmentNumber;
    private String departmentName;
    private String location;
    private Set<Employee> employees;

    public Department() {
    }

    // accessors

    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }

        if (object == null) {
            return false;
        }

        if (!(object instanceof Department)) {
            return false;
        }

        Department department = (Department) object;
        return departmentNumber.equals(department.getDepartmentNumber());
    }

    @Override
    public int hashCode() {
        if (departmentNumber != null) {
            return 31 * departmentNumber.hashCode();
        } else {
            return System.identityHashCode(this);
        }
    }

    public void readExternal(PofReader reader) throws IOException {
        System.out.println("DEPARTMENT DESERIALIZATION");
        departmentNumber = reader.readInt(0);
        departmentName = reader.readString(1);
        location = reader.readString(2);
        employees = (Set<Employee>) reader.readObject(3);
    }

    public void writeExternal(PofWriter writer) throws IOException {
        System.out.println("DEPARTMENT SERIALIZATION");
        if (departmentNumber != null) {
            writer.writeInt(0, departmentNumber);
        }
        if (departmentName != null) {
            writer.writeString(1, departmentName);
        }
        if (location != null) {
            writer.writeString(2, location);
        }
        if (employees != null) {
            writer.writeObject(3, employees);
        }
    }
}

The methods (readExternal and writeExternal) defined by the PortableObject interface return no value and accept a single argument that allows to read the indexed attributes from a POF stream (PofReader), or to write attributes to a POF stream (PofWriter). The implementation of the Employee object goes along the same line.

The object/relational mapping is defined as follows

<hibernate-mapping package="datamodel.entities">
    <class name="Department" table="DEPT">
        <id name="departmentNumber" type="integer" column="DEPTNO">
            <generator class="assigned"/>
        </id>
        <property name="departmentName" type="string" column="DNAME"/>
        <property name="location" type="string" column="LOC"/>
        <set name="employees" inverse="true" lazy="false" sort="unsorted" outer-join="true">
            <key column="DEPTNO"/>
            <one-to-many class="Employee"/>
        </set>
    </class>
</hibernate-mapping>

<hibernate-mapping package="datamodel.entities">
    <class name="Employee" table="EMP">
        <id name="employeeNumber" type="integer" column="EMPNO">
            <generator class="assigned"/>
        </id>
        <property name="employeeName" type="string" column="ENAME"/>
        <property name="job" type="string" column="JOB"/>
        <property name="managerNumber" type="integer" column="MGR"/>
        <property name="hiredate" type="date" column="HIREDATE"/>
        <property name="salary" type="double" column="SAL"/>
        <property name="commission" type="double" column="COMM"/>
        <property name="departmentNumber" type="integer" column="DEPTNO"/>
        <set name="managedEmployees" inverse="true" lazy="false" sort="unsorted" outer-join="true">
            <key column="MGR"/>
            <one-to-many class="Employee"/>
        </set>
    </class>
</hibernate-mapping>

Note that Hibernate entities must use the assigned id-generator if the HibernateCacheStore class is used. Now that we have our domain objects in place we can start with implementing the data access layer. We implement this by using a generic approach, which we also used in this post, i.e., we consider the basic persistence operations that every entity shares and group these in a generic superinterface, for example,

package datamodel.logic;

import java.io.Serializable;
import java.util.List;

public interface GenericDAO<T, ID extends Serializable> {
    public void addEntity(ID id, T entity);

    public void removeEntity(ID id);

    public void updateEntity(ID id, T entity);

    public T findEntity(ID id);

    public List<T> findEntities();
}

When using the Coherence API we can implement the GenericDAO interface as follows

package datamodel.logic;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.util.ClassFilter;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.filter.EqualsFilter;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public abstract class GenericCoherenceDAO<T, ID extends Serializable> implements GenericDAO<T, ID> {

    private Class<T> persistentClass;
    private NamedCache namedCache;

    public GenericCoherenceDAO() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            setPersistentClass((Class<T>) parameterizedType.getActualTypeArguments()[0]);
        } else {
            System.out.println("Not an instance of parameterized type: " + type);
        }

        setNamedCache(CacheFactory.getCache(getPersistentClass().getName()));
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    public void setPersistentClass(Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }

    public NamedCache getNamedCache() {
        return namedCache;
    }

    public void setNamedCache(NamedCache namedCache) {
        this.namedCache = namedCache;
    }

    public void addEntity(ID id, T entity) {
        getNamedCache().put(id, entity);
    }

    public void removeEntity(ID id) {
        getNamedCache().remove(id);
    }

    public void updateEntity(ID id, T entity) {
        getNamedCache().put(id, entity);
    }

    public T findEntity(ID id) {
        return (T) getNamedCache().get(id);
    }

    public List<T> findEntities() {
        // Filter is not serializable: ClassFilter:
        // Set keys = getNamedCache().keySet(new ClassFilter(getPersistentClass()));
        ValueExtractor classChecker = new ChainedExtractor("getClass.getName");
        Set keys = getNamedCache().keySet(new EqualsFilter(classChecker, getPersistentClass().getName()));
        return new ArrayList<T>(getNamedCache().getAll(keys).values());
    }
}

To create a data access object for a particular entity, for example Department, we can use the following interface

package datamodel.logic;

import datamodel.entities.Department;

public interface DepartmentDAO extends GenericDAO<Department, Integer> {
}

with the corresponding implementation

package datamodel.logic;

import datamodel.entities.Department;

public class DepartmentDAOBean extends GenericCoherenceDAO<Department, Integer> implements DepartmentDAO {
    public DepartmentDAOBean() {
    }
}

To test our example we can use

package datamodel.test;

import datamodel.entities.Department;
import datamodel.entities.Employee;
import datamodel.logic.DepartmentDAO;
import datamodel.logic.DepartmentDAOBean;
import datamodel.logic.EmployeeDAO;
import datamodel.logic.EmployeeDAOBean;

import java.util.GregorianCalendar;
import java.util.List;
import java.util.Random;

public class Test {
    public Test() {
    }

    public static void main(String[] args) {
        DepartmentDAO departmentDAO = new DepartmentDAOBean();
        EmployeeDAO employeeDAO = new EmployeeDAOBean();

        //doRandomReadTest(departmentDAO);
        doWriteBehindTest(departmentDAO, employeeDAO);
    }

    private static void doWriteBehindTest(DepartmentDAO departmentDAO, EmployeeDAO employeeDAO) {
        Department newDepartment = createDepartment();
        departmentDAO.addEntity(newDepartment.getDepartmentNumber(), newDepartment);

        Department department = departmentDAO.findEntity(10);
        System.out.println("FIND ENTITY " + department);
        for (Employee employee : department.getEmployees()) {
            System.out.println(" - " + employee);
            for (Employee managedEmployee : employee.getManagedEmployees()) {
                System.out.println("   + " + managedEmployee);
            }
        }

        List<Department> departments = departmentDAO.findEntities();
        System.out.println("FIND ENTITIES " + departments);

        newDepartment.setDepartmentName("Else");
        departmentDAO.updateEntity(newDepartment.getDepartmentNumber(), newDepartment);

        Employee newEmployee = createEmployee();
        employeeDAO.addEntity(newEmployee.getEmployeeNumber(), newEmployee);

        System.out.println("WAITING FOR A FEW SECONDS TO TEST WRITE BEHIND");
        try {
            Thread.sleep(15000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("DONE WAITING");

        employeeDAO.removeEntity(2211);

        Employee employee = employeeDAO.findEntity(7934);
        System.out.println("FIND ENTITY " + employee);

        Department otherDepartment = departmentDAO.findEntity(20);
        if (otherDepartment.getEmployees().isEmpty()) {
            departmentDAO.removeEntity(20);
        } else {
            System.out.println("DEPARTMENT STILL HAS EMPLOYEES");
        }

        departmentDAO.removeEntity(12);

        try {
            Thread.sleep(15000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void doRandomReadTest(DepartmentDAO departmentDAO) {
        Integer[] integers = {10, 20, 30, 40};
        Random random = new Random();
        while (true) {
            System.out.println(departmentDAO.findEntity(integers[random.nextInt(4)]));
        }
    }

    private static Department createDepartment() {
        Department department = new Department();
        department.setDepartmentNumber(12);
        department.setDepartmentName("Something");
        department.setLocation("Somewhere");
        return department;
    }

    private static Employee createEmployee() {
        Employee employee = new Employee();
        employee.setEmployeeNumber(2211);
        employee.setDepartmentNumber(20);
        employee.setEmployeeName("Someone");
        employee.setJob("Some");
        employee.setManagerNumber(7100);
        employee.setHiredate(new GregorianCalendar(1980, 11, 11).getTime());
        employee.setSalary(4322.00);
        return employee;
    }
}

Before we can run the test, we need a Hibernate configuration, for example

<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="hibernate.connection.driver_class">oracle.jdbc.OracleDriver</property>
        <property name="hibernate.connection.url">jdbc:oracle:thin:@hostname:1521:databasename</property>
        <property name="hibernate.connection.username">username</property>
        <property name="hibernate.connection.password">password</property>
        <!-- Use the C3P0 connection pool provider -->
        <property name="hibernate.c3p0.min_size">5</property>
        <property name="hibernate.c3p0.max_size">20</property>
        <property name="hibernate.c3p0.timeout">300</property>
        <property name="hibernate.c3p0.max_statements">50</property>
        <property name="hibernate.c3p0.idle_test_period">3000</property>
        <!-- SQL dialect -->
        <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
        <!-- Enable Hibernate's automatic session context management -->
        <property name="hibernate.current_session_context_class">thread</property>
        <!-- Echo all executed SQL to stdout -->
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <!-- Configure SessionFactory -->
        <mapping resource="datamodel/entities/Department.hbm.xml"/>
        <mapping resource="datamodel/entities/Employee.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

Note that we did not configure a second level cache. In our cache configuration, we have configured the HibernateCacheStore to accept a single String which is coupled to the cache name. In this case HibernateCacheStore will automatically pick up the hibernate.cfg.xml file placed in the root of the classpath and create a SessionFactory instance.

If you look at the constructor of the GenericCoherenceDAO class the cache name will be the fully qualified class name of a particular entity. As we have used the wildcard * in the cache-name element, the code snippet CacheFactory.getCache(getPersistentClass().getName()) will obtain a NamedCache instance of the hibernate-distributed scheme. Before this to happen we have to make one further adjustment. The configuration file for the Coherence cache is controlled by the system Java property -Dtangosol.coherence.cacheconfig. Without specifying anything in Java command line, Coherence will attempt to use the cache configuration descriptor named coherence-cache-config.xml, that it finds in the class-path. Since Coherence ships with this file packaged into the coherence.jar, unless you place another file with the same name in the class-path location preceding coherence.jar, that is the one that Coherence will use. So if we want Coherence to use our cache configuration we add the following to the Java command line -Dtangosol.coherence.cacheconfig=hibernate-cache-config.xml.

If we run the test we get the following output. Note that the lines starting with # are extra comments and are not test outputs.

# We add a new department entity to the cache, writeExternal is triggered; the department is serialized to the cache
DEPARTMENT SERIALIZATION
# Find a department entity using findEntity(10), which calls NamedCache.get that triggers the CacheStore to load data from the database
# Hibernate issues some queries to the database to retrieve the data
Hibernate:
    select
        department0_.DEPTNO as DEPTNO0_2_,
        department0_.DNAME as DNAME0_2_,
        department0_.LOC as LOC0_2_,
        employees1_.DEPTNO as DEPTNO4_,
        employees1_.EMPNO as EMPNO4_,
        employees1_.EMPNO as EMPNO1_0_,
        employees1_.ENAME as ENAME1_0_,
        employees1_.JOB as JOB1_0_,
        employees1_.MGR as MGR1_0_,
        employees1_.HIREDATE as HIREDATE1_0_,
        employees1_.SAL as SAL1_0_,
        employees1_.COMM as COMM1_0_,
        employees1_.DEPTNO as DEPTNO1_0_,
        managedemp2_.MGR as MGR5_,
        managedemp2_.EMPNO as EMPNO5_,
        managedemp2_.EMPNO as EMPNO1_1_,
        managedemp2_.ENAME as ENAME1_1_,
        managedemp2_.JOB as JOB1_1_,
        managedemp2_.MGR as MGR1_1_,
        managedemp2_.HIREDATE as HIREDATE1_1_,
        managedemp2_.SAL as SAL1_1_,
        managedemp2_.COMM as COMM1_1_,
        managedemp2_.DEPTNO as DEPTNO1_1_
    from
        DEPT department0_
    left outer join
        EMP employees1_
            on department0_.DEPTNO=employees1_.DEPTNO
    left outer join
        EMP managedemp2_
            on employees1_.EMPNO=managedemp2_.MGR
    where
        department0_.DEPTNO=?
Hibernate:
    select
        managedemp0_.MGR as MGR1_,
        managedemp0_.EMPNO as EMPNO1_,
        managedemp0_.EMPNO as EMPNO1_0_,
        managedemp0_.ENAME as ENAME1_0_,
        managedemp0_.JOB as JOB1_0_,
        managedemp0_.MGR as MGR1_0_,
        managedemp0_.HIREDATE as HIREDATE1_0_,
        managedemp0_.SAL as SAL1_0_,
        managedemp0_.COMM as COMM1_0_,
        managedemp0_.DEPTNO as DEPTNO1_0_
    from
        EMP managedemp0_
    where
        managedemp0_.MGR=?
# The objects retrieved by Hibernate are serialized to the cache, writeExternal is called
DEPARTMENT SERIALIZATION
# As we queried for department number 10, the object is created for our environment so Coherence deserializes the object by calling readExternal
DEPARTMENT DESERIALIZATION
# We print some output
FIND ENTITY ACCOUNTING, NEW YORK: €8750.0
 - MILLER, CLERK
 - CLARK, MANAGER
   + MILLER, CLERK
 - KING, PRESIDENT
   + JONES, MANAGER
   + CLARK, MANAGER
   + BLAKE, MANAGER
# We query for all department objects in the cache by calling findEntities, which calls QueryMap.keySet(ClassFilter)
# Note that cache queries operate on data stored in the cache and will not trigger the CacheStore to load
# missing data from the database. Therefore, applications that query the cache should ensure that the required data
# has been pre-loaded.
DEPARTMENT DESERIALIZATION
DEPARTMENT DESERIALIZATION
DEPARTMENT DESERIALIZATION
DEPARTMENT DESERIALIZATION
# We print some results
FIND ENTITIES [ACCOUNTING, NEW YORK: €8750.0, Something, Somewhere: €0.0]
# Update the new department object and add it to the cache. Note that Coherence coalesces the data so only one
# data manipulation statement will be issued to the database. We also add a new employee to department 20.
DEPARTMENT SERIALIZATION
DEPARTMENT DESERIALIZATION
WAITING FOR A FEW SECONDS TO TEST WRITE BEHIND
# When the time is ripe, Coherence dequeues the new entries queue and writes them to the HibernateCacheStore
DEPARTMENT DESERIALIZATION
# As we are using the assigned generator type in Hibernate, the HibernateCacheStore first checks if the new entity exists.
Hibernate:
    select
        department0_.DEPTNO as DEPTNO0_0_,
        department0_.DNAME as DNAME0_0_,
        department0_.LOC as LOC0_0_
    from
        DEPT department0_
    where
        department0_.DEPTNO=?
# Issue an insert query
Hibernate:
    insert
    into
        DEPT
        (DNAME, LOC, DEPTNO)
    values
        (?, ?, ?)
# First check if the new entity exists
Hibernate:
    select
        employee0_.EMPNO as EMPNO5_0_,
        employee0_.ENAME as ENAME5_0_,
        employee0_.JOB as JOB5_0_,
        employee0_.MGR as MGR5_0_,
        employee0_.HIREDATE as HIREDATE5_0_,
        employee0_.SAL as SAL5_0_,
        employee0_.COMM as COMM5_0_,
        employee0_.DEPTNO as DEPTNO5_0_
    from
        EMP employee0_
    where
        employee0_.EMPNO=?
# Issue an insert query
Hibernate:
    insert
    into
        EMP
        (ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO, EMPNO)
    values
        (?, ?, ?, ?, ?, ?, ?, ?)
DONE WAITING
# Remove the just added employee entity
Hibernate:
    select
        employee_.EMPNO,
        employee_.ENAME as ENAME5_,
        employee_.JOB as JOB5_,
        employee_.MGR as MGR5_,
        employee_.HIREDATE as HIREDATE5_,
        employee_.SAL as SAL5_,
        employee_.COMM as COMM5_,
        employee_.DEPTNO as DEPTNO5_
    from
        EMP employee_
    where
        employee_.EMPNO=?
Hibernate:
    delete
    from
        EMP
    where
        EMPNO=?
# Find an employee entity by using findEntity(7934)
Hibernate:
    select
        employee0_.EMPNO as EMPNO5_1_,
        employee0_.ENAME as ENAME5_1_,
        employee0_.JOB as JOB5_1_,
        employee0_.MGR as MGR5_1_,
        employee0_.HIREDATE as HIREDATE5_1_,
        employee0_.SAL as SAL5_1_,
        employee0_.COMM as COMM5_1_,
        employee0_.DEPTNO as DEPTNO5_1_,
        managedemp1_.MGR as MGR3_,
        managedemp1_.EMPNO as EMPNO3_,
        managedemp1_.EMPNO as EMPNO5_0_,
        managedemp1_.ENAME as ENAME5_0_,
        managedemp1_.JOB as JOB5_0_,
        managedemp1_.MGR as MGR5_0_,
        managedemp1_.HIREDATE as HIREDATE5_0_,
        managedemp1_.SAL as SAL5_0_,
        managedemp1_.COMM as COMM5_0_,
        managedemp1_.DEPTNO as DEPTNO5_0_
    from
        EMP employee0_
    left outer join
        EMP managedemp1_
            on employee0_.EMPNO=managedemp1_.MGR
    where
        employee0_.EMPNO=?
# Print some results
FIND ENTITY MILLER, CLERK
# Find a department entity by using findEntity(20)
Hibernate:
    select
        department0_.DEPTNO as DEPTNO0_2_,
        department0_.DNAME as DNAME0_2_,
        department0_.LOC as LOC0_2_,
        employees1_.DEPTNO as DEPTNO4_,
        employees1_.EMPNO as EMPNO4_,
        employees1_.EMPNO as EMPNO1_0_,
        employees1_.ENAME as ENAME1_0_,
        employees1_.JOB as JOB1_0_,
        employees1_.MGR as MGR1_0_,
        employees1_.HIREDATE as HIREDATE1_0_,
        employees1_.SAL as SAL1_0_,
        employees1_.COMM as COMM1_0_,
        employees1_.DEPTNO as DEPTNO1_0_,
        managedemp2_.MGR as MGR5_,
        managedemp2_.EMPNO as EMPNO5_,
        managedemp2_.EMPNO as EMPNO1_1_,
        managedemp2_.ENAME as ENAME1_1_,
        managedemp2_.JOB as JOB1_1_,
        managedemp2_.MGR as MGR1_1_,
        managedemp2_.HIREDATE as HIREDATE1_1_,
        managedemp2_.SAL as SAL1_1_,
        managedemp2_.COMM as COMM1_1_,
        managedemp2_.DEPTNO as DEPTNO1_1_
    from
        DEPT department0_
    left outer join
        EMP employees1_
            on department0_.DEPTNO=employees1_.DEPTNO
    left outer join
        EMP managedemp2_
            on employees1_.EMPNO=managedemp2_.MGR
    where
        department0_.DEPTNO=?
# The objects are serialized to the Coherence cache
DEPARTMENT SERIALIZATION
# Ask the department object if it has some employees
DEPARTMENT DESERIALIZATION
DEPARTMENT STILL HAS EMPLOYEES
# Remove the newly added department object
Hibernate:
    select
        department_.DEPTNO,
        department_.DNAME as DNAME0_,
        department_.LOC as LOC0_
    from
        DEPT department_
    where
        department_.DEPTNO=?
Hibernate:
    delete
    from
        DEPT
    where
        DEPTNO=?
DEPARTMENT DESERIALIZATION

The output above gives some insight in how Coherence and Hibernate interact when using the read-through/write-behind patterns.

One last remark is in order. We have seen how read/write caching is used when the data source is a database. Coherence supports read/write caching for any data source, for example, web services. This is where the CacheStore comes into play. You can see the CacheStore as an application-specific adapter that is used to connect the cache to an underlying data source. CacheStore implementations access the data source by using a data access mechanism, such as the one we have used in our example.

References

[1] Bauer and King, “Java Persistence with Hibernate”, Manning, 2007. The reference on Java Persistence, Enterprise Java Beans and of course Hibernate.
[2] Seovic, “Oracle Coherence 3.5″, Packt Publishing, 2010. A book that helps you build high-performance applications using Coherence.
[3] Hibernate Reference Information.
[4] Read-Through, Write-Through, Write-Behind and Refresh-Ahead Caching.
[5] The Portable Object Format
[6] Cache Configuration Elements.


Classloading: Making Hibernate work on WebLogic

A good definition of classloading is something like: Locate and convert bytes of a given class to an instances of the Java class. This process is performed by the Java Virtual Machine during start-up, i.e., on runtime using class loaders or sub-classes of java.lang.ClassLoader. Class loaders are coupled in a parent-child way. In the context of the WebLogic server every application gets its own class loader hierarchy; the parent class loader is the system class loader, which is set using the commEnv script located in the directory <middleware-home>/wlserver10.3/common/bin, for example

# set up WebLogic Server's class path
WEBLOGIC_CLASSPATH="${JAVA_HOME}/lib/tools.jar${CLASSPATHSEP}${BEA_HOME}/utils/config/10.3.1.0/config-launch.jar${CLASSPATHSEP}${WL_HOME}/server/lib/weblogic_sp.jar${CLASSPATHSEP}${WL_HOME}/server/lib/weblogic.jar${CLASSPATHSEP}${FEATURES_DIR}/weblogic.server.modules_10.3.1.0.jar${CLASSPATHSEP}${WL_HOME}/server/lib/webservices.jar${CLASSPATHSEP}${ANT_HOME}/lib/ant-all.jar${CLASSPATHSEP}${ANT_CONTRIB}/lib/ant-contrib.jar"
export WEBLOGIC_CLASSPATH

Classloading is performed by using a so-called delegation model. A delegation model is the class loader diagram passing load requests to one another, i.e., class loaders asks their parent class loader to load a class, before they load the class themselves. If a class is present in both the parent and the child class loader, the version in the parent is loaded. This prevents multiple loaded copies of the same form (multiple copies can lead to a ClassCastException). Note that loaded classes are identified by their package plus class name and class loader which loaded the class.

Classes are found as follows:

  • The Java launcher, java, starts the JVM.
  • The JVM searches and loads classes as follows:
    • Bootstrap classes – Classes of the Java platform (JAR files in the directory jre/lib, such as rt.jar). Specified in the System property sun.boot.class.path.
    • Extension classes – Classes bundled in a JAR file located in the extension directory (jre/lib/ext). Specified in de System property java.ext.dirs.
    • User classes – The location of these classes is specified by the -classpath commandline option or the environment variable CLASSPATH. Specified in the System property java.class.path.

Now that we have a basic understanding of how class loading works, let us take a look at WebLogic. In WebLogic, class loading is based on the concept of an application, i.e., every application obtains its own class loader hierarchy; the parent being the system class loader. In this case application code only sees classes loaded by the class loader associated with the application (or module) and by the parent class loader. This concept allows us to run multiple applications within the same JVM.

During deployment (of an enterprise application) the following class loader hierarchy is created:

  • A root class loader for the EJB JAR file.
  • A child class loader for the WAR file.

Deploying the application as an EAR file thus produces a class loader coupling, which gives an optimal hierarchy for most applications. Note that the class loader coupling enables a call-by-reference semantic while calling EJBs. Separate deployment (of the EJB-JAR and WAR files) creates separate class loaders. In this case the EJB interfaces must be included in the WAR file (RMI stub and skeleton classes are calling EJBs which leads to a call-by-value semantic).

The delegation model is all very nice, but if we where to deploy an application, which uses for example Hibernate3.2.6 we run into some trouble. To be more precise Hibernate uses its own implementation of antlr. In the system classpath of WebLogic another version of antlr is already loaded. In this case we have a problem, i.e.,

org.hibernate.HibernateException: Errors in named queries: Film.findAll, Persoon.findPersoonBySofinummer, Film.findFilmsForPersoon, Persoon.findAll
	at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:365)
	at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1300)
	at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:691)
	at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:127)

This exception is thrown because the queries cannot be parsed correctly. To parse queries the library antlr is used, thus we have to make sure the Hibernate version is loaded before the system class loader. WebLogic offers a class loading framework, which enables us to override the basic delegation model. For example, class loaders associated with a Web application can be configured to locate local classes first. To enable this we have to set the prefer-web-inf-classes to true in the deployment override weblogic.xml, for example

<container-descriptor>
	<prefer-web-inf-classes>true</prefer-web-inf-classes>
</container-descriptor>

It is also possible to create custom a class loader hierarchy. This can be configured in the deployment override weblogic-application.xml, for example

<weblogic-application ...>
    <classloader-structure>
        <module-ref>
            <module-uri>Model.jar</module-uri>
        </module-ref>
        <module-ref>
            <module-uri>Userinterface.war</module-uri>
        </module-ref>
    </classloader-structure>
</weblogic-application>

Note that the above configuration is the default class loader structure we would get if we deployed an enterprise application. Some limitations of a custom class loader hierarchy are:

  • Reloading of servlets is not possible.
  • A maximum of three levels (inclusive the application class loader).
  • Only EJB and Web modules.
  • EJB interfaces must be added to the Web module, if these are not visibible.
  • Call-by-value semantic – It is possible to configure the class loader hierarchy so that modules are located in different branches, in this case call-by-value semantic is used. The default class loader hierarchy allows call-by-reference.

Before we continue a little summary is in order. A jar file in the system classpath is loaded by the system class loader. Applications are loaded using application class loaders, which are childs of the system class loader. Identical jar versions located in the application class loader are not visible to the applications itself (delegation model – a child class loaders first asks their parent (in this case the system class loader) to load the class).

As we have already done for the class loaders associated with a Web application, we can filter the class loading. In the case of a Web application all the jars in the WEB-INF/lib directory are loaded before the jars loaded by the system class loader. With WebLogic we can take this principle a step further such that certain packages are loaded before packages loaded by the system class loader. For this we can use the filtering class loader feature (A mechanism to configure deployment descriptors so that particular packages are loaded by the application instead of the system class loader). The filtering class loader is located between the application class loader and the system class loader. If we want the filtering class loader to filter for the package antlr.*, we add the following to the deployment override weblogic-application.xml

<prefer-application-packages>
	<package-name>antlr.*</package-name>
</prefer-application-packages>

For the filtering class loader to work properly, we package the jar files in the APP-INF/lib directory within the enterprise application, for example

EAR
	APP-INF/lib
		antlr-2.7.6.jar
		hibernate3.jar
		hibernate-annotations-3.2.0.ga.jar
		hibernate-entitymanager-3.2.0.ga.jar
		javassist-3.8.0.GA.jar
		jboss-common-4.0.2.jar
		...
	META-INF
		application.xml
		weblogic-application.xml
	Model.jar
	Userinterface.war

In which Model.jar has the following structure

Model.jar
	META-INF
		ejb-jar.xml
		persistence.xml
		weblogic-ejb-jar.xml
	model (packages containing Java classes)

and Userinterface.war the following structure

Userinterface.war
	WEB-INF
		classes
			userinterface (packages containing Java classes)
		lib
			jsf-api.jar
			jsf-impl.jar
			trinidad-api-1.0.10.jar
			trinidad-impl-1.0.10.jar
		faces-config.xml
		web.xml
		weblogic.xml

As you can see there are a lot of jars packaged in the enterprise application, such as JSF related jars and Hibernate related jars. These jars can be deployed separately as shared libraries. Every refering application can use the shared library as if it was a part of the application itself. Classes contained in the library are added to refering application’s classpath and the deployment descriptors are merged in memory. If we want to create a shared library for Hibernate, we create an ear with a dummy java module, for example

SharedLibraryHibernateEAR
	APP-INF/lib
		antlr-2.7.6.jar
		asm.jar
		asm-attrs.jar
		c3p0-0.9.1.jar
		cglib-2.1.3.jar
		commons-collections-2.1.1.jar
		commons-logging-1.0.4.jar
		dom4j-1.6.1.jar
		ehcache-1.2.3.jar
		hibernate3.jar
		hibernate-annotations-3.2.0.ga.jar
		hibernate-entitymanager-3.2.0.ga.jar
		javassist-3.8.0.GA.jar
		jboss-common-4.0.2.jar
		jta.jar
	META-INF
		application.xml
		MANIFEST.MF
	empty.jar

Note that the dummy java module is necessary in order to be able to deploy an enterprise application. The file application.xml has the following contents

<application ...>
    <module>
        <java>empty.jar</java>
    </module>
	<library-directory>APP-INF/lib</library-directory>
</application>

and the MANIFEST.MF file looks like

Manifest-Version: 1.0
Created-By: 1.6.0_05 (BEA Systems, Inc.)
Extension-Name: hibernate
Specification-Title: Hibernate Library
Specification-Version: 3.2
Specification-Vendor: Middleware Magic
Implementation-Title: Hibernate Library
Implementation-Version: 3.2
Implementation-Vendor: Middleware Magic

To package the shared library we can use the jar utility, for example, jar cvfm hibernatesharedlibrary.ear ./META-INF/MANIFEST.MF *. Deploy the ear to the WebLogic server and choose library as deployment type. Next, we have to tell WebLogic to use this library. This can be accomplished by using the deployment overrides weblogic-application.xml if we refer to an ear file or weblogic.xml is we refer to a war file. To refer to the shared library we created above, we add the following to weblogic-application.xml

<library-ref>
	<library-name>hibernate</library-name>
	<specification-version>3.2</specification-version>
	<implementation-version>3.2</implementation-version>
	<exact-match>true</exact-match>
</library-ref>

The same can be accomplished for JSF and Trinidad. For example, in the case of Trinidad we create a war file with the following structure

SharedLibraryTrinidadWAR
	META-INF
		MANIFEST.MF
	WEB-INF
		lib
			trinidad-api-1.0.10.jar
			trinidad-impl-1.0.10.jar
	web.xml

Note that a shared library for jsf-ri is already present in the directory <middleware-home>/wlserver_10.3/common/deployable-libraries. To refer to the shared libraries jsf-ri and trinidad we add the following to weblogic.xml

<library-ref>
	<library-name>jsf-ri</library-name>
	<specification-version>1.1</specification-version>
	<implementation-version>1.1.1</implementation-version>
	<exact-match>true</exact-match>
</library-ref>
<library-ref>
	<library-name>trinidad</library-name>
	<specification-version>1.0.10</specification-version>
	<implementation-version>1.0.10</implementation-version>
	<exact-match>true</exact-match>
</library-ref>

In summary, we have the following class loading features provided by WebLogic:

  • Every application obtains its own class loader hierarchy.
  • Define custom class loader hierarchies.
  • Filtering so that classes of the application are loaded first.
  • Refer to shared libraries so that these become part of the application.

References

[1] Bauer and King, “Java Persistence with Hibernate”, Manning, 2007. The reference on Java Persistence, Enterprise Java Beans and of course Hibernate.
[2] Understanding WebLogic Server Application Classloading.
[3] The Basics of Java Class Loaders.
[4] Making the Most of WebLogic Class Loaders.
[5] Internals of Java Class Loading.


Hibernate and Coherence

A major justification for the claim that applications using an object/relational persistence layer are expected to outperform applications built using direct JDBC is the potential for caching. Although most applications should be designed so that it is possible to achieve acceptable performance without the use of a cache, there is no doubt that for some kinds of applications, especially read-mostly applications or applications that keep significant meta data in the database, caching can have an enormous impact on performance. Furthermore, scaling a highly concurrent application to thousands of online transactions usually requires some caching to reduce the load on the database server(s).

Caching is all about performance optimization, so naturally it is not part of the Java Persistence or EJB 3.0 specification. Every vendor provides different solutions for optimization, in particular any second-level caching. The strategy we present work for a native Hibernate application or an application that depends on Java Persistence interfaces and uses Hibernate as a persistence provider.

A cache keeps a representation of current database state close to the application, either in memory or on disk of the application server machine. The cache is a local copy of the data. The cache sits between your application and the database. The cache may be used to avoid a database hit whenever

  • The application performs a look-up by identifier (primary key).
  • The persistence layer resolves an association or collection lazily.

It is also possible to cache the query results. The performance gain of caching query results is minimal in many cases, so this functionality is used much less often.

Caching is such a fundamental concept in object/relational persistence that you can not understand the performance, scalability, or transactional semantics of an object/relational mapping (ORM) implementation without first knowing what kind of caching strategy (or strategies) it uses. There are three main types of cache:

  • Transaction scope cache – Attached to the current unit of work, which may be a database transaction or a conversation. It is valid and used only as long as the unit of work runs. Every unit of work has its own cache. Data in this cache is not accessed concurrently.
  • Process scope cache – Shared between many (possibly concurrent) units of work or transactions. This means that data in the process scope cache is accessed by concurrently running threads, obviously with implications on transaction isolation.
  • Cluster scope cache – Shared between multiple processes on the same machine or between multiple machines in a cluster. Here, network communication is an important point worth consideration.

A process scope cache may store the persistent instances themselves in the cache, or it may store just their persistent state in a disassembled format. Every unit of work that accesses the shared cache then reassembles a persistent instance from the cached data. A cluster scope cache requires some kind of remote process communication to maintain consistency. Caching information must be replicated to all nodes in the cluster. For many applications, cluster scope caching is of dubious value, because reading and updating the cache may be only marginally faster than going straight to the database.

Any ORM implementation that allows multiple units of work to share the same persistent instances must provide some form of object-level locking to ensure synchronization of concurrent access. Usually this is implemented using read and write locks (held in memory) together with deadlock detection. Implementations like Hibernate that maintain a distinct set of instances for each unit of work avoid these issues to a great extent. Locks held in memory should be avoided, at least for web and enterprise applications where multi-user scalability is an overriding concern. In these applications, it usually is not required to compare object identity across concurrent units of work; each user should be completely isolated from other users.

A transaction/unit of work-scoped cache is preferred if you use unit of work-scoped object identity and if it is the best strategy for highly concurrent multi-user systems. This first-level cache is mandatory, because it also guarantees identical objects. However, this is not the only cache you can use. For some data, a second-level cache scoped to the process (or cluster) that returns data by value can be useful. This scenario therefore has two cache layers. The first layer is implemented by Hibernate for the second layer we are going to use Coherence.

When should we be using a second level cache? Many Java applications share access to the database with other applications. In this case, you should not use any kind of cache beyond a unit of work scoped first-level cache. There is no way for a cache system to know when the other application updated the shared data.

A full ORM solution lets you configure second-level caching separately for each class. Good candidate classes for caching are classes that represent:

  • Data that changes rarely
  • Noncritical data (for example, content-management data)
  • Data that is local to the application and not shared

Bad candidates for second-level caching are

  • Data that is updated often
  • Data that is shared with a legacy application

Also many applications have a number of classes with the following properties:

  • A small number of instances
  • Each instance referenced by many instances of another class or classes
  • Instances that are rarely (or never) updated

This kind of data is sometimes called reference data. Examples of reference data are ZIP codes, reference addresses, static text messages, and so on. Reference data is an excellent candidate for caching with a process or cluster scope, and any application that uses reference data heavily will benefit greatly if that data is cached.

Hibernate has a two-level cache architecture:

  • The first-level cache is the persistence context cache. A Hibernate Session lifespan corresponds to either a single request (usually implemented with one database transaction) or a conversation. This is a mandatory first-level cache that also guarantees the scope of object and database identity.
  • The second-level cache in Hibernate is pluggable and may be scoped to the process or cluster. This is a cache of state (returned by value), not of actual persistent instances. A cache concurrency strategy defines the transaction isolation details for a particular item of data, whereas the cache provider (such as Coherence) represents the physical cache implementation. Use of the second-level cache is optional and can be configured on a per-class and per-collection basis – each such cache utilizes its own physical cache region.

To use Coherence as the second level we have to write an adapter by implementing org.hibernate.cache.CacheProvider. Setting up caching involves two steps:

  • First, you look at the mapping meta-data for your persistent classes and collections and decide which cache concurrency strategy you would like to use for each class and each collection.
  • Secondly, you enable your preferred cache provider in the global Hibernate configuration and customize the provider-specific settings and physical cache regions.

Before we begin let us first see how to Coherence works. We begin with creating some objects which implement the com.tangosol.io.pof.PortableObject interface, for example,

package datamodel.entities;

import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;

import java.io.IOException;

import java.util.Set;

public class Department implements PortableObject {
    private Integer departmentNumber;
    private String departmentName;
    private String location;
    private Set<Employee> employees;
    private Double departmentSalary;

    public Department() {
    }

    public void setDepartmentNumber(Integer departmentNumber) {
        this.departmentNumber = departmentNumber;
    }

    public Integer getDepartmentNumber() {
        return departmentNumber;
    }

    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }

    public String getDepartmentName() {
        return departmentName;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getLocation() {
        return location;
    }

    public void setEmployees(Set<Employee> employees) {
        this.employees = employees;
    }

    public Set<Employee> getEmployees() {
        return employees;
    }

    public Double getDepartmentSalary() {
        departmentSalary = 0.0;
        for (Employee employee : employees) {
            departmentSalary = departmentSalary + employee.getSalary();
        }
        return departmentSalary;
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }

        if (object == null) {
            return false;
        }

        if (getClass() != object.getClass()) {
            return false;
        }

        Department department = (Department) object;
        return departmentNumber.equals(department.getDepartmentNumber());
    }

    @Override
    public int hashCode() {
        if (departmentNumber != null) {
            return 31 * departmentNumber.hashCode();
        } else {
            return System.identityHashCode(this);
        }
    }

    @Override
    public String toString() {
        return departmentName + ", " + location + ": \u20AC" + getDepartmentSalary();
    }

    public void readExternal(PofReader reader) throws IOException {
        departmentNumber = reader.readInt(0);
        departmentName = reader.readString(1);
        location = reader.readString(2);
        employees = (Set<Employee>) reader.readObject(3);

    }

    public void writeExternal(PofWriter writer) throws IOException {
        if (departmentNumber != null) {
            writer.writeInt(0, departmentNumber);
        }
        if (departmentName != null) {
            writer.writeString(1, departmentName);
        }
        if (location != null) {
            writer.writeString(2, location);
        }
        if (employees != null) {
            writer.writeObject(3, employees);
        }
    }
}

The PortableObject interface has two methods readExternal and writeExternal, which tell how Coherence must serialize and de-serialize the object. You could also use Serializable for serialization, but if we are talkng performance here let us get the most of it. Object instances are stored in the cache in a disassembled form. Think of disassembly as a process a bit like serialization (but the algorithm is much, much faster than Java serialization).

As we are using PortableObject as the 'serialization' we have to tell Coherence which objects these are by using the following configuration file:

<?xml version="1.0"?>
<!DOCTYPE pof-config SYSTEM "pof-config.dtd">
<pof-config>
    <user-type-list>
        <include>coherence-pof-config.xml</include>
        <user-type>
            <type-id>1001</type-id>
            <class-name>datamodel.entities.Department</class-name>
        </user-type>
        <user-type>
            <type-id>1002</type-id>
            <class-name>datamodel.entities.Employee</class-name>
        </user-type>
    </user-type-list>
    <allow-interfaces>true</allow-interfaces>
    <allow-subclasses>true</allow-subclasses>
</pof-config>

Next, we have to configure the Coherence cache, for example,

<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
    <caching-scheme-mapping>
        <cache-mapping>
            <cache-name>SomeCache</cache-name>
            <scheme-name>SomePofScheme</scheme-name>
        </cache-mapping>
    </caching-scheme-mapping>
    <caching-schemes>
        <distributed-scheme>
            <scheme-name>SomePofScheme</scheme-name>
            <service-name>PartitionedPofCache</service-name>
            <serializer>
                <class-name>com.tangosol.io.pof.ConfigurablePofContext</class-name>
                <init-params>
                    <init-param>
                        <param-type>String</param-type>
                        <param-value>explore-pof-config.xml</param-value>
                    </init-param>
                </init-params>
            </serializer>
            <backing-map-scheme>
                <local-scheme>
                    <high-units>250M</high-units>
                    <unit-calculator>binary</unit-calculator>
                </local-scheme>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>
    </caching-schemes>
</cache-config>

Now, that we all the configuration in place we can start by using the cache, for example,

package cache;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;

import datamodel.entities.Department;
import datamodel.entities.Employee;

import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Set;

public class Test {
    public Test() {
    }

    public static void main(String[] args) {
        // create cache.
		NamedCache cache = CacheFactory.getCache("SomeCache");

        // add an object to the cache.
		// note that the cache can be seen as some sort of java.util.Map, i.e., having key-value pairs.
		cache.put("department1", createDepartment());

        // get an object from the cacheby using the key.
		Department department = (Department) cache.get("department1");
        System.out.println(department);
    }

    private static Department createDepartment() {
        Department department = new Department();
        department.setDepartmentNumber(12);
        department.setDepartmentName("Something");
        department.setLocation("Somewhere");
        Set<Employee> employees = new HashSet<Employee>();
        employees.add(createEmployee());
        department.setEmployees(employees);
        return department;
    }

    private static Employee createEmployee() {
        Employee employee = new Employee();
        employee.setEmployeeNumber(2211);
        employee.setDepartmentNumber(12);
        employee.setEmployeeName("Someone");
        employee.setJob("Some");
        employee.setManagerNumber(7100);
        employee.setHiredate(new GregorianCalendar(1980, 11, 11).getTime());
        employee.setSalary(4322.00);
        return employee;
    }
}

Before we can run this class, we have to add the following parameter to the JVM -Dtangosol.coherence.cacheconfig=explore-config.xml. This parameter tells Coherence where to look for the cache configurations in the classpath.

Now that we have a basic understand of how Coherence works, let us continue by implementing the org.hibernate.cache.CacheProvider interface, for example,

package cache;

import com.tangosol.net.CacheFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.hibernate.cache.Cache;
import org.hibernate.cache.CacheProvider;

public class CoherenceCacheProvider implements CacheProvider {

    private static Map<String, CoherenceCache> cacheMap = new HashMap<String, CoherenceCache>();

    public CoherenceCacheProvider() {
    }

    /**
     * Configure the cache
     *
     * @param regionName the name of the cache region
     * @param properties configuration settings
     * @return configured cache
     */
    public Cache buildCache(String regionName, Properties properties) {
        System.out.println("CACHEPROVIDER BUILDCACHE " + regionName);
        CoherenceCache cache = new CoherenceCache(CacheFactory.getCache(regionName));
        cacheMap.put(regionName, cache);
        return cache;
    }

    /**
     * Generate a timestamp
     *
     * @return timestamp
     */
    public long nextTimestamp() {
        System.out.println("CACHEPROVIDER NEXTTIMESTAMP");
        return CacheFactory.getCluster().getTimeMillis();
    }

    /**
     * Callback to perform any necessary initialization of the underlying cache implementation during SessionFactory construction.
     *
     * @param properties current configuration settings.
     */
    public void start(Properties properties) {
        System.out.println("CACHEPROVIDER START");
        CacheFactory.ensureCluster();
    }

    /**
     * Callback to perform any necessary cleanup of the underlying cache implementation during SessionFactory.close().
     */
    public void stop() {
        System.out.println("CACHEPROVIDER STOP");
        CacheFactory.shutdown();
    }

    public boolean isMinimalPutsEnabledByDefault() {
        return false;
    }

    /**
     * Helper method used for statistics
     *
     * @return a Set containing keys
     */
    public static Set<String> getCacheNames() {
        return cacheMap.keySet();
    }

    /**
     * Helper method used for statistics
     *
     * @param cacheName
     * @return
     */
    public static CoherenceCache getCacheByName(String cacheName) {
        return cacheMap.get(cacheName);
    }
}

The class CoherenceCache implements org.hibernate.cache.Cache. This class tells Hibernate how to read objects from the cache and put objects into the cache.

package cache;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;

import java.io.Serializable;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.hibernate.cache.Cache;
import org.hibernate.cache.CacheException;

public class CoherenceCache implements Cache {

    private NamedCache cache;
    private CoherenceCacheStatistics statistics;

    public CoherenceCache(NamedCache cache) {
        this.cache = cache;
        this.statistics = new CoherenceCacheStatistics();
    }

    public CoherenceCacheStatistics getStatistics() {
        return statistics;
    }

    /**
     * Get an item from the cache
     *
     * @param key
     * @return the cached object or null
     */
    public Object read(Object key) {
        System.out.println("CACHE READ " + key);
        long start = System.currentTimeMillis();
        Object value = cache.get(key);
        long end = System.currentTimeMillis();
        long timeToGet = end - start;
        statistics.registerGet(timeToGet);
        if (value != null) {
            statistics.registerHit(timeToGet);
        } else {
            statistics.registerMiss(timeToGet);
        }
        return value;
    }

    /**
     * Get an item from the cache, nontransactionally
     *
     * @param key
     * @return the cached object or null
     */
    public Object get(Object key) {
        System.out.println("CACHE GET " + key);
        long start = System.currentTimeMillis();
        Object value = cache.get(key);
        long end = System.currentTimeMillis();
        long timeToGet = end - start;
        statistics.registerGet(timeToGet);
        if (value != null) {
            statistics.registerHit(timeToGet);
        } else {
            statistics.registerMiss(timeToGet);
        }
        return value;
    }

    /**
     * Add an item to the cache, nontransactionally, with failfast semantics
     *
     * @param key
     * @param value
     */
    public void put(Object key, Object value) {
        System.out.println("CACHE PUT " + key + ", " + value);
        long start = System.currentTimeMillis();
        cache.put(key, value);
        long end = System.currentTimeMillis();
        statistics.registerPut(end - start);
    }

    /**
     * Add an item to the cache
     *
     * @param key
     * @param value
     */
    public void update(Object key, Object value) {
        System.out.println("CACHE UPDATE " + key + ", " + value);
        long start = System.currentTimeMillis();
        cache.put(key, value);
        long end = System.currentTimeMillis();
        statistics.registerPut(end - start);
    }

    /**
     * Remove an item from the cache
     *
     * @param key
     */
    public void remove(Object key) {
        System.out.println("CACHE REMOVE " + key);
        cache.remove(key);
    }

    /**
     * Clear the cache
     */
    public void clear() {
        System.out.println("CACHE CLEAR");
        cache.clear();
    }

    /**
     * Clean up
     */
    public void destroy() {
        System.out.println("CACHE DESTROY");
        cache.release();
    }

    /**
     * If this is a clustered cache, lock the item
     *
     * @param key
     */
    public void lock(Object key) {
        System.out.println("CACHE LOCK " + key);
        cache.lock(key);
    }

    /**
     * If this is a clustered cache, unlock the item
     *
     * @param key
     */
    public void unlock(Object key) {
        System.out.println("CACHE UNLOCK " + key);
        cache.unlock(key);
    }

    /**
     * Generate a timestamp
     *
     * @return timestamp
     */
    public long nextTimestamp() {
        System.out.println("CACHE NEXTTIMESTAMP");
        return CacheFactory.getCluster().getTimeMillis();
    }

    /**
     * Get a reasonable "lock timeout"
     *
     * @return reasonable timeout
     */
    public int getTimeout() {
        System.out.println("CACHE GETTIMEOUT");
        return 10000;
    }

    /**
     * Get the name of the cache region
     *
     * @return cache region name
     */
    public String getRegionName() {
        return cache.getCacheName();
    }

    /**
     * The number of bytes this cache region is currently consuming in memory
     *
     * @return The number of bytes consumed by this region; -1 if unknown or unsupported.
     */
    public long getSizeInMemory() {
        return -1;
    }

    /**
     * The count of entries currently contained in the regions in-memory store.
     *
     * @return The count of entries in memory; -1 if unknown or unsupported.
     */
    public long getElementCountInMemory() {
        long size = 0;
        try {
            size = cache.size();
        } catch (CacheException ex) {
            throw new CacheException(ex);
        }
        return size;
    }

    /**
     * The count of entries currently contained in the regions disk store.
     *
     * @return The count of entries on disk; -1 if unknown or unsupported.
     */
    public long getElementCountOnDisk() {
        return -1;
    }

    /**
     * Optional operation
     *
     * @return Map instance of the cache
     */
    public Map toMap() {
        Map result = new HashMap();
        try {
            Iterator iterator = cache.keySet().iterator();
            while (iterator.hasNext()) {
                Object key = iterator.next();
                result.put(key, cache.get((Serializable) key));
            }
        } catch (Exception ex) {
            throw new CacheException(ex);
        }
        return result;
    }
}

As an extra, we have added some statistics in order to measure the cache response. This is done in the class CoherenceCacheStatistics (this is not necessary and could have been left out). Also in the method implementations such as read, for example, the only necessary line of code is: return cache.get(key). An example implementation of com.tangosol.net.cache.CacheStatistics is the following:

package cache;

import com.tangosol.net.cache.CacheStatistics;

public class CoherenceCacheStatistics implements CacheStatistics {

    private long gets;
    private long totalGetMillis;
    private long puts;
    private long totalPutMillis;
    private long hits;
    private long totalHitsMillis;
    private long miss;
    private long totalMissMillis;

    public CoherenceCacheStatistics() {
    }

    public void registerGet(long millisToGet) {
        this.gets++;
        this.totalGetMillis += millisToGet;
    }

    public void registerPut(long millisToPut) {
        this.puts++;
        this.totalPutMillis += millisToPut;
    }

    public void registerHit(long millisToGet) {
        this.hits++;
        this.totalHitsMillis += millisToGet;
    }

    public void registerMiss(long millisToGet) {
        this.miss++;
        this.totalMissMillis += millisToGet;
    }

    /**
     * Determine the total number of get() operations since the cache statistics were last reset.
     *
     * @return the total number of get() operations
     */
    public long getTotalGets() {
        return this.gets;
    }

    /**
     * Determine the total number of milliseconds spent on get() operations since the cache statistics were last reset.
     *
     * @return the total number of milliseconds processing get() operations
     */
    public long getTotalGetsMillis() {
        return this.totalGetMillis;
    }

    /**
     * Determine the average number of milliseconds per get() invocation since the cache statistics were last reset.
     *
     * @return the average number of milliseconds per get() operation
     */
    public double getAverageGetMillis() {
        if (getTotalGets() == 0) {
            return 0.0;
        } else {
            return getTotalGetsMillis() / (1.0 * getTotalGets());
        }
    }

    /**
     * Determine the total number of put() operations since the cache statistics were last reset.
     *
     * @return the total number of put() operations
     */
    public long getTotalPuts() {
        return this.puts;
    }

    /**
     * Determine the total number of milliseconds spent on put() operations since the cache statistics were last reset.
     *
     * @return the total number of milliseconds processing put() operations
     */
    public long getTotalPutsMillis() {
        return this.totalPutMillis;
    }

    /**
     * Determine the average number of milliseconds per put() invocation since the cache statistics were last reset.
     *
     * @return the average number of milliseconds per put() operation
     */
    public double getAveragePutMillis() {
        if (getTotalPuts() == 0) {
            return 0.0;
        } else {
            return getTotalPutsMillis() / (1.0 * getTotalPuts());
        }
    }

    /**
     * Determine the rough number of cache hits since the cache statistics were last reset.
     * A cache hit is a read operation invocation (e.g. get()) for which an entry exists in this map.
     *
     * @return the number of get() calls that have been served by existing cache entries
     */
    public long getCacheHits() {
        return this.hits;
    }

    /**
     * Determine the total number of milliseconds (since that last statistics reset) for the
     * get() operations for which an entry existed in this map.
     *
     * @return the total number of milliseconds for the get() operations that were hits
     */
    public long getCacheHitsMillis() {
        return this.totalHitsMillis;
    }

    /**
     * Determine the average number of milliseconds per get() invocation that is a hit.
     *
     * @return the average number of milliseconds per cache hit
     */
    public double getAverageHitMillis() {
        if (getCacheHits() == 0) {
            return 0.0;
        } else {
            return getCacheHitsMillis() / (1.0 * getCacheHits());
        }
    }

    /**
     * Determine the rough number of cache misses since the cache statistics were last reset.
     * A cache miss is a get() invocation that does not have an entry in this map.
     *
     * @return the number of get() calls that failed to find an existing cache entry because
     *         the requested key was not in the cache
     */
    public long getCacheMisses() {
        return this.miss;
    }

    /**
     * Determine the total number of milliseconds (since that last statistics reset) for
     * the get() operations for which no entry existed in this map.
     *
     * @return the total number of milliseconds (since that last statistics reset)
     *         for the get() operations that were misses
     */
    public long getCacheMissesMillis() {
        return this.totalMissMillis;
    }

    /**
     * Determine the average number of milliseconds per get() invocation that is a miss.
     *
     * @return
     */
    public double getAverageMissMillis() {
        if (getCacheMisses() == 0) {
            return 0.0;
        } else {
            return getCacheMissesMillis() / (1.0 * getCacheMisses());
        }
    }

    /**
     * Determine the rough probability (0 <= p <= 1) that the next invocation will be a hit,
     * based on the statistics collected since the last reset of the cache statistics.
     *
     * @return the cache hit probability (0 <= p <= 1)
     */
    public double getHitProbability() {
        if (getTotalGets() > getTotalPuts()) {
            return (getTotalGets() - getTotalPuts()) / (1.0 * getTotalGets());
        } else {
            return 0.0;
        }
    }

    /**
     * Determine the rough number of cache pruning cycles since the cache statistics were last reset.
     * For the LocalCache implementation, this refers to the number of times that the prune() method is executed.
     *
     * @return the total number of cache pruning cycles (since that last statistics reset)
     */
    public long getCachePrunes() {
        return 0;
    }

    /**
     * Determine the total number of milliseconds (since that last statistics reset) spent on cache pruning.
     * For the LocalCache implementation, this refers to the time spent in the prune() method.
     *
     * @return the total number of milliseconds (since that last statistics reset) for cache pruning operations
     */
    public long getCachePrunesMillis() {
        return 0;
    }

    /**
     * Reset all of the cache statistics. Note that the method name implies that only the hit
     * statistics are cleared, which is not the case; all of the statistics are cleared.
     */
    public void resetHitStatistics() {
        this.gets = 0;
        this.totalGetMillis = 0;
        this.puts = 0;
        this.totalPutMillis = 0;
        this.hits = 0;
        this.totalHitsMillis = 0;
        this.miss = 0;
        this.totalMissMillis = 0;
    }
}

As mentioned before, the configuration file for the Coherence cache is controlled by the system Java property -Dtangosol.coherence.cacheconfig=hibernate-cache-config.xml. Without specifying anything in Java command line, Coherence will attempt to use the cache configuration descriptor named coherence-cache-config.xml, that it finds in the class-path. Since Coherence ships with this file packaged into the coherence.jar, unless you place another file with the same name in the class-path location preceding coherence.jar, that is the one that Coherence will use. The following is an example of a Coherence configuration file for Hibernate:

<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
    <caching-scheme-mapping>
        <!--Query cache configuration.-->
        <cache-mapping>
            <cache-name>org.hibernate.cache.*</cache-name>
            <scheme-name>hibernate-replicated</scheme-name>
            <init-params>
                <init-param>
                    <param-name>size-limit</param-name>
                    <param-value>100</param-value>
                </init-param>
            </init-params>
        </cache-mapping>
        <!--Entity cache configuration.-->
        <cache-mapping>
            <cache-name>*</cache-name>
            <scheme-name>hibernate-replicated</scheme-name>
            <init-params>
                <init-param>
                    <param-name>size-limit</param-name>
                    <param-value>1000</param-value>
                </init-param>
            </init-params>
        </cache-mapping>
    </caching-scheme-mapping>
    <caching-schemes>
        <!--Size-limited replicated caching scheme.-->
        <replicated-scheme>
            <scheme-name>hibernate-replicated</scheme-name>
            <service-name>HibernateReplicatedCache</service-name>
            <serializer>
				<class-name>com.tangosol.io.DefaultSerializer</class-name>
            </serializer>
            <!--serializer>
                <class-name>com.tangosol.io.pof.ConfigurablePofContext</class-name>
                <init-params>
                    <init-param>
                        <param-type>String</param-type>
                        <param-value>hibernate-pof-config.xml</param-value>
                    </init-param>
                </init-params>
            </serializer-->
            <backing-map-scheme>
                <local-scheme>
                    <high-units>{size-limit 0}</high-units>
                </local-scheme>
            </backing-map-scheme>
            <autostart>true</autostart>
        </replicated-scheme>
    </caching-schemes>
</cache-config>

Now comes the difficult part – the correct usage of the cache policies (caching strategies and physical cache providers). Different kinds of data require different cache policies: The ratio of reads to writes varies, the size of the database tables varies, and some tables are shared with other external applications. The second-level cache is configurable at the granularity of an individual class or collection role. This lets you, for example, enable the second-level cache for reference data classes and disable it for classes that represent financial records. The cache policy involves setting the following:

  • Whether the second-level cache is enabled
  • The Hibernate concurrency strategy
  • The cache expiration policies
  • The physical format of the cache

Not all classes benefit from caching, so it is important to be able to disable the second-level cache. The cache is usually useful only for read-mostly classes. If you have data that is updated much more often than it is read, do not enable the second-level cache, even if all other conditions for caching are true. The price of maintaining the cache during updates can possibly outweigh the performance benefit of faster reads. Furthermore, the second-level cache can be dangerous in systems that share the database with other writing applications. You must exercise careful judgment here for each class and collection you want to enable caching for.

The Hibernate second-level cache is set up in two steps.

  • First, you have to decide which concurrency strategy to use.
  • Secondly, you configure cache expiration and physical cache attributes using the cache provider.

An example Hibernate configuration, which uses second-level caching, is the following:

<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="hibernate.connection.driver_class">oracle.jdbc.OracleDriver</property>
        <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:orcl</property>
        <property name="hibernate.connection.username">example</property>
        <property name="hibernate.connection.password">example</property>
        <!-- Use the C3P0 connection pool provider -->
        <property name="hibernate.c3p0.min_size">5</property>
        <property name="hibernate.c3p0.max_size">20</property>
        <property name="hibernate.c3p0.timeout">300</property>
        <property name="hibernate.c3p0.max_statements">50</property>
        <property name="hibernate.c3p0.idle_test_period">3000</property>
        <!-- SQL dialect -->
        <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
        <!-- Enable Hibernate's automatic session context management -->
        <property name="hibernate.current_session_context_class">thread</property>
        <!-- Echo all executed SQL to stdout -->
        <property name="hibernate.show_sql">false</property>
        <property name="hibernate.format_sql">false</property>
        <property name="hibeante.use_sql_comments">false</property>
        <!-- Build in Coherence Cache provider configuration make sure coherence-hibernate.jar is in the classpath-->
        <!-- Use -Dtangosol.coherence.hibernate.cacheconfig=hibernate-cache-config.xml to configure the cache -->
        <!--property name="hibernate.cache.provider_class">com.tangosol.coherence.hibernate.CoherenceCacheProvider</property-->
        <!-- Using our implementation -->
		<!-- Use -Dtangosol.coherence.cacheconfig=hibernate-cache-config.xml to configure the cache -->
        <property name="hibernate.cache.provider_class">cache.CoherenceCacheProvider</property>
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <!--property name="hibernate.cache.use_minimal_puts">true</property-->
        <!--property name="hibernate.cache.use_query_cache">true</property-->
        <!-- Configure SessionFactory -->
        <mapping resource="datamodel/entities/Department.hbm.xml"/>
        <mapping resource="datamodel/entities/Employee.hbm.xml"/>
        <!-- Cache configuration-->
        <class-cache class="datamodel.entities.Department" usage="transactional"/>
        <class-cache class="datamodel.entities.Employee" usage="transactional"/>
        <collection-cache collection="datamodel.entities.Department.employees" usage="transactional"/>
        <collection-cache collection="datamodel.entities.Employee.managedEmployees" usage="transactional"/>
    </session-factory>
</hibernate-configuration>

In the example above, we have used transactional as the concurrency strategy. What does this mean? A concurrency strategy is a mediator: It is responsible for storing items of data in the cache and retrieving them from the cache. This is an important role, because it also defines the transaction isolation semantics for that particular item. You will have to decide, for each persistent class and collection, which cache concurrency strategy to use if you want to enable the second-level cache. Hibernate has four built-in concurrency strategies that represent decreasing levels of strictness in terms of transaction isolation:

  • Transactional – Available in a managed environment only, it guarantees full transactional isolation up to repeatable read, if required. Use this strategy for read-mostly data where it is critical to prevent stale data in concurrent transactions, in the rare case of an update.
  • Read-write – This strategy maintains read committed isolation, using a time-stamping mechanism and is available only in non-clustered environments. Again, use this strategy for read-mostly data where it is critical to prevent stale data in concurrent transactions, in the rare case of an update.
  • Nonstrict-read-write – Makes no guarantee of consistency between the cache and the database. If there is a possibility of concurrent access to the same entity, you should configure a sufficiently short expiry timeout. Otherwise, you may read stale data from the cache. Use this strategy if data hardly ever changes (many hours, days, or even a week) and a small
    likelihood of stale data is not of critical concern.
  • Read-only – A concurrency strategy suitable for data which never changes. Use it for reference data only.

Note that with decreasing strictness comes increasing performance. You have to carefully evaluate the performance of a clustered cache with full transaction isolation before using it in production. In many cases, you may be better off disabling the second-level cache for a particular class if stale data is not an option. First benchmark your application with the second-level cache disabled. Enable it for good candidate classes, one at a time, while continuously testing the scalability of your system and evaluating concurrency strategies.

Now, let us start the application and look at some (performance) logging. The application is some sort of company which maintains departments and employees (which you might have guessed from the classes presented above). If we start the application the cache has to be built and the data must be added to the cache:

Nov 13, 2010 1:44:05 PM org.hibernate.impl.SessionFactoryImpl <init>
INFO: building session factory
CACHEPROVIDER START

Oracle Coherence Version 3.5.3/465
 Grid Edition: Development mode
Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.

2010-11-13 13:44:07.765/3.562 Oracle Coherence GE 3.5.3/465 <D5> (thread=Cluster, member=n/a): Service Cluster joined the cluster with senior service member n/a
2010-11-13 13:44:11.015/6.812 Oracle Coherence GE 3.5.3/465 <Info> (thread=Cluster, member=n/a): Created a new cluster "cluster:0xDDEB" with Member(Id=1, Timestamp=2010-11-13 13:44:07.531, Address=192.168.109.1:8088, MachineId=1281, Location=machine:LT1379,process:2584, Role=IntellijRtExecutionAppMain, Edition=Grid Edition, Mode=Development, CpuCount=2, SocketCount=1) UID=0xC0A86D010000012C45439BEB05011F98
2010-11-13 13:44:11.078/6.875 Oracle Coherence GE 3.5.3/465 <D5> (thread=Invocation:Management, member=1): Service Management joined the cluster with senior service member 1
2010-11-13 13:44:11.203/7.000 Oracle Coherence GE 3.5.3/465 <Info> (thread=main, member=1): Loaded cache configuration from "file:/C:/Documents/SpringHibernate/Voorbeelden/VoorbeeldHibernate/out/production/VoorbeeldHibernate/hibernate-cache-config.xml"
2010-11-13 13:44:11.281/7.078 Oracle Coherence GE 3.5.3/465 <Info> (thread=main, member=1): Loading POF configuration from resource "file:/C:/Documents/SpringHibernate/Voorbeelden/VoorbeeldHibernate/out/production/VoorbeeldHibernate/hibernate-pof-config.xml"
2010-11-13 13:44:11.281/7.078 Oracle Coherence GE 3.5.3/465 <Info> (thread=main, member=1): Loading POF configuration from resource "jar:file:/C:/Documents/SpringHibernate/Voorbeelden/VoorbeeldHibernate/lib/Coherence/coherence.jar!/coherence-pof-config.xml"
2010-11-13 13:44:11.343/7.140 Oracle Coherence GE 3.5.3/465 <D5> (thread=ReplicatedCache:HibernateReplicatedCache, member=1): Service HibernateReplicatedCache joined the cluster with senior service member 1
CACHEPROVIDER BUILDCACHE datamodel.entities.Department
CACHEPROVIDER BUILDCACHE datamodel.entities.Employee
CACHEPROVIDER BUILDCACHE datamodel.entities.Employee.managedEmployees
CACHEPROVIDER BUILDCACHE datamodel.entities.Department.employees
CACHEPROVIDER NEXTTIMESTAMP
CACHE PUT datamodel.entities.Department#10, CacheEntry(datamodel.entities.Department)[ACCOUNTING,NEW YORK,10]
CACHE PUT datamodel.entities.Department#20, CacheEntry(datamodel.entities.Department)[RESEARCH,DALLAS,20]
CACHE PUT datamodel.entities.Department#30, CacheEntry(datamodel.entities.Department)[SALES,CHICAGO,30]
CACHE PUT datamodel.entities.Employee#7100, CacheEntry(datamodel.entities.Employee)[KING,PRESIDENT,null,1981-11-17,5000.0,222.0,10,7100,null,10]
CACHE PUT datamodel.entities.Employee#7298, CacheEntry(datamodel.entities.Employee)[BLAKE,MANAGER,7100,1981-05-01,2850.0,null,30,7298,7100,30]
CACHE PUT datamodel.entities.Employee#7521, CacheEntry(datamodel.entities.Employee)[WARD,SALESMAN,7298,1981-02-22,1250.0,500.0,30,7521,7298,30]
CACHE PUT datamodel.entities.Employee#7844, CacheEntry(datamodel.entities.Employee)[TURNER,SALESMAN,7298,1981-09-08,1500.0,0.0,30,7844,7298,30]
CACHE PUT datamodel.entities.Employee#7699, CacheEntry(datamodel.entities.Employee)[ALLEN,SALESMAN,7298,1981-02-20,1600.0,300.0,30,7699,7298,30]
CACHE PUT datamodel.entities.Employee#7654, CacheEntry(datamodel.entities.Employee)[MARTIN,SALESMAN,7298,1981-09-28,1250.0,1400.0,30,7654,7298,30]
CACHE PUT datamodel.entities.Department.employees#30, CollectionCacheEntry[7699,7844,7654,7521,7298]
CACHE PUT datamodel.entities.Employee.managedEmployees#7654, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee.managedEmployees#7699, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee.managedEmployees#7844, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee.managedEmployees#7521, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee#7900, CacheEntry(datamodel.entities.Employee)[JAMESS,CLERKY,7298,1981-12-03,950.0,null,20,7900,7298,20]
CACHE PUT datamodel.entities.Employee.managedEmployees#7298, CollectionCacheEntry[7699,7844,7900,7654,7521]
CACHE PUT datamodel.entities.Employee.managedEmployees#7900, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee#7366, CacheEntry(datamodel.entities.Employee)[JONES,DOIETS,7100,1981-04-02,2975.0,null,20,7366,7100,20]
CACHE PUT datamodel.entities.Employee.managedEmployees#7100, CollectionCacheEntry[7366,7298]
CACHE PUT datamodel.entities.Employee#7402, CacheEntry(datamodel.entities.Employee)[FORDA,ANALYST,7366,1981-12-03,3000.0,null,20,7402,7366,20]
CACHE PUT datamodel.entities.Employee#7488, CacheEntry(datamodel.entities.Employee)[SCOTT,ANALYST,7366,1982-12-09,3000.0,null,20,7488,7366,20]
CACHE PUT datamodel.entities.Employee.managedEmployees#7366, CollectionCacheEntry[7402,7488]
CACHE PUT datamodel.entities.Employee#7876, CacheEntry(datamodel.entities.Employee)[ADAMS,CLERK,7488,1983-01-12,1100.0,null,20,7876,7488,20]
CACHE PUT datamodel.entities.Employee.managedEmployees#7488, CollectionCacheEntry[7876]
CACHE PUT datamodel.entities.Employee.managedEmployees#7876, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee#7769, CacheEntry(datamodel.entities.Employee)[SMITH,CLERK,7402,1980-12-17,800.0,null,20,7769,7402,20]
CACHE PUT datamodel.entities.Employee.managedEmployees#7402, CollectionCacheEntry[7769]
CACHE PUT datamodel.entities.Employee.managedEmployees#7769, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Department.employees#20, CollectionCacheEntry[7366,7402,7876,7769,7488,7900]
CACHE PUT datamodel.entities.Employee#7934, CacheEntry(datamodel.entities.Employee)[MILLER,CLERK,7934,1982-01-23,1300.0,null,10,7934,7934,10]
CACHE PUT datamodel.entities.Department.employees#10, CollectionCacheEntry[7934,7100]
CACHE PUT datamodel.entities.Employee.managedEmployees#7934, CollectionCacheEntry[7934]

As you can see from the logging four caches are build (two class caches and two collection caches). After a bunch of reads we print some statistics:

CACHE datamodel.entities.Department
 - GETS 30, TOTALTIME 0, AVERAGE TIME 0.0
 - PUTS 3, TOTALTIME 0, AVERAGE TIME 0.0
 - HITS 30
 - MISSES 0
 - HITPROBABILTY 0.9
CACHE datamodel.entities.Employee.managedEmployees
 - GETS 130, TOTALTIME 32, AVERAGE TIME 0.24615384615384617
 - PUTS 13, TOTALTIME 0, AVERAGE TIME 0.0
 - HITS 130
 - MISSES 0
 - HITPROBABILTY 0.9
CACHE datamodel.entities.Employee
 - GETS 130, TOTALTIME 32, AVERAGE TIME 0.24615384615384617
 - PUTS 13, TOTALTIME 47, AVERAGE TIME 3.6153846153846154
 - HITS 130
 - MISSES 0
 - HITPROBABILTY 0.9
CACHE datamodel.entities.Department.employees
 - GETS 30, TOTALTIME 0, AVERAGE TIME 0.0
 - PUTS 3, TOTALTIME 0, AVERAGE TIME 0.0
 - HITS 30
 - MISSES 0
 - HITPROBABILTY 0.9

Sometimes it is a little to fast to measure, but you get a good idea of fast it can get (an average of 0.3ms for a read is not that bad now is it).

Now, for the total knock-out let us put in some Spring and let Spring handle all the transactions and of course manage our Hibernate SessionFactory. Below is an example of a Spring configurationfile:

<?xml version="1.0" encoding="UTF-8" ?>
<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-2.5.xsd">
    <bean id="statistics" class="cache.logic.StatisticsBean"/>
    <bean id="companyBeanTarget" class="datamodel.logic.CompanyBean">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>datamodel/entities/Department.hbm.xml</value>
                <value>datamodel/entities/Employee.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.c3p0.min_size">${connectionpool.minimum.size}</prop>
                <prop key="hibernate.c3p0.max_size">${connectionpool.maximum.size}</prop>
                <prop key="hibernate.c3p0.timeout">${connectionpool.timeout}</prop>
                <prop key="hibernate.c3p0.max_statements">${connectionpool.maximum.statements}</prop>
                <prop key="hibernate.c3p0.idle_test_period">${connectionpool.idle.period}</prop>
                <prop key="hibernate.cache.provider_class">${cache.provider_class}</prop>
                <prop key="hibernate.cache.use_second_level_cache">${cache.use_second_level_cache}</prop>
                <prop key="hibernate.cache.use_query_cache">${cache.use_query_cache}</prop>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
            </props>
        </property>
        <property name="entityCacheStrategies">
            <props>
                <prop key="datamodel.entities.Department">transactional</prop>
                <prop key="datamodel.entities.Employee">transactional</prop>
            </props>
        </property>
        <property name="collectionCacheStrategies">
            <props>
                <prop key="datamodel.entities.Department.employees">transactional</prop>
                <prop key="datamodel.entities.Employee.managedEmployees">transactional</prop>
            </props>
        </property>
    </bean>
    <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
        <property name="driverType" value="${jdbc.driverClassName}"/>
        <property name="URL" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:company.properties</value>
            </list>
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="companyBean" parent="transactionTemplate">
        <property name="target" ref="companyBeanTarget"/>
        <property name="proxyInterfaces" value="datamodel.logic.Company"/>
    </bean>
    <bean id="transactionTemplate" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
          abstract="true">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="add*">PROPAGATION_REQUIRED,-CompanyException</prop>
                <prop key="remove*">PROPAGATION_REQUIRED,-CompanyException</prop>
                <prop key="update*">PROPAGATION_REQUIRED,-CompanyException</prop>
                <prop key="get*">PROPAGATION_SUPPORTS,readOnly</prop>
            </props>
        </property>
    </bean>
</beans>

Just look at the good old fashioned transactionmanager configuration. Now we can use the following entry point in our application and let everything be managed by Spring (as it should be):

package datamodel.util;

import cache.logic.Statistics;

import datamodel.logic.Company;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Utilities {

    private static ApplicationContext context;

    private Utilities() {
    }

    static {
        context = new ClassPathXmlApplicationContext("CompanyContext.xml");
    }

    public static Company getCompanyInstance() {
        return (Company) context.getBean("companyBean");
    }

    public static Statistics getStatisticsInstance() {
        return (Statistics) context.getBean("statistics");
    }
}

Company is a business interface of a DAO. We also have configured Hibernate to use query caching. This is controlled by the parameter hibernate.cache.use_query_cache. In the Hibernate mapping file we have configured the following:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="datamodel.entities">
    <class name="Department" table="DEPT">
        <id name="departmentNumber" type="integer" column="DEPTNO">
            <generator class="assigned"/>
        </id>
        <property name="departmentName" type="string" column="DNAME"/>
        <property name="location" type="string" column="LOC"/>
        <set name="employees" inverse="true" lazy="false" sort="unsorted" outer-join="true">
            <key column="DEPTNO"/>
            <one-to-many class="Employee"/>
        </set>
    </class>
    <query name="findAllDepartments" flush-mode="always" cache-mode="put" read-only="false">
        <![CDATA[select d from Department d]]>
    </query>
    <query name="findSomeDepartments" flush-mode="auto" cache-mode="ignore" read-only="true" timeout="30"
           fetch-size="20">
        <![CDATA[select d from Department d where d.departmentName like '%E%']]>
    </query>
</hibernate-mapping>

In the configuration above, we have used cache-mode to tell Hibernate how to interact with the second-level cache. The available options are as follows:

  • CacheMode.NORMAL – The default behavior.
  • CacheMode.IGNORE – Hibernate never interacts with the second-level cache except to invalidate cached items when updates occur.
  • CacheMode.GET – Hibernate may read items from the second-level cache, but it will not add items except to invalidate items when updates occur.
  • CacheMode.PUT – Hibernate never reads items from the second-level cache, but it adds items to the cache as it reads them from the database.
  • CacheMode.REFRESH – Hibernate never reads items from the second-level cache, but it adds items to the cache as it reads them from the database.

When a query is executed for the first time, its results are cached in a cache region – this region is different from any other entity or collection cache region you may already have configured. The query result cache region holds the SQL statements and the resultset of each SQL statement. This is not the complete SQL resultset, however. If the resultset contains entity instances, only the identifier values are held in the resultset cache. The data columns of each entity are discarded from the resultset when it is put into the cache region. So, hitting the query result cache means that Hibernate will, for the previous queries, find some Department identifier values.

It is the responsibility of the second-level cache region datamodel.entities.Department (in conjunction with the persistence context) to cache the state of entities. If you query for entities and decide to enable caching, make sure you also enabled regular second-level caching for these entities. If you do not, you may end up with more database hits after enabling the query cache. If you cache the result of a query that does not return entity instances, but returns only the same scalar values, these values are held in the query result cache directly. If the query result cache is enabled in Hibernate, another always required cache region is also present: org.hibernate.cache.UpdateTimestampsCache. This is a cache region used by Hibernate internally. Hibernate uses the timestamp region to decide whether a cached query resultset is stale. When you re-execute a query that has caching enabled, Hibernate looks in the timestamp cache for the timestamp of the most recent insert, update, or delete made to the queried table(s). If the found timestamp is later than the timestamp of the cached query results, the cached results are discarded and a new query is issued. This effectively guarantees that Hibernate will not use the cached query result if any table that may be involved in the query contains updated data; hence, the cached result may be stale.

Note that the majority of queries do not benefit from result caching. This may come as a surprise. After all, it sounds like avoiding a database hit is always a good thing. There are two good reasons why this does not always work for arbitrary queries, compared to object navigation or retrieval by identifier:

  • First, you must ask how often you are going to execute the same query repeatedly. Granted, you may have a few queries in your application that are executed over and over again, with exactly the same arguments bound to parameters, and the same automatically generated SQL statement. When you are certain a query is executed repeatedly, it becomes a good candidate for result caching.
  • Second, for applications that perform many queries and few inserts, deletes, or updates, caching queries can improve performance and scalability. On the other hand if the application performs many writes, the query cache will not be utilized efficiently. Hibernate expires a cached query resultset when there is any insert, update, or delete of any row of a table that appeared in the cached query result. This means cached results may have a short lifetime, and even if a query is executed repeatedly, no cached result can be used due to concurrent modifications of the same data.

For many queries, the benefit of the query result cache is nonexistent or, at least, does not have the impact you would expect.

Now let us look at some logging:

Nov 13, 2010 2:08:53 PM org.hibernate.impl.SessionFactoryImpl <init>
INFO: building session factory

Oracle Coherence Version 3.5.3/465
 Grid Edition: Development mode
Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.

2010-11-13 14:08:55.859/4.156 Oracle Coherence GE 3.5.3/465 <D5> (thread=Cluster, member=n/a): Service Cluster joined the cluster with senior service member n/a
2010-11-13 14:08:59.109/7.406 Oracle Coherence GE 3.5.3/465 <Info> (thread=Cluster, member=n/a): Created a new cluster "cluster:0xDDEB" with Member(Id=1, Timestamp=2010-11-13 14:08:55.625, Address=192.168.109.1:8088, MachineId=1281, Location=machine:LT1379,process:2208, Role=IntellijRtExecutionAppMain, Edition=Grid Edition, Mode=Development, CpuCount=2, SocketCount=1) UID=0xC0A86D010000012C455A50C905011F98
2010-11-13 14:08:59.156/7.453 Oracle Coherence GE 3.5.3/465 <D5> (thread=Invocation:Management, member=1): Service Management joined the cluster with senior service member 1
2010-11-13 14:08:59.343/7.640 Oracle Coherence GE 3.5.3/465 <Info> (thread=main, member=1): Loaded cache configuration from "file:/C:/Documents/SpringHibernate/Voorbeelden/VoorbeeldSpringHibernate/out/production/VoorbeeldSpringHibernate/hibernate-cache-config.xml"
2010-11-13 14:08:59.406/7.703 Oracle Coherence GE 3.5.3/465 <D5> (thread=ReplicatedCache:HibernateReplicatedCache, member=1): Service HibernateReplicatedCache joined the cluster with senior service member 1
CACHEPROVIDER BUILDCACHE datamodel.entities.Department
CACHEPROVIDER BUILDCACHE datamodel.entities.Employee
CACHEPROVIDER BUILDCACHE datamodel.entities.Employee.managedEmployees
CACHEPROVIDER BUILDCACHE datamodel.entities.Department.employees
Nov 13, 2010 2:08:59 PM org.hibernate.cache.UpdateTimestampsCache <init>
INFO: starting update timestamps cache at region: org.hibernate.cache.UpdateTimestampsCache
Nov 13, 2010 2:08:59 PM org.hibernate.cache.StandardQueryCache <init>
INFO: starting query cache at region: org.hibernate.cache.StandardQueryCache
CACHEPROVIDER BUILDCACHE org.hibernate.cache.UpdateTimestampsCache
CACHEPROVIDER BUILDCACHE org.hibernate.cache.StandardQueryCache
Nov 13, 2010 2:08:59 PM org.springframework.orm.hibernate3.HibernateTransactionManager afterPropertiesSet
INFO: Using DataSource [oracle.jdbc.pool.OracleDataSource@7f8062] of Hibernate SessionFactory for HibernateTransactionManager
CACHEPROVIDER NEXTTIMESTAMP
CACHE PUT datamodel.entities.Department#10, CacheEntry(datamodel.entities.Department)[ACCOUNTING,NEW YORK,10]
CACHE PUT datamodel.entities.Department#20, CacheEntry(datamodel.entities.Department)[RESEARCH,DALLAS,20]
CACHE PUT datamodel.entities.Department#30, CacheEntry(datamodel.entities.Department)[SALES,CHICAGO,30]
CACHE PUT datamodel.entities.Employee#7100, CacheEntry(datamodel.entities.Employee)[KING,PRESIDENT,null,1981-11-17,5000.0,222.0,10,7100,null,10]
CACHE PUT datamodel.entities.Employee#7298, CacheEntry(datamodel.entities.Employee)[BLAKE,MANAGER,7100,1981-05-01,2850.0,null,30,7298,7100,30]
CACHE PUT datamodel.entities.Employee#7521, CacheEntry(datamodel.entities.Employee)[WARD,SALESMAN,7298,1981-02-22,1250.0,500.0,30,7521,7298,30]
CACHE PUT datamodel.entities.Employee#7844, CacheEntry(datamodel.entities.Employee)[TURNER,SALESMAN,7298,1981-09-08,1500.0,0.0,30,7844,7298,30]
CACHE PUT datamodel.entities.Employee#7699, CacheEntry(datamodel.entities.Employee)[ALLEN,SALESMAN,7298,1981-02-20,1600.0,300.0,30,7699,7298,30]
CACHE PUT datamodel.entities.Employee#7654, CacheEntry(datamodel.entities.Employee)[MARTIN,SALESMAN,7298,1981-09-28,1250.0,1400.0,30,7654,7298,30]
CACHE PUT datamodel.entities.Department.employees#30, CollectionCacheEntry[7699,7844,7654,7521,7298]
CACHE PUT datamodel.entities.Employee.managedEmployees#7654, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee.managedEmployees#7699, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee.managedEmployees#7844, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee.managedEmployees#7521, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee#7900, CacheEntry(datamodel.entities.Employee)[JAMESS,CLERKY,7298,1981-12-03,950.0,null,20,7900,7298,20]
CACHE PUT datamodel.entities.Employee.managedEmployees#7298, CollectionCacheEntry[7699,7844,7900,7654,7521]
CACHE PUT datamodel.entities.Employee.managedEmployees#7900, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee#7366, CacheEntry(datamodel.entities.Employee)[JONES,DOIETS,7100,1981-04-02,2975.0,null,20,7366,7100,20]
CACHE PUT datamodel.entities.Employee.managedEmployees#7100, CollectionCacheEntry[7366,7298]
CACHE PUT datamodel.entities.Employee#7402, CacheEntry(datamodel.entities.Employee)[FORDA,ANALYST,7366,1981-12-03,3000.0,null,20,7402,7366,20]
CACHE PUT datamodel.entities.Employee#7488, CacheEntry(datamodel.entities.Employee)[SCOTT,ANALYST,7366,1982-12-09,3000.0,null,20,7488,7366,20]
CACHE PUT datamodel.entities.Employee.managedEmployees#7366, CollectionCacheEntry[7402,7488]
CACHE PUT datamodel.entities.Employee#7876, CacheEntry(datamodel.entities.Employee)[ADAMS,CLERK,7488,1983-01-12,1100.0,null,20,7876,7488,20]
CACHE PUT datamodel.entities.Employee.managedEmployees#7488, CollectionCacheEntry[7876]
CACHE PUT datamodel.entities.Employee.managedEmployees#7876, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Employee#7769, CacheEntry(datamodel.entities.Employee)[SMITH,CLERK,7402,1980-12-17,800.0,null,20,7769,7402,20]
CACHE PUT datamodel.entities.Employee.managedEmployees#7402, CollectionCacheEntry[7769]
CACHE PUT datamodel.entities.Employee.managedEmployees#7769, CollectionCacheEntry[]
CACHE PUT datamodel.entities.Department.employees#20, CollectionCacheEntry[7366,7402,7876,7769,7488,7900]
CACHE PUT datamodel.entities.Employee#7934, CacheEntry(datamodel.entities.Employee)[MILLER,CLERK,7934,1982-01-23,1300.0,null,10,7934,7934,10]
CACHE PUT datamodel.entities.Department.employees#10, CollectionCacheEntry[7934,7100]
CACHE PUT datamodel.entities.Employee.managedEmployees#7934, CollectionCacheEntry[7934]

If we perform some reads we have the following statistics

CACHE org.hibernate.cache.StandardQueryCache
 - GETS 0, TOTALTIME 0, AVERAGE TIME 0.0
 - PUTS 0, TOTALTIME 0, AVERAGE TIME 0.0
 - HITS 0
 - MISSES 0
 - HITPROBABILTY 0.0
CACHE datamodel.entities.Department
 - GETS 45, TOTALTIME 0, AVERAGE TIME 0.0
 - PUTS 3, TOTALTIME 0, AVERAGE TIME 0.0
 - HITS 45
 - MISSES 0
 - HITPROBABILTY 0.9333333333333333
CACHE org.hibernate.cache.UpdateTimestampsCache
 - GETS 0, TOTALTIME 0, AVERAGE TIME 0.0
 - PUTS 0, TOTALTIME 0, AVERAGE TIME 0.0
 - HITS 0
 - MISSES 0
 - HITPROBABILTY 0.0
CACHE datamodel.entities.Employee.managedEmployees
 - GETS 195, TOTALTIME 0, AVERAGE TIME 0.0
 - PUTS 13, TOTALTIME 0, AVERAGE TIME 0.0
 - HITS 195
 - MISSES 0
 - HITPROBABILTY 0.9333333333333333
CACHE datamodel.entities.Employee
 - GETS 195, TOTALTIME 47, AVERAGE TIME 0.24102564102564103
 - PUTS 13, TOTALTIME 32, AVERAGE TIME 2.4615384615384617
 - HITS 195
 - MISSES 0
 - HITPROBABILTY 0.9333333333333333
CACHE datamodel.entities.Department.employees
 - GETS 45, TOTALTIME 0, AVERAGE TIME 0.0
 - PUTS 3, TOTALTIME 0, AVERAGE TIME 0.0
 - HITS 45
 - MISSES 0
 - HITPROBABILTY 0.9333333333333333

References

[1] Bauer and King, “Java Persistence with Hibernate”, Manning, 2007. The reference on Java Persistence, Enterprise Java Beans and of course Hibernate.
[2] Walls, “Spring in Action”, Manning, 2008. All you ever wanted to know about Spring (also a good reference when you are going for your Spring certification).
[3] Seovic, “Oracle Coherence 3.5″, Packt Publishing, 2010. A book that helps you build high-performance applications using Coherence.
[4] Hibernate Reference Information.
[5] The Coherence Knowlegde Base.


  • Testimonials

  • RSS Middleware Magic – JBoss

  • Receive FREE Updates


    FREE Email updates of our new posts Enter your email address:



  • Magic Archives

  • Sitemeter Status

  • ClusterMap 7-Nov-2011 till Date

  • ClusterMap 6-Nov-2010 till 7-Nov-2011

  • Copyright © 2010-2012 Middleware Magic. All rights reserved. | Disclaimer