Using Coherence with Universal Connection Pool (UCP)
By Pablo Silberkasten-Oracle on Apr 15, 2016
Coherenceis an in-memory data gridsolution that addresses the issues of working with distributed objectsinmemory (cache). Amongst its many features, Pluggable Cache Storesallows caching (i.e., load/store) contents from any persistence layer.
Thisway, the cache abstracts content persistence and loading through theCacheStore interface to be implemented by the user.The synchronization of updates, also decoupled from the persistencelayer, is governed by the following strategies:
- Refresh-Ahead/ Read-Through: whether you want data to be loaded in the cache beforebeingactually requested vs staleness of the data in the cache
- Write-Behind/ Write-Through: whether you expect better response time by doing theactualization of the data asynchronous vs immediate persistence of thechange
Relational databases is the most common option for persistence, but itsselectionforces you to define an object-relational mapping from Java classes tothe underlying relational model(using for example: hibernate, TopLink or any ad-hoc JPAimplementation). But thatis onlyhalf of the job to be done. It is also paramount to define how you willmanagethe connections to execute this persistence.
Thefollowing diagram depicts how Coherence persists in an RDBMS throughthe CacheStore interface:
TheCacheStore is responsible forhandling the connection to the database. Not only in terms ofconnectioncreation/pooling but also in terms of how to detect changes in theinstances ofthe database it is connected to.
Connectionmanagement is critical whenyou are looking for extreme performance and high availability; henceit’scritical for the CacheStore to handle connection acquirement properly.
OracleUniversal Connection Pool (UCP)provides you notonly all the intrinsic advantages of connection pooling (connectionre-use,management, availability, purge, etc.) but it also leverages thepossibility touse all the features you have when connected to a RAC environment:
- RuntimeConnection Load Balancing (RCLB)
- FastConnection Failover (FCF)
- TransactionAffinity
- Built-insupport for Database Resident Connection Pooling (DRCP) and ApplicationContinuity (AC)
Bytaking this approach, you are not onlysetting the relationship between your cache and your persistence layer.You are also optimizing the configuration and management with yourunderlying RAC, by exploiting all its features in terms of connectionhandling.
Formore information about UCP, please refer to: Introductionto UCP.
Ina previous article we already discussed these features and how toexploit them from a JBoss Web application: UsingUniversal Connection Pooling (UCP) with JBoss AS
Inthis article we are going to show you how to use UCP from Coherenceusing Pluggable Cache Stores, hence also making it available for anytype ofCoherence client.
1.Downloadand install coherence standalone, UCP and Oracle JDBC Driver.
Download“Coherence Stand-Alone Install” from this location.
Unzipthe downloaded file.
Runthe universal installation jar:
java -jarfmw_12.2.1.0.0_coherence.jar
Duringinstall, select an Oracle Home location. In this location youwill find the coherence jar that we will be using during thisdemo:
ORACLE_HOME/coherence/lib/coherence.jar.
DownloadUCP and Oracle JDBC Driver (ucp.jar and ojdbc.jar) UCPJDBC
Forthis sample, I copied these jars are in the home directory (~) andcoherence.jar to ~/ucpcoherence dir:
~/ucpcoherence/coherence.jar
~/ucp.jar
~/ojdbc8.jar
2.Configure Cache and CacheStore.
Youneed to indicate coherence the schemefor your cache. The scheme will define its behavior (such as if it’slocal,replicated or distributed, etc.). For our configuration, these are themostimportant things to understand:
- cache-mapping:this will indicate coherence what cache-names (ids) will match to thisspecific type (scheme). In our sample we’ll create cache “test1” tomatch it with “test*”, which in turns will associate with scheme-name“example-distributed” and under this scheme-name we’ll define ourcache-scheme.
- class-name(in cachestore-scheme): here we will inject into our distributed cachethe name of the class that will handle the persistence (load / loadAll/ store / storeAll / erase / eraseAll) operations in our cache. In ourcase it will be ucp_samples.EmployeeCacheStore, which we will definelater in this article.
- init-params(in class-scheme): here you can specify values that will be used in theconstructor of our class.
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
< caching-scheme-mapping >
< cache-mapping ><cache-name>test*</cache-name>
<scheme-name>example-distributed</scheme-name>
</cache-mapping></caching-scheme-mapping>
<caching-schemes>
<distributed-scheme><scheme-name>example-distributed</scheme-name>
< service-name > DistributedCache </ service-name >
< backing-map-scheme >
< read-write-backing-map-scheme >
< internal-cache-scheme >
< local-scheme />
</ internal-cache-scheme >
< cachestore-scheme >
< class-scheme >
< class-name > ucp_samples.EmployeeCacheStore </ class-name >
< init-params >
< init-param >
< param-type > java.lang.String </ param-type >
< param-value > jdbc:oracle:thin:@//localhost:1521/cdb1 </ param-value >
</ init-param >
< init-param >
< param-type > java.lang.String </ param-type >
< param-value > scott </ param-value >
</ init-param >
< init-param >
< param-type > java.lang.String </ param-type >
< param-value > tiger </ param-value >
</ init-param >
</ init-params >
</class-scheme>
</cachestore-scheme>
</read-write-backing-map-scheme>
</backing-map-scheme>
<autostart>true</autostart></distributed-scheme>
</caching-schemes></cache-config>
3.Provide CacheStore Implementation.
Asconfigured in previous file, you needto provide an implementation for CacheStore that will execute itsmethods whenobjects in the cache are added or requested (it will store or load iftheobject is not on the cache). We provide an implementation for the loadmethodand the constructor of the class (which shows how to use UCP). You candefinethe behavior for the rest of the methods as part of your test.
Noticethat the values for UCP can be changed/monitored through JMX asit was explained in thisarticle.
Tocheck more in how to use UCP with Hibernate you can check in thisarticle.
publicclassEmployeeCacheStore implementsCacheStore {
privatePoolDataSource pds= null;
private int INITIAL_POOL_SIZE= 5;privateConnection getConnection() throwsSQLException {
return pds .getConnection();}
/**
* Constructor fortheCacheStore, parsesinitial values and sets the
* connection pool.
*
* @paramurl
* @paramuser
* @parampassword
*/
publicEmployeeCacheStore(String url,String user,String password){
}try{
//Create pool-enabled data source instance
pds =PoolDataSourceFactory.getPoolDataSource();
//set the connection properties on the data source
pds .setConnectionFactoryClassName(
"oracle.jdbc.pool.OracleDataSource" );pds .setURL( url );
pds .setUser( user );
pds .setPassword( password );//Override pool properties
pds.setInitialPoolSize(INITIAL_POOL_SIZE);
pds.setConnectionPoolName(this.getClass().getName());} catch(SQLException e){
e .printStackTrace();}
/**
* When an object isnot inthe cache, it willgo through cache store to
* retrieve it,using theconnection pool. Inthis sample we execute a
* manual objectrelationalmapping, in a real-lifescenario hibernate, or
* any other ad-hocJPA implementation should be used.
*/@Override
publicEmployee load(Object employeeId){Employee employee= null;
try{
PreparedStatement ps = getConnection().prepareStatement( "select* from employees where employee_id = ?" );} catch (SQLException e ){
ps .setObject(1, employeeId );
ResultSet rs = ps .executeQuery();
if ( rs .next()){
employee = new Employee();}
employee .setEmployeeId( rs .getInt( "employee_id" ));
employee .setFirstName( rs .getString( "first_name" ));
employee .setLastName( rs .getString( "last_name" ));
employee .setPhoneNumber( rs .getString( "phone_number" ));
ps .close();
rs .close();
e .printStackTrace();
}
return employee;
}
}
4.Provide the Java Bean.
Thisis the class that will live in ourcache. It’s a simple Java Bean which needs to implement Serializable inorderto be able to be shared across the nodes of the cache. In a real-lifescenarioyou will want to implement coherence’s PortableObject instead of onlySerializable, so you will be using coherence ultra-optimized mechanismstoshare/store objects: Portable Object Format Serialization (POF).
Note:getters/setters/constructors areremoved to be easier to read this example. Also note that yourimplementationof toString() is what you are going to see in coherence’s console.
publicclassEmployee implementsSerializable {
int employeeId ;
String firstName ;
String lastName ;
String email ;
String phoneNumber ;
Date hireDate ;
String jobId ;
@Override
public String toString() {
returngetLastName() + ", "}
+getFirstName() + ": "
+getPhoneNumber();
5.Configure Operational File.
Inorder to provide some specificoperational values for your cache, you will need to provide thefollowingoperational configuration file.
Takeparticular attention for the name ofthe cluser “cluster_ucp”, the address/port you will be synchronizingwith othernodes of the cluster “localhost:6699” and the name of the configurationfileyou will use (you set this through the system property parameter“tangosol.coherence.cacheconfig=example-ucp.xml”, defined in 2ndstep.
Note: sinceversion 12.2.1 you no longer needto use prefix “tangosol”.
~/ucpcoherence/tangosol-coherence-override.xml
<?xmlversion='1.0'?>
<!DOCTYPEcoherence SYSTEM "coherence.dtd">
<coherence>
< member-identity >
</member-identity>
<unicast-listener>
< address > localhost </ address >
< port > 6699 </ port >
< time-to-live > 0 </ time-to-live >
< well-known-addresses >
< socket-address id="1">
< address > localhost </ address >
< port > 6699 </ port >
</ socket-address >
</ well-known-addresses ></unicast-listener>
</cluster-config>
<services>
< service id="3">
< service-type > DistributedCache </ service-type >
< service-component > PartitionedService.PartitionedCache </ service-component ></service>
</services>
<configurable-cache-factory-config>
< class-name system-property="tangosol.coherence.cachefactory">com.tangosol.net.ExtensibleConfigurableCacheFactory</class-name>
< init-params > < init-param > < param-type > java.lang.String </ param-type >
< param-value system-property="tangosol.coherence.cacheconfig">example-ucp.xml</param-value>
</ init-param > </ init-params ></configurable-cache-factory-config>
</coherence>
6.Start Cache nodes and client.
Fromcommand line you can start the nodesof your cache through DefaultCacheServer class and the followingparameters:
(from~/ucpcocherence, being./ucp_samples/bin the output folder for your eclipse project or theplace whereyou have your compiled custom classes)
../jdk1.8.0_60/jre/bin/java-Dtangosol.coherence.override=tangosol-coherence-override.xml-Dtangosol.coherence.cluster=cluster_ucp-cp coherence.jar:../ucp.jar:../ojdbc8.jar:./ucp_samples/bincom.tangosol.net.DefaultCacheServer
Note:asstated before, since 12.2.1 you nolonger need to use tangosol prefix, it will also work withcoherence.cluster.
Youcan check by the output the name ofthe cluster (cluster_ucp), the addresses it’s listening(localhost:6699) andthe name of the member you just started (Id=1).
Ifyou issue the same command on aseparate process to start 2nd node:
Youcan check member id = 2 joining thecluster (it shows in both processes). This way now you have 2 nodesworking forthis cluster.
Lastly,you start the client for thecluster (which indeed is a new member itself) using CacheFactory classandrunning this command:
../jdk1.8.0_60/jre/bin/java-Dtangosol.coherence.override=tangosol-coherence-override.xml-Dtangosol.coherence.cluster=cluster_ucp -cpcoherence.jar:../ucp.jar:../ojdbc8.jar:./ucp_samples/bincom.tangosol.net.CacheFactory
Notethat if you wouldn’t want yourclient to be a storage member you can provide the option-Dtangosol.coherence.distributed.localstorage=false to achieve thisbehavior.This can also be done via configuration.
Yousee member id = 3 with command lineoption to interact with the cache.
Thefirst thing you need to do is tostart a cache with the same scheme as the one we defined. In order todo thatyou use the same name pattern we defined in cache-mapping using a namematching“test*”. So you issue:
cachetest1
Andyou will notice now that the promptis with this cache
Map(test1):
Nowyou try to load the first object byissuing:
get100
You’llnotice for each new object loadedin the cache the additional time it takes to read its value from thedatabaseand you’ll see the output of the object retrieved in the console(executingEmployee.toString() method).
Runagain get 100 and you’ll notice thedifference in the response time of using an object that’s already inthe cache.
7.Interact with the cache from Java code
Tointeract with the cache from a javaclass it’s even easier. The only thing you should do is add the VMparameter-Dtangosol.coherence.override=tangosol-coherence-override.xml (pointingto thesame one that started the nodes) to the following code:
publicclassEmployeeQueryCache {
public static voidmain(String[] args){
//define cache
NamedCache cache= CacheFactory.getCache("test1");//retrieve object
Employee employee= (Employee) cache.get(101);//update it
employee.setPhoneNumber("(650)-500-5000");//print in console
System.out.println(employee);//store it
cache.put(101, employee);}
}
Sincecoherence 12.2.1 the interfaceNamedCache supports generics, so you might be able to update previouscodewith:
NamedCache<Integer,Employee> cache = CacheFactory.getTypedCache("test1",TypeAssertion.withoutTypeChecking());