What is caching
Caching means saving some data or records in a temporary location(referred as a cache) after they are fetched for the first time for later access.
Caching is done so that if a record is needed after being fetched once, then it should not be fetched again from the source location, instead it is read from the cache.
Source location may be a database, a file or a web service.
Caching is performed to speed up performance by avoiding repetitive read operations from all these sources.


Guava Cache
Google’s Guava library provides a caching utility which stores the records after they are fetched for the first time.

If you try to access the same record again, it will not reach out to the actual source(from where it got the record first time) but will return it from its cache.
This article will explain how to use Guava cache and demonstrate that the records are actually being cached with an example.

Guava requires following steps for caching to work.
1. Create a cache builder
This cache builder will be responsible for creating a cache object.
This cache builder shall be an object of com.google.common.cache.CacheBuilder class from guava library.

Cache builder will be created using below code statement.

CacheBuilder.newBuilder();

2. Build a cache loader
Cache will hold the stored data after it is fetched or loaded once. A cache will be created by a cache builder created in step 1.

Cache loader has a build method which creates a cache. This method accepts an argument of type com.google.common.cache.CacheLoader.

CacheLoader is an abstract class which has a load() method.
load() actually returns the data that you need to cache.
load method accepts a single argument which represents the key and loads the data corresponding to that key.

Key could be any value that is unique across the records. It may be a name, phone number, email id etc.
Cache first searches for the record corresponding to the key in cache. If found, returns the record otherwise it tries to fetch the data from actual source.

Code snippet for cache creation is given below.

// creating loader
CacheLoader<String, Object> loader = new CacheLoader<String, Object>() {

   @Override
   public Website load(String key) throws Exception {
      // record fetch logic from source
   }
};
// creating cache
LoadingCache<String, Object> cache = CacheBuilder.
                                     newBuilder().
                                     build(loader);

CacheLoader is an abstract class and therefore we provide an implementation of its load method. load method should contain the actual logic to fetch data from source which may be a database, file, web service call etc.
Note that cache will first look for the record in local storage and will reach out to remote source when the required data is not found locally.

load() method is only called if the record with the given key could not be found in the cache.

3. Get record
When the cache has been created, you need to call its get() or getUnchecked() methods with the specified key as argument and it will return the record for that key as shown below.

Object record = cache.get("user@website.com");

Guava example
Keeping all the above steps together, below is the code example of caching using Google guava library.

public class CachingExample {

  public static void main(String args) {
    new CachingExample().cacheData();
  }

  private void cacheData() {
    try {
      // create builder object
      CacheBuilder<Object, Object> builder = CacheBuilder.
                                             newBuilder();
      // create cache loader object
      CacheLoader<String, Website> loader = new CacheLoader<String, Website>() {
        @Override
        public Website load(String key) throws Exception {
          // get data from source
          return fetchWebsite(key);
        }
      };
      // create cache
      LoadingCache<String, Website> cache = builder.
                                            build(loader);
      // fetch websites from cache
      Website w = cache.get("Google");
      System.out.println("Website fetched is: " + w.getName());
      w = cache.get("Facebook");
      System.out.println("Website fetched is: " + w.getName());
      // fetch a website once again
      System.out.println("Trying to get same data again...");
      w = cache.get("Google");
      System.out.println("Website fetched is: " + w.getName());
      System.out.println("Total items in cache: " + cache.size());      
    } catch(ExecutionException e) {
        e.printStackTrace();
    }

  // fetch object
  Website fetchWebsite(String name) {
    System.out.println("Fetching website from source: " + name);
    return new Website(name);
  }
}

class Website {
   private String name;
   /*
   * Constructor
   **/
   public Website(String n) {
      name = n;
   }

   public String getName() {
      return name;
   }
}

Above code example creates a cache using the steps explained in the previous section. Note that the cache is of type Website which is a separate class.

load() method of cache loader also returns an object of Website. Method fetchWebsite() is called from inside load() method which simulates fetching of source data.

Remember that fetchWebsite() will only be called when the cache could not find a record in its local storage.

In practical applications, it will be replaced by database fetch, web service call or a file read mechanism and instead of Website object,  actual object will be returned.

The key for fetching records is the name of Website but it could be any unique value throughout the records so that searching logic may identify the record that needs to be returned.

Note that the method fetchWebsite() prints a message to the console to show that the cache is trying to read data from the source.
Above example also tries to get same record again by invoking get() method twice with the same key to demonstrate if caching actually works and below is the output.

Fetching website from source: Google
Website fetched is: Google
Fetching website from source: Facebook
Website fetched is: Facebook
———————–
Trying to get same data again…
Website fetched is: Google
Total items in cache: 2

Note that second time for the same key, cache does not reach out to the source but returns data from local storage only.
Size of the cache also shows the total items present in it.


Eviction Strategies
Cache removes or evicts the records automatically to free up space based on the configured eviction strategy. You can consider eviction strategy as a limit for the cache.
Following are the common eviction policies that may be configured with the cache.
1. Evict by size
Set maximum number of records that the cache should hold. After the limit is reached, cache will remove(or evict) the items.
If a record is removed from cache, then when you try to fetch it, the cache will again load it from the source location since it will not be found in the cache.
In order to set this eviction strategy on the cache, use maximumSize() method on cache builder as shown below.

CacheBuilder<String, Website> builder = CacheBuilder.
                                        newBuilder().
                                        maximumSize(3);

If the size is set to 0, then the cache will not hold any items and evict them as soon as they are loaded. Example for this strategy is shown below.

// create builder object
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumSize(2);
// create cache loader object
CacheLoader<String, Website> loader = new CacheLoader<String, Website>() {
   @Override
   public Website load(String key) throws Exception {
      // get data from source
      return fetchWebsite(key);
   }
};
// create cache
LoadingCache<String, Website> cache = builder.build(loader);
// fetch websites from cache
try {
   Website w = cache.get("Google");
   System.out.println("Website fetched is: " + w.getName());
   w = cache.get("Facebook");
   System.out.println("Website fetched is: " + w.getName());
   w = cache.get("Youtube");
   System.out.println("Website fetched is: " + w.getName());
   // fetch a website once again
   System.out.println("-----------------------");
   System.out.println("Trying to get same data again...");
   w = cache.get("Google");
   System.out.println("Website fetched is: " + w.getName());
   System.out.println("Total items in cache: " + cache.size());
} catch (ExecutionException e) {
   e.printStackTrace();
}

In the above example, set a maximum size of 2 for the cache and load 3 records into it.
Now when you try to fetch a previously loaded record, it will reach out to the source to fetch the record.

Below output shows this

Fetching website from source: Google
Website fetched is: Google
Fetching website from source: Facebook
Website fetched is: Facebook
Fetching website from source: Youtube
Website fetched is: Youtube
———————–
Trying to get same data again…
Fetching website from source: Google
Website fetched is: Google
Total items in cache: 2

Note that when a third record is loaded into the cache, it removes the first record and tries to fetch it again. Also, look that the total size of cache is equal to the maximum size configured.
2. Evict by time
Set the maximum time for which the items will be kept in cache. If an item is not accessed for the specified time, it will be removed from the cache.
Invoke expireAfterWrite() method on cache builder with the amount and unit of time after which the record will be removed from cache. Example,

// create builder object
CacheBuilder<Object, Object> builder = CacheBuilder. 
                                       newBuilder().
                                       expireAfterWrite(3, TimeUnit.SECONDS);
// create cache loader object
CacheLoader<String, Website> loader = new CacheLoader<String, Website>() {
   @Override
   public Website load(String key) throws Exception {
      // get data from source
      return fetchWebsite(key);
   }
};
// create cache
LoadingCache<String, Website> cache = builder.build(loader);
// fetch websites from cache
try {
   Website w = cache.get("Google");
   System.out.println("Website fetched is: " + w.getName());
   w = cache.get("Facebook");
   System.out.println("Website fetched is: " + w.getName());
   w = cache.get("Youtube");
   System.out.println("Website fetched is: " + w.getName());
   System.out.println("Waiting for 5 seconds...");
   try {
      TimeUnit.SECONDS.sleep(5);
   } catch(Exception e) {
      e.printStackTrace();
   }
   // fetch a website once again
   System.out.println("-----------------------");
   System.out.println("Trying to get same data again...");
   w = cache.get("Google");
   System.out.println("Website fetched is: " + w.getName());
   System.out.println("Total items in cache: " + cache.size());
} catch (ExecutionException e) {
   e.printStackTrace();
}

Above example sets the expiration time of 3 seconds.

After fetching items, it sleeps or halts the thread execution for 5 seconds, using any of the methods to make a thread sleep and then tries to fetch the same record again.
From the output it is clear that the record has been removed from the cache and it needs to reach out to the source to fetch it again.

 

Fetching website from source: Google
Website fetched is: Google
Fetching website from source: Facebook
Website fetched is: Facebook
Waiting for 5 seconds
———————–
Trying to get same data again…
Fetching website from source: Google
Website fetched is: Google
Total items in cache: 2

Important methods
Other than the get method discussed above, following is a list of useful methods that can be invoked on cache object.

1. getUnchecked(Key)

This method is used to return the value for the given key from the cache. Key is supplied as argument. It is similar to get method, difference is that it does not throw checked exception.

2. asMap()

Returns a map view of the cache where key is the same as that used to fetch record from the cache and value is the record stored in cache.

3. getIfPresent(Key)

It returns the value associated with a given key or null if the value is not present in the cache. Unlike get() or getUnchecked() method, this method does not try to fetch the record from the source.
Key is supplied as the argument.

4. invalidate(Key)
It accepts a key as argument and removes the record corresponding to that key from the cache.

5. invalidateAll()

This method does not receive any argument and removes all the entries from the cache.

6. refresh(Key)

This method receives a key as argument and tries to reload the record for the given key from the source. While the record is being reloaded, any call to get or getUnchecked() will return the record from the cache.

7. put(Key, Value)

This method is used to add an entry to the cache. It receives two arguments where the first argument is the key and second argument is the record associated with the key and adds the new record to the cache.

Hope you found the article useful and informative.