Teaser Image

qnoid

Markos Charatzas - Edinburgh, UK




/**
     * This class provides a way to retrieve all the values in a result set based
     * on the columns specified and create a new object of type {@code T} based on 
     * that values
     *
     * Classes extending this class should provide an implementation for the 
     * template method {@link #newType(Row)} which must return the object based on 
     * the row's columns
     * @see ResultSetHandler
     */
    public abstract class TemplateResultSetHandler<T> implements ResultSetHandler<T>
    {
        private final List<Column<?>> columns;

        /**
         * @param columns all columns available in the ResultSet
         */
        public TemplateResultSetHandler(List<Column<?>> columns)
        {
            this.columns = columns;        
        }

        /**
         * This method will iterate through all the columns 
         * (as specified in the constructor) in the result set and provide a single 
         * callback to the {@link #newType(Row)} with a Row object holding all the 
         * column values.
         * 
         * @param rs a ResultSet pointing to the row holding the next instance of {@code T}
         * @return an object of type T as returned by the {@link #newType(Row)}
         * @see ResultSet#next()
         */
        @Override
        public final T handle(ResultSet rs) throws SQLException
        {        
            Row row = new Row();
            
            for (Column<?> column : this.columns) {
                row.put(column, rs.getObject(column.name()) );
            }
            
        return this.newType( row );
        }
        
        /**
         * Template method to be overridden and provide the type of object related
         * to the given row.
         * 
         * @param row the row 
         * @return a new object based on the column 
         * @see Row#get(Column)
         */
        protected abstract T newType(Row row);    
    }

    /*
     * Our implementation of a ResultSetHandler (part of DbUtils)
     */
    public class ResultSetListHandler<T> implements ResultSetHandler<List<T>>
    {
        private ResultSetHandler<T> handler;
            
        /**
         * @param handler
         */
        public ResultSetListHandler(ResultSetHandler<T> handler)
        {
            this.handler = handler;
        }

        /**
         * This method will iterate through the result set and return a list of 
         * objects as defined in the {@link ResultSetHandler} used in the 
         * constructor
         * 
         * @see ResultSet#next()
         * @see ResultSetHandler#handle(ResultSet)
         */
        @Override            
        public List<T> handle(ResultSet rs) throws SQLException
        {
            List<T> results = new ArrayList<T>();
            
            while(rs.next()){
                results.add( this.handler.handle(rs) );
            }
            
        return results;
        }        
    }

During the first part of this post we identified 2 classes that we'll use as the corner stone for taking advantage of a heterogeneous container under JDBC.

DbUtils has a ResultSetHandler interface where it clearly states that

Implementations of this interface convert ResultSets into other objects.

which pinpoints exactly where we would want to write the algorithm which populates the a Row instance as well as have a callback to a template method to create its corresponding object.

That template method is what will allow subclasses to create objects with the given Row which is populated with all the values required in a type safe manner!

You may notice that we call ResultSet#getObject(String) to retrieve the value for a given column and think that this is wrongly specified. If you read the Javadoc closely though you will notice that the type of the object is right there and is what allows us to take advantage of the heterogeneous container concept.

This method will return the value of the given column as a Java object. The type of the Java object will be the default Java object type corresponding to the column's SQL type, following the mapping for builtwin types specified in the JDBC specification.

However in order to fail early we choose to cast the object in Row#put.

Also the reason to have the call to ResultSet#next outside of the TemplateResultSetHandler is to handle both single (mostly unique) results and list queries.

In the third and final part of the post we'll see how it all looks like from the client code side.

Source