Heterogenous Containers... JDBC (Part 1)
/**
* Defines a column in a table of a specified type under a given name.
*/
public final class Column<T>
{
public static final Column<Integer> newIntegerType(String name){
return new Column<Integer>(Integer.class, name);
}
public static final Column<Long> newLongType(String name){
return new Column<Long>(Long.class, name);
}
public static final Column<Date> newDateType(String name){
return new Column<Date>(Date.class, name);
}
public static final Column<String> newStringType(String name){
return new Column<String>(String.class, name);
}
public static final <E> Column<E> newColumn(Class<E> clazz, String name){
return new Column<E>(clazz, name);
}
private Class<T> clazz;
private String name;
public Column(Class<T> clazz, String name)
{
this.clazz = clazz;
this.name = name;
}
public Class<T> type() {
return clazz;
}
public String name() {
return name;
}
}
/*
* This class emulates a database row holding the columns' values
*/
public class Row
{
/*
* A column can be of arbitrary type therefore we use
* a heterogeneous map
* to hold the different types of values
*/
private final Map<Column<?>, Object> columns;
Row()
{
this.columns = new HashMap<Column<?>, Object>();
}
/**
* Returns the value for the specified column
*
* @param column the column to get the value for
* @return the value of the column
*/
public <T> T get(Column<T> column) {
return column.type().cast( this.columns.get(column) );
}
/**
* Adds a new column value
*
* @param column the column related to the value
* @param value the value under that column
* @throws InvalidColumnType if the column type specified for
* the value is wrong
*/
public <T> void put(Column<T> column, Object value)
{
Class<T> type = column.type();
/*
* early cast just in case the column type is wrongly specified
* as opposed to the actual
*/
try{
T typed = type.cast(value);
this.columns.put(column, typed);
}
catch(ClassCastException e){
throw new InvalidColumnType(
String.format(
"Actual type for column '%s' is %s",
column.name(),
value.getClass().getSimpleName()
));
}
}
}
On the preamble we identified a list that allows us to add any type of object and retrieve it back to its actual type. Let’s see how we can benefit from a similar typesafe heterogeneous container in conjuction with JDBC which has been around well before generics.
First thing is to define a Column class which merely describes a table column. All it takes is its name and its type. In JDBC you use a ResultSet to retrieve the value of a column as you iterate through it.
So the Column class not only specifies the column name you need to get the value from but will also give you the right class to cast it to the correct type!
Second is a Row class which is the actual heterogeneous container holding each column and its value. We’ll use that as we iterate through the ResultSet to put the value for each column. This Row instance is what we’ll also use to query for a column and get its value in the actual type.
In general when thinking of such containers there are two things to have in mind.
- a Map that’s used as a storage for your heterogeneous type objects
- a Class instance to allow you to cast to the actual type
In the second part we’ll see how it all comes together in conjunction with Apache’s DbUtils library to make plain JDBC fun.
Kudos to the Apache Foundation which has been around since 1999 providing all that great open source software making developers’ lives easier.