G Sample CacheStores

Cache stores are used by caches to read and write cache entries to external stores such as a database. The examples on this page illustrate different ways in which you can interact with a cache store.

Note:

Save processing effort by bulk loading the cache. The following examples use the put method to write values to the cache store. Often, performing bulk loads with the putAll method will result in a savings in processing effort and network traffic. For more information on bulk loading, see Chapter 13, "Pre-Loading the Cache."

G.1 Sample CacheStore

This section provides a very basic implementation of the com.tangosol.net.cache.CacheStore interface. The implementation in Example G-1 uses a single database connection by using JDBC, and does not use bulk operations. A complete implementation would use a connection pool, and, if write-behind is used, implement CacheStore.storeAll() for bulk JDBC inserts and updates. "Cache of a Database" provides an example of a database cache configuration.

Example G-1 Implementation of the CacheStore Interface

package com.tangosol.examples.coherence;


import com.tangosol.net.cache.CacheStore;
import com.tangosol.util.Base;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;


/**
* An example implementation of CacheStore
* interface.
*
* @author erm 2003.05.01
*/
public class DBCacheStore
        extends Base
        implements CacheStore
    {
    // ----- constructors ---------------------------------------------------
    /**
    * Constructs DBCacheStore for a given database table.
    *
    * @param sTableName the db table name
    */
    public DBCacheStore(String sTableName)
        {
        m_sTableName = sTableName;
        configureConnection();
        }

        /** 
        * Set up the DB connection.
        */
        protected void configureConnection()
                {
                try
                        {
                        Class.forName("org.gjt.mm.mysql.Driver");
                        m_con = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);
                        m_con.setAutoCommit(true);
                        }
                catch (Exception e)
                        {
                        throw ensureRuntimeException(e, "Connection failed");
                        }
                }


        // ---- accessors -------------------------------------------------------

    /** 
    * Obtain the name of the table this CacheStore is persisting to.
    * 
    * @return the name of the table this CacheStore is persisting to
    */
    public String getTableName()
        {
        return m_sTableName;
        }

    /** 
    * Obtain the connection being used to connect to the database.
    * 
    * @return the connection used to connect to the database
    */
    public Connection getConnection()
        {
        return m_con;
        }


    // ----- CacheStore Interface --------------------------------------------

    /**
    * Return the value associated with the specified key, or null if the
    * key does not have an associated value in the underlying store.
    *
    * @param oKey  key whose associated value is to be returned
    *
    * @return the value associated with the specified key, or
    *         <tt>null</tt> if no value is available for that key
    */
    public Object load(Object oKey)
        {
        Object     oValue = null;
        Connection con    = getConnection();
        String     sSQL   = "SELECT id, value FROM " + getTableName()
                          + " WHERE id = ?";
        try
            {
            PreparedStatement stmt = con.prepareStatement(sSQL);

            stmt.setString(1, String.valueOf(oKey));

            ResultSet rslt = stmt.executeQuery();
            if (rslt.next())
                {
                oValue = rslt.getString(2);
                if (rslt.next())
                    {
                    throw new SQLException("Not a unique key: " + oKey);
                    }
                }
            stmt.close();
            }
        catch (SQLException e)
            {
            throw ensureRuntimeException(e, "Load failed: key=" + oKey);
            }
        return oValue;
        }

    /**
    * Store the specified value under the specific key in the underlying
    * store. This method is intended to support both key/value creation
    * and value update for a specific key.
    *
    * @param oKey    key to store the value under
    * @param oValue  value to be stored
    *
    * @throws UnsupportedOperationException  if this implementation or the
    *         underlying store is read-only
    */
    public void store(Object oKey, Object oValue)
        {
        Connection con     = getConnection();
        String     sTable  = getTableName();
        String     sSQL;
        
        // the following is very inefficient; it is recommended to use DB
        // specific functionality that is, REPLACE for MySQL or MERGE for Oracle
 if (load(oKey) != null)
                {
                // key exists - update
         sSQL = "UPDATE " + sTable + " SET value = ? where id = ?";
                }
        else
                {
                // new key - insert
         sSQL = "INSERT INTO " + sTable + " (value, id) VALUES (?,?)";
                }
        try
                {
                PreparedStatement stmt = con.prepareStatement(sSQL);
                int i = 0;
                stmt.setString(++i, String.valueOf(oValue));
                stmt.setString(++i, String.valueOf(oKey));
                stmt.executeUpdate();
                stmt.close();
                }
        catch (SQLException e)
                {
                throw ensureRuntimeException(e, "Store failed: key=" + oKey);
                }
        }

    /**
    * Remove the specified key from the underlying store if present.
    *
    * @param oKey key whose mapping is to be removed from the map
    *
    * @throws UnsupportedOperationException  if this implementation or the
    *         underlying store is read-only
    */
    public void erase(Object oKey)
        {
        Connection con  = getConnection();
        String     sSQL = "DELETE FROM " + getTableName() + " WHERE id=?";
        try
            {
            PreparedStatement stmt = con.prepareStatement(sSQL);

            stmt.setString(1, String.valueOf(oKey));
            stmt.executeUpdate();
            stmt.close();
            }
        catch (SQLException e)
            {
            throw ensureRuntimeException(e, "Erase failed: key=" + oKey);
            }
        }

        /**
        * Remove the specified keys from the underlying store if present.
        *
        * @param colKeys  keys whose mappings are being removed from the cache
        *
        * @throws UnsupportedOperationException  if this implementation or the
        *         underlying store is read-only
        */
        public void eraseAll(Collection colKeys)
                {
                throw new UnsupportedOperationException();
                }

        /**
        * Return the values associated with each the specified keys in the
        * passed collection. If a key does not have an associated value in
        * the underlying store, then the return map will not have an entry
        * for that key.
        *
        * @param colKeys  a collection of keys to load
        *
        * @return a Map of keys to associated values for the specified keys
        */
        public Map loadAll(Collection colKeys)
                {
                throw new UnsupportedOperationException();
                }

        /**
        * Store the specified values under the specified keys in the underlying
        * store. This method is intended to support both key/value creation
        * and value update for the specified keys.
        *
        * @param mapEntries   a Map of any number of keys and values to store
        *
        * @throws UnsupportedOperationException  if this implementation or the
        *         underlying store is read-only
        */
        public void storeAll(Map mapEntries)
                {
                throw new UnsupportedOperationException();
                }

    /**
    * Iterate all keys in the underlying store.
    *
    * @return a read-only iterator of the keys in the underlying store
    */
    public Iterator keys()
        {
        Connection con  = getConnection();
        String     sSQL = "SELECT id FROM " + getTableName();
        List       list = new LinkedList();
        
        try
            {
            PreparedStatement stmt = con.prepareStatement(sSQL);
            ResultSet         rslt = stmt.executeQuery();
            while (rslt.next())
                {
                Object oKey = rslt.getString(1);
                list.add(oKey);
                }
            stmt.close();
            }
        catch (SQLException e)
            {
            throw ensureRuntimeException(e, "Iterator failed");
            }

        return list.iterator();
        }

    
    // ----- data members ---------------------------------------------------

    /**
    * The connection.
    */
    protected Connection m_con;

    /**
    * The db table name.
    */
    protected String m_sTableName;

        /**
        * Driver class name.
        */
        private static final String DB_DRIVER   = "org.gjt.mm.mysql.Driver";

        /**
        * Connection URL.
        */
        private static final String DB_URL      = "jdbc:mysql://localhost:3306/CacheStore";

        /**
        * User name.
        */
        private static final String DB_USERNAME = "root";

        /**
        * Password.
        */
        private static final String DB_PASSWORD = null;
    }

G.2 Sample Controllable CacheStore

This section illustrates the implementation of a controllable cache store. In this scenario, the application can control when updated values in the cache are written to the data store. The most common use case for this scenario is during the initial population of the cache from the data store at startup. At startup, there is no need to write values in the cache back to the data store. Any attempt to do so would be a waste of resources.

The Main.java file in Example G-2 illustrates two different approaches to interacting with a controllable cache store:

  • Use a controllable cache (note that it must be on a different service) to enable or disable the cache store. This is illustrated by the ControllableCacheStore1 class.

  • Use the CacheStoreAware interface to indicate that objects added to the cache do not need to be stored. This is illustrated by the ControllableCacheStore2 class.

Both ControllableCacheStore1 and ControllableCacheStore2 extend the com.tangosol.net.cache.AbstractCacheStore class. This helper class provides unoptimized implementations of the storeAll and eraseAll operations.

The CacheStoreAware.java file is an interface which can be used to indicate that an object added to the cache should not be stored in the database.

See "Cache of a Database" for a sample cache configurations.

Example G-2 provides a listing of the Main.java interface.

Example G-2 Main.java - Interacting with a Controllable CacheStore

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.net.cache.AbstractCacheStore;
import com.tangosol.util.Base;

import java.io.Serializable;
import java.util.Date;

public class Main extends Base
    {

    /**
     * CacheStore implementation which is controlled by a control cache
     */
    public static class ControllableCacheStore1 extends AbstractCacheStore
        {
        public static final String CONTROL_CACHE = "cachestorecontrol";

        String m_sName;

        public static void enable(String sName)
            {
            CacheFactory.getCache(CONTROL_CACHE).put(sName, Boolean.TRUE);
            }

        public static void disable(String sName)
            {
            CacheFactory.getCache(CONTROL_CACHE).put(sName, Boolean.FALSE);
            }

        public void store(Object oKey, Object oValue)
            {
            Boolean isEnabled = (Boolean) CacheFactory.getCache(CONTROL_CACHE).get(m_sName);
            if (isEnabled != null && isEnabled.booleanValue())
                {
                log("controllablecachestore1: enabled " + oKey + " = " + oValue);
                }
            else
                {
                log("controllablecachestore1: disabled " + oKey + " = " + oValue);
                }
            }

        public Object load(Object oKey)
            {
            log("controllablecachestore1: load:" + oKey);
            return new MyValue1(oKey);
            }

        public ControllableCacheStore1(String sName)
            {
            m_sName = sName;
            }

        }

    /**
     * CacheStore implementation which is controlled by values 
     * implementing the CacheStoreAware interface
     */
    public static class ControllableCacheStore2 extends AbstractCacheStore
        {

        public void store(Object oKey, Object oValue)
            {
            boolean isEnabled = oValue instanceof CacheStoreAware ? !((CacheStoreAware) oValue).isSkipStore() : true;
            if (isEnabled)
                {
                log("controllablecachestore2: enabled " + oKey + " = " + oValue);
                }
            else
                {
                log("controllablecachestore2: disabled " + oKey + " = " + oValue);
                }
            }

        public Object load(Object oKey)
            {
            log("controllablecachestore2: load:" + oKey);
            return new MyValue2(oKey);
            }

        }

    public static class MyValue1 implements Serializable
        {
        String m_sValue;

        public String getValue()
            {
            return m_sValue;
            }

        public String toString()
            {
            return "MyValue1[" + getValue() + "]";
            }

        public MyValue1(Object obj)
            {
            m_sValue = "value:" + obj;
            }
        }

    public static class MyValue2 extends MyValue1 implements CacheStoreAware
        {
        boolean m_isSkipStore = false;

        public boolean isSkipStore()
            {
            return m_isSkipStore;
            }

        public void skipStore()
            {
            m_isSkipStore = true;
            }

        public String toString()
            {
            return "MyValue2[" + getValue() + "]";
            }

        public MyValue2(Object obj)
            {
            super(obj);
            }

        }

    public static void main(String[] args)
        {
        try
            {

            // example 1

            NamedCache cache1 = CacheFactory.getCache("cache1");

            // disable cachestore
            ControllableCacheStore1.disable("cache1");
            for(int i = 0; i < 5; i++)
                {
                cache1.put(new Integer(i), new MyValue1(new Date()));
                }

            // enable cachestore
            ControllableCacheStore1.enable("cache1");
            for(int i = 0; i < 5; i++)
                {
                cache1.put(new Integer(i), new MyValue1(new Date()));
                }

            // example 2

            NamedCache cache2 = CacheFactory.getCache("cache2");

            // add some values with cachestore disabled
            for(int i = 0; i < 5; i++)
                {
                MyValue2 value = new MyValue2(new Date());
                value.skipStore();
                cache2.put(new Integer(i), value);
                }

            // add some values with cachestore enabled
            for(int i = 0; i < 5; i++)
                {
                cache2.put(new Integer(i), new MyValue2(new Date()));
                }


            }
        catch(Throwable oops)
            {
            err(oops);
            }
        finally
            {
            CacheFactory.shutdown();
            }
        }

    }

Example G-3 provides a listing of the CacheStoreAware.java interface.

Example G-3 CacheStoreAware.java interface

public interface CacheStoreAware
    {
    public boolean isSkipStore();
    }