Skip directly to content

Using the Spring Converter API with VoltDB Data Objects

Wednesday, June 20, 2012 - 12:00am

Written by Andrew Wilson

 

Mapping one type to another is a pretty common task. Hibernate and other ORM’s map a result from a data source’s native representation to an application specific representation. In English, I want to convert a JDBC result set, or data objects, into a collection of POJOs (plain old Java objects) using some kind of data mapping tool or API. Spring exposes a low level service provider interface that makes it very easy to convert one data type to another with either built-in converters or customer converters, allowing a developer to support just about any conversion one can think of. Today I am writing about how to convert VoltDB’s VoltTable result object into an application specific POJO using the Spring Converter API.

 

Why even have a converter API in any application? A fairly common pattern in any database backed application is the value object (VO) to data access object (DAO) mapping. A similar pattern is the transfer object to entity pattern. The data layer object is generally a mapping of fields to database table columns or query results. That data layer object may contain additional objects that map to other tables too. It may also expose methods, either explicitly or implicitly, to perform the usual CRUD operations or it can rely on a service to accomplish those operations.

The application layer object may include some or all of the data layer fields as well as application layer specific fields. A data layer object may represent a student and the grades she got on all her tests while the application layer object may represent the student, the grades and then additional fields like the average, highest and lowest grades for each of those tests. The importance of the converter API is that it makes it possible to abstract the link between the data access layer and the application layer objects such that either layer can be swapped out or updated without impacting the other layer. Further, changes to the application layer object may not need to be persisted to the database while changes to the DAO are often persisted to the database.

Use Spring’s Converter API for Data Layer Abstraction

A converter API makes it much easier to integrate and replace or add data layers without breaking existing application layer objects. Here we are showing how to integrate VoltDB into a web application that tracks votes for a candidate. The resulting application could be replacing or enhancing an existing data layer without requiring changes at the application layer.

 

Spring’s Converter API has some default converters for managing simple data type conversions and exposes an interface for implementing custom object conversions. Each custom converter is registered with the Spring native ConversionService. In my case, I’m going to register them via an xml configuration.

 

<beans:bean id="conversionService">
<beans:property name="converters">
<beans:list>
<beans:bean
class="org.voltdb.examples.converter.VoltTableToElectionResultsConverter"/>
<beans:bean
class="org.voltdb.examples.converter.VoltTableToCandidateResultConverter”/>
</beans:list>
</beans:property>
</beans:bean>
- See more at: http://voltdb.com/blog/using-spring-converter-api-voltdb-data-objects#sthash.v58DoR5a.dpuf

 

The above XML registers the VoltTableToElectionResultsConverter and VoltTableToCandidateResultConverter classes with the ConversionServiceFactoryBean. We will use an instance of ConversionService to get access to our converters. The ConversionService is instantiated via the ConversionServiceFactoryBean.

Converting Query Results to Application Objects

The two converters take a VoltTable instance and create a hierarchy of objects. The VoltTableToElectionResultsConverter will take a VoltTable and convert it into an ElectionResults object. The ElectionResults object is made up of a collection of CandidateResult objects. Let’s take a look at how to convert the results of a query into the ElectionResults and CandidateResult objects.

 

@Autowired
private ConversionService conversionService;
 
public ElectionResults getResults() throws Exception {
ElectionResults results = new ElectionResults();
ClientResponse response = this.client.callProcedure("Results");
VoltTable[] tables = response.getResults();
if (tables != null && tables.length > 0) {
VoltTable voteTable = tables[0];
results = this.conversionService.convert(voteTable,
ElectionResults.class);
}
return results;
}

 

The above code synchronously calls a stored procedure through the VoltDB client. We check that the results are valid and request that the ConversionService find the VoltTable-to-ElectionResults converter and then performs the entire conversion from VoltTable instance to ElectionResults.

 

public class VoltTableToElectionResultsConverter implements
 
Converter {
 
@Autowired
ApplicationContext context;
 
@Override
public ElectionResults convert(VoltTable table) {
// The following gets around an issue where this converter
// depends on another converter
ConversionService conversionService = (ConversionService) context
.getBean("conversionService");
 
ElectionResults voteResults = new ElectionResults();
CandidateResult[] candidateResults = new CandidateResult[table
.getRowCount()];
voteResults.setCandidateResults(candidateResults);
int index = 0;
while (table.advanceRow()) {
candidateResults[index++] = conversionService.convert(table,
CandidateResult.class);
}
return voteResults;
}
}

 

The VoltTableToElectionResultsConverter implements the Converter interface specifying the source and target classes for the conversion. It then exposes the required convert() method. Note that the code is getting the ConversionService through the ApplicationContext rather than autowiring the ConversionService. This is actually important when working with conversion hierarchies because referencing the ConversionService directly from a containing object will not work. It makes sense because the ConversionService is initializing the underlying Converter during the ConversionService’s initialization. Consequently, we resolve the ConversionService at the invocation of the convert() method to work around this order of operations issue.

 

The VoltTableToElectionResultsConverter then iterates over each row, converting the row into a CandidateResult instance.

 

public class VoltTableToCandidateResultConverter implements Converter<VoltTable, CandidateResult> {
 
@Override
public CandidateResult convert(VoltTable table) {
CandidateResult result = new CandidateResult();
result.setCandidateName(table.getString(0));
result.setTotalVotes(table.getLong(2));
return result;
}
}

 

The VoltTableToCandidateResultConverter is much shorter. It just needs needs to extract the CandidateResult fields from the current row. The difficulty in this conversion is that we have to extract the values from accessor methods that require a position rather than the more typical mapping based on field names.

 

The effect is that just by invoking the ElectionResults converter we get a complete conversion from the data layer VoltDB VoltTable object into an application layer ElectionResults object containing a collection of rows represented by CandidateResult objects.

Summary

The Spring Converter API buys an application architecture a simple way to drop in new data layers without having to rewrite the application layer objects, providing a mechanism for evolving applications. It also encourages developers to write very small converter components making the code easier to maintain and much more readable.