04 June 2018

Cache Coordination

When using JPA, sooner or later the question of caching will arise to improve performance. Especially for data that is frequently read but only written/updated infrequently, it makes sense to enable the second-level cache via shared-cache-mode-element in the persistence.xml. See the Java EE 7 tutorial for details.

By default, EclipseLink has the second-level cache enabled as you can read here. Consider what will happen in a clustered environment: What happens if server one has the entity cached and server two will update the entity? server one will have a stale cache-entry and by default noone will tell the server that its cache is out-of-date. How to deal with it? Define a hard-coded expiration? Or not use the second-level-cache at all?

A better solution is to get the second-level caches sychronized in the cluster. EclipseLink’s vendor-specific feature for this is called cache-coordination. You can read more about it here, but in a nutshell you can use either JMS, RMI or JGroups to distribute cache-invalidations/updates within the cluster. This post focuses on getting EclipseLink’s cache-coordination working under Websphere Liberty via JGroups.

Application Configuration

From the application’s perspective, you only have to enable this feature in the persistence.xml via

<property name="eclipselink.cache.coordination.protocol" value="jgroups" />

Liberty Server Configuration with Global Library

Deploying this application on Webspher Liberty, will lead to the following error:

Exception Description: ClassNotFound: [org.eclipse.persistence.sessions.coordination.jgroups.JGroupsTransportManager] specified in [eclipselink.cache.coordination.protocol] property.

Thanks to the great help on the openliberty.io mailing-list, I was able to solve the problem. You can read the full discussion here.

The short summary is that the cache-coordination feature of EclipseLink using JGroups is an extension and Liberty does not ship this extension by default. RMI and JMS are supported out-of-the-box but both have disadvantages:

  • RMI is a legacy technology that I have not worked with in years.

  • JMS in general is a great technology for asychroneous communication but it requires a message-broker like IBM MQ or ActiveMQ. This does not sound like a good fit for a caching-mechanism.

This leaves us with JGroups. The prefered solution to get JGroups working is to replace the JPA-implementation with our own. For us, this will simply be EclipseLink but including the extension. In Liberty this is possible via the jpaContainer feature in the server.xml. The offical documentation describes how to use our own JPA-implementation. As there are still a few small mistakes you can make on the way, let me describe the configuration that works here in detail:

  1. Assuming you are working with the javaee-7.0-feature in the server.xml (or in specific jpa-2.1), you will have to get EclipseLink 2.6 as this implements JPA 2.1. For javaee-8.0 (or in specific jpa-2.2) it would be EclipseLink 2.7.

    I assume javaee-7.0 here; that’s why I downloaded EclipseLink 2.6.5 OSGi Bundles Zip.

  2. Create a folder lib/global within your Liberty server-config-folder. E.g. defaultServer/lib/global and copy the following from the zip (same as referenced here plus the extension):

    • org.eclipse.persistence.asm.jar

    • org.eclipse.persistence.core.jar

    • org.eclipse.persistence.jpa.jar

    • org.eclipse.persistence.antlr.jar

    • org.eclipse.persistence.jpa.jpql.jar

    • org.eclipse.persistence.jpa.modelgen.jar

    • org.eclipse.persistence.extension.jar

  3. If you would use it like this, you will find a ClassNotFoundException later for the actual JGroups implementation-classes. You will need to get it seperately from here.

    If we look on the 2.6.5-tag in EclipseLink’s Git Repo, we see that we should use org.jgroups:jgroups:3.2.8.Final.

    Download it and copy the jgroups-3.2.8.Final.jar to the lib/global folder as well.

  4. The last step is to set up your server.xml like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <server description="new server">
    
        <!-- Enable features -->
        <featureManager>
    		<feature>servlet-3.1</feature>
    		<feature>beanValidation-1.1</feature>
    		<feature>ssl-1.0</feature>
    		<feature>jndi-1.0</feature>
    		<feature>jca-1.7</feature>
    		<feature>jms-2.0</feature>
    		<feature>ejbPersistentTimer-3.2</feature>
    		<feature>appSecurity-2.0</feature>
    		<feature>j2eeManagement-1.1</feature>
    		<feature>jdbc-4.1</feature>
    		<feature>wasJmsServer-1.0</feature>
    		<feature>jaxrs-2.0</feature>
    		<feature>javaMail-1.5</feature>
    		<feature>cdi-1.2</feature>
    		<feature>jcaInboundSecurity-1.0</feature>
    		<feature>jsp-2.3</feature>
    		<feature>ejbLite-3.2</feature>
    		<feature>managedBeans-1.0</feature>
    		<feature>jsf-2.2</feature>
    		<feature>ejbHome-3.2</feature>
    		<feature>jaxws-2.2</feature>
    		<feature>jsonp-1.0</feature>
    		<feature>el-3.0</feature>
    		<feature>jaxrsClient-2.0</feature>
    		<feature>concurrent-1.0</feature>
    		<feature>appClientSupport-1.0</feature>
    		<feature>ejbRemote-3.2</feature>
    		<feature>jaxb-2.2</feature>
    		<feature>mdb-3.2</feature>
    		<feature>jacc-1.5</feature>
    		<feature>batch-1.0</feature>
    		<feature>ejb-3.2</feature>
    		<feature>json-1.0</feature>
    		<feature>jaspic-1.1</feature>
    		<feature>distributedMap-1.0</feature>
    		<feature>websocket-1.1</feature>
    		<feature>wasJmsSecurity-1.0</feature>
    		<feature>wasJmsClient-2.0</feature>
    
    		<feature>jpaContainer-2.1</feature>
        </featureManager>
    
    
        <basicRegistry id="basic" realm="BasicRealm">
        </basicRegistry>
    
        <httpEndpoint id="defaultHttpEndpoint"
                      httpPort="9080"
                      httpsPort="9443" />
    
    	<applicationManager autoExpand="true"/>
    
    	<jpa defaultPersistenceProvider="org.eclipse.persistence.jpa.PersistenceProvider"/>
    
    </server>

Some comments on the server.xml:

  • Note that we have to list all of the features that are included in the javaee-7.0 feature minus the jpa-2.1 feature explicitly now because we don`t want the default JPA-provider.

  • Instead of jpa-2.1 I added jpaContainer-2.1 to bring our own JPA-provider.

  • The defaultPersistenceProvider will set the JPA-provider to use ours and is required by the jpaContainer feature.

Liberty Configuration without Global Library

Be aware that there are different ways how to include our EclipseLink library. Above, I chose the way that requires the least configuration in the server.xml and also works for dropin-applications. The way I did it was via a global library. The offical documentation defines it as an explicit library in the server.xml and reference it for each invidual application like this:

<bell libraryRef="eclipselink"/>
<library id="eclipselink">
	<file name="${server.config.dir}/jpa/org.eclipse.persistence.asm.jar"/>
	<file name="${server.config.dir}/jpa/org.eclipse.persistence.core.jar"/>
	<file name="${server.config.dir}/jpa/org.eclipse.persistence.jpa.jar"/>
	<file name="${server.config.dir}/jpa/org.eclipse.persistence.antlr.jar"/>
	<file name="${server.config.dir}/jpa/org.eclipse.persistence.jpa.jpql.jar"/>
	<file name="${server.config.dir}/jpa/org.eclipse.persistence.jpa.modelgen.jar"/>

	<file name="${server.config.dir}/jpa/org.eclipse.persistence.extension.jar"/>
	<file name="${server.config.dir}/jpa/jgroups.jar"/>
</library>

<application location="myapp.war">
    <classloader commonLibraryRef="eclipselink"/>
</application>

Also note, that the JARs are this time in the defaultServer/jpa-folder, not under defaultServer/lib/global and I removed all the version-suffixes from the file-names. Additionally, make sure to add <feature>bells-1.0</feature>.

Try it

As this post is already getting to long, I will not got into detail here how to use this from your Java EE application. This will be for another post. But you can already get a working Java EE project to get your hands dirty from my GitHub repository. Start the Docker Compose environment and use the contained test.sh to invoke some cURL requests against the application on two different cluster-nodes.

Conclusion

With the either of the aboved approaches I was able to enable EclipseLink’s cache-coordination feature on Websphere Liberty for Java EE 7.

I did not try it, but I would assume that it will work similar for Java EE 8 on the latest OpenLiberty builds.

For sure it is nice that plugging in your own JPA-provider is so easy in Liberty; but I don’t like that I have to do this to get a feature of EclipseLink working under Liberty which I would expect to work out of the box. EclipseLink’s cache-coordination feature is a quiet useful extension and it leaves me uncomfortable that I have configured my own snowflake Liberty instead of relying on the standard package. On the other hand, it works; and if I make sure to use the exact same version of EclipseLink as packaged with Liberty out of the box, I would hope the differences are minimal.

The approach I chose/prefer in the end is Liberty Server Configuration with Global Library instead of using the approach that is also in the offical documentation (Liberty Configuration without Global Library). The reason is that for Liberty Configuration without Global Library I have to reference the library in the server.xml indvidually for each application. This will not work for applications I would like throw into the dropins.