Search

about
article
documents
projects

Audio
Images
Software
Travel
Video

Hey Tiger
It's like musical pop rocks in your jack and coke!

Kristin Hoffmann
Voice. Piano. Guitar.

Mike Ferraro
indie rock

s-s-s-spectres
Just like Dad's beer can collection, except without the dust... and louder.

Asako Kohno
J-Power

c2677
Some links are stronger than others

Java 5 Database Backed Enumerations

A pattern to associate enumerations with database tables at runtime in Java 5

As a warning I feel obliged to state that the below strikes me as over-engineered. I am currently looking for simpler ways of doing the same thing in Java and would appreciate feedback.

A quick refresher on some new Java 5 features we will use:

Enumerations

Java 5 adds an enumeration type which is quite useful for keeping track of fixed sets of values. Compared to the old Java 4 typesafe Enum pattern of using Strings or ints to define a list of values, it wins hands down.

Generics

Another new feature in Java 5 is Generics support. The Java 5 enumeration class is one of the built in classes that uses generics. In the simple cases, generics can really help readability without sacrificing type safety. Not surprisingly, Sun recommends you "use generics everywhere you can." In more complex cases, it can get messy fast. Read through the Java 5 Generics PDFif you have any doubts about that.

EnumMaps

An EnumMap is a new Collection class in Java 5 which is similar to a HashMap except it uses Enum values as keys.

Enhanced For Loop

This is easily my favorite Java 5 feature which was sorely lacking.

Motivation

In almost every enterprise application, certain database tables represent valid states of other fields. I have heard these referred to as dropdown, lookup, or source tables among others. I'll be calling them Lookup tables because as far as I know that is the most common. Typically these tables have an id, name and description field and rarely get updated.

Similarly, Java programmers often have this data in their code. Usually it is represented using the old Java 4 Enumeration Pattern, a collection of beans, or simply as arrays of Strings. It would be nice to consolidate both of these into a single authoritative source.

What follows is a pattern for moving the responsibility for the id and description fields out of Java and into the database using Java 5 Enums. The mapping between the Enum and database happens at runtime between the developer friendly name column and the Enum values.

Prerequisites

Obviously will need to be using Java 5. Furthermore, your lookup tables should have at least an id and name field. You should also have some sort of DAO or perhaps an entity manager for returning beans from the database. In this example code I am using a DAO.

Program Structure

For the example code I am assuming we have a table of statuses like this:

  id   name       description
  -------------------------
  1    stopped    not running
  2    starting   loading data
  3    running    running
  4    stopping   shutting down
In Java, we build an enum class to mirror it:
  
  public enum Status {
  STOPPED,
  STARTING,
  RUNNING,
  STOPPING
  }
The first thing that we set up is an interface to define a Lookupable object. This will be implemented by any beans that we want to fetch.
  public interface Lookupable {
  public abstract String getDescription();
  public abstract int getId();
  public abstract String getName();
  }
We can add this to any existing bean in our system and it will be supported. For the most part though, all of the Lookup tables only have these three fields so it makes sense to create a single reusable bean to represent a row in any of the Lookup tables.
public class LookupImpl implements Lookupable {
  private int id;
  private String name;
  private String description;
  
  public LookupImpl(int id, String name, String description) {
    this.id = id;
    this.name = name;
    this.description = description;
  }

  public String getDescription() {
    return description;
  }

  public int getId() {
    return id;
  }

  public String getName() {
    return name;
  }  
}

Next we will create an interface to mark our DAO's as capable of participating in the system.

public interface LookupDAO {
  public abstract List<? extends Lookupable> getAll() throws SQLException;
}
This is our fist glimpse of the Java 5 generics. It simply states that the DAO will have a method called getAll() that returns a List of unknown objects that extend Lookupable. In the StatusDAO we will implement this method. The important thing is that a List of beans gets returned. Depending on your system it may look something like this:
public List<? extends Lookupable> getAll() throws SQLException {
  List<Lookupable> list = new ArrayList<Lookupable>();
  ResultSet rs;
  PreparedStatement ps = connection.prepareStatement("select * from status")
  while (rs.next()) {
    list.add(new LookupImpl(rs.getInt("id"),
                            rs.getString("name"),
                            rs.getString("description")));
  }
  rs.close();
  return list;
}
The generics above are an example of how they are typically used. The List has been declared to hold a type of Lookupable so the compiler does not complain when we return a List of unknown object that extend Lookupable.

Now for the real meat of the problem. We want to associate each of these beans with the corresponding Java 5 Enum value. The EnumMap I mentioned above is the ideal structure for this. We will also want a utility called Lookup to return the various values: id, name and description when given a particular Enum value. For example we would like to be able to write:

Lookup status = new Lookup(statusDAO, Status.class);
String debug = "The status id is: " + status.getId(ProductStatus.PROCEED)
To begin with we start to define the Lookup
public class Lookup {
  private List<? extends Lookupable> all;     // for holding all of the values returned from the DAO
  private Map map;  // for holding the mapping between the two

  public Lookup(LookupDAO dao, Class clazz) throws SQLException {
    map = new EnumMap(clazz);
    // map the lookups together
    all = dao.getAll();
    for (Lookupable item : all) {
      // look for the Enum value
    }
  }
}
We now have a map of all of the beans that had name columns that matched an Enum value. I've kept the original List returned by the DAO as a private instance variable because it will be useful later on. Since this data changes so infrequently, there is no need to go back to the database for it. I'm using the new for construct to cycle through each bean and examine it.

We haven't actually done the work yet but at this point your IDE is probably already warning that we should parameterize our map. In this case we want to have a map of some enumeration values to a Lookupable bean. Unfortunately, we don't know what type the Enumeration will be yet so we're going to have to use a placeholder for that in the code.

public class Lookup<T extends Enum<T>> {
  private List<? extends Lookupable> all;     // for holding all of the values returned from the DAO
  private Map<T,Lookupable> map;  // for holding the mapping between the two

  public Lookup(LookupDAO dao, Class clazz) throws SQLException {
    map = new EnumMap<T,Lookupable>(clazz);
    // map the lookups together
    all = dao.getAll();
    for (Lookupable item : all) {
      // look for the Enum value
    }
  }
}
Confusingly, the Java 5 Enum class is defined as Enum<E extends Enum<E>> which has been explained pretty well by others. The generic <T extends Enum<T>> in our code above means that each reference to T in the code below is a stand in for a yet to be defined Enum type.

We will force the users of the Lookup utility to specify the type that it is going to hold when they create the Lookup. They will now have to write:

Lookup<Status> status = new Lookup<Status>(statusDAO, Status.class);
String debug = "The status id is: " + status.getId(ProductStatus.PROCEED)
In fact, now is a pretty good time to add a JUnit test to make sure we are implementing all of this correctly. The method I wrote looks like this:
  public void testLookupDecorator() throws SQLException {
      StatusDAO dao = StatusDAO.Factory.getInstance();
      try {
        dao.start();
        Lookup<Status> lookup = new Lookup<Status>(dao, ProductStatus.class);      assertTrue(lookup.getName(Status.STARTING).equals("Starting"));        assertTrue(lookup.getDescription(Status.STARTING).equals("Loading Data"));
      } finally {
        dao.stop();
      }
    }
When we are done writing code, this test should pass. We can now reference T in our code to specify the key's type.
public class Lookup<T extends Enum<T>> {
  private List<? extends Lookupable> all;     // for holding all of the values returned from the DAO
  private Map<T,Lookupable> map;  // for holding the mapping between the two

  public Lookup(LookupDAO dao, Class clazz) throws SQLException {
    map = new EnumMap<T,Lookupable>(clazz);
    // map the lookups together
    all = dao.getAll();
    for (Lookupable item : all) {
      T key = Enum.valueOf(clazz, item.getName().toUpperCase());
        //debug.info(key.toString() + ": added");
        map.put(key, item);
      } catch (IllegalArgumentException e) {
        //debug.warn(item.getName() + ": missing from enum");
      }
    }
  }
}
Here we use the Enum static valueOf method to to pull out the key with a matching name and add it to the map. If there is a database entry without a matching enum key we log the error and continue along. It is up to you to decide if this should be an error condition for your application but I decided that it is usually allowable.

Now that the hard part is done we simply write some accessor methods to get the data in various ways:

  public String getName(T item) {
    debug.info("Size: " + map.size());
    return map.get(item).getName();
  }

  public int getId(T item) {
    return map.get(item).getId();
  }

  public String getDescription(T item) {
    debug.info("Size: " + map.size());
    return map.get(item).getDescription();
  }
Sometimes it is useful to have access to all of the mapped enumerations:
  public Collection<? extends Lookupable> getAllEnums() {
   return map.values(); 
  }
Since we've kept that List of all of the database entries around, we can write a method that shows if any were skipped:
  public boolean isFullMapping() {
    return all.size() == map.size();
  }

Consequences

Further Exploration:

Eclipse-Subversion-Subclipse

A quick tutorial on how to access a subversion repository using Eclipse, Subclipse, and JavaSVN.

(read more..)