Using ReentrantReadWriteLock for Database Caches

Some things never change, even in your database. There are always tables holding geographical data, historical transactions, and mundane status codes that won’t ever be modified for the lifetime of the application. Typically, the data is read from the table at startup and stored in-memory:

public class States{
 private List<StatesTbl>  stateList = null;
 public States(){
      stateList = database.fetchStatesList();  //Fetch a list of the US States
 }

 public List<StatesTbl> getStates(){          //getter for states list
    return stateList;
 }
}

 

Other tables are either far too big or complex to cache, so a database fetch is done for each lookup.

How about tables in between? Things like product codes, business addresses, and telephone numbers don’t change often, so it’s efficient to store them in memory in application startup. When they do change, you want to refresh the memory cache without restarting the application, yet not have the overhead of checking the database each time to see if it’s been updated.

One way to do this is to take advantage of Java’s ReentrantReadWriteLocks.

static private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();  
static private Lock readlock = readWriteLock.readLock();  
static private Lock writelock = readWriteLock.writeLock();

 

Reentrant locks are similar in concept to the Java “synchronize” locks, but with these useful refinements:

  1. Read locks can occur concurrently, instead of one at a time.
  2. Write locks wait until read locks have been released.

So ReentrantReadWriteLocks fit our needs very nicely: controlling access to an object that is read far more often than it’s written to.

An implementation of our modified data cache:

public List<CompanyProductCodesTbl>  getCodes(){
    readlock.lock();
     try{
       return company_codes_list;
     }finally{
       readlock.unlock();
     }
}
public void refreshCodes(){
   writelock.lock();
   try{
      company_codes_list=database.getCompanyCodes(); 
    }finally{
      writelock.unlock();
    }
}

Wrapping the locks around try/finally blocks ensures that locks will always be released in the event that an exception occurs.

How does “refreshCodes()” get executed? In this case it is a manual process. When the database is refreshed for that particular table, care is needed to execute refreshCodes(). Since updates are rare, there could be an administrative console or a data process that triggers this function at the same event the Product Codes table is updated.

This saves an enormous amount of overhead in making sure your seldom changed data is always current in your running application.

Java: Using A HashMap instead of if/else chains or switch

Here in In Theory Yes, one of the functions we do is to generate complex reports and documents. Not surprisingly, any organization of size is going to need to generate a wide variety of documents, often a lot of them at once. The easiest way is to make templates of form letters, and filling in tags or placeholders, and run them through a report generator.

For example, a form letter template may read:

Dear {alumni_name}, please accept this personal welcome from {current_university_president}!

And run through the generator, comes to your e-mail or post  as:

Dear Joe Smith, please accept this personal welcome from Tom Jones!

For junk mail, a “mail merge” function found in Word or similar species against a simple database would be sufficient. But say you want to do this from your own Java application?

One way you could accomplish this is by having a function with a giant if/else/then chain:

String processTag(String inputTag){
  if("{alumni_tag}".equals(inputTag)){
       //...find and return name of alumni
  }else if("{current_university_president}".equals(inputTag)){
       //..find and return name of president
  }else if......
}

 

Or maybe if you were ahead of the curve and are using Java 7:

 
switch(inputTag){
  case: "{alumni_name}" returnVal="...name of alumni"; break;
  case: "{current_president_name" returnVal="..name of president"; break;
  ...
  ...
  default: returnVal="Error! tag "+inputTag+" not found!";
}
return returnVal;

Both examples suffer from a linear search performance penalty. To find a certain tag, all the previous tags need to be evaluated in the chain before the correct one is found. What if a commonly used tag for a certain document was at the very end of several thousand if/else evaluations? Too bad! Go make your users get some coffee while the job runs!

If you were to say that HashMaps and the like in Java are very good at retrieving random, unsorted tags in far faster than linear time and could be used in fetching functions to process tags, you would be correct:

HashMap<String, ReturnTagValue> tagValues = new  HashMap<String, ReturnTagValue>();

The generic signature ReturnTagValue can be any function that implements a user defined ReturnTagValue interface:

public interface ReturnTagValue{
  public String findVal(ClientObject client);
}
 

 

ReturnTagValue contains a single function, findVal(). It could certainly be embellished with other functions but in this case the single function was sufficient. The ClientObject is an object that contains all the reference data needed to determine the correct value for the tag. In the actual implementation, it was a Hibernate object for a given client.

Building the previously initalized tagValues HashMap:

tagValues.put("{alumni_name}", new ReturnTagValue(){
      @Override
       public String findVal(ClientObject client){
         return client.getAlumniName();
       }
     }
 
tagValues.put("{current_university_president}", new ReturnTagValue(){
      @Override
       public String findVal(ClientObject client){
         return client.getCurrentUniversityPresident();
       }
     }
 

 

If a function implements the ReturnTagValue interface, it can be directly referenced:

tagValues.put("{current_university_president}",new GetCurrentPresident());

 

In our report generator, we put it all together:

return tagValues.get("{current_university_president}").findVal(myClient);

On our document generator, this resulted in a nice speed increase, as well as making it easier to add complex tags by regulating them to their own functions as needed.

A nice bonus with this scheme is the ability to get a list of all currently defined tags for meta-introspective purposes such as listing them for a template builder or for documentation:

List<String> listOfTags = new ArrayList<String>(tagValues.keySet());
Collections.sort(listOfTags); //Nicely alphabetize it
return listOfTags;

 

A nice technique to boost performance in the otherwise mundane task of generating complex and voluminous documents!