Author: Renato Brunella
This tutorial demonstrates how immutable objects can be mapped. Immutable objects get their state during initialzation through parameters in the constructor. The example is an exchange rate calculator that reads the exchange rates from the database.
You can find the source code in the
Code Repository
in the sf.qof.tutorial.immutable
package.
ExchangeRateCalculator.java | This file contains the main command line control loop. |
Currency.java | An immutable currency object. |
CurrencyCode.java | A currency code enumeration (USD, GBP, etc). |
DatabaseSetup.java | Database setup. |
Make sure you built the tutorial jar using Ant.
Use
java -cp dist/qof-tutorial-1.0.0.jar;lib/qof-1.0.0.jar;lib/cglib-2.1_3.jar;lib/asm-1.5.3.jar;lib/hsqldb.jar sf.qof.tutorial.immutable.ExchangeRateCalculator
to run the example (replace ; with : on Unix based systems).
Alternatively under Windows you can use the run.bat
batch file:
run.bat sf.qof.tutorial.immutable.ExchangeRateCalculator
The Currency
class is immutable i.e. once it is initialized with a state this state cannot be changed anymore.
Therefore it cannot have any setter methods and the state must be passed in through the constructor:
In the Introduction we saw an example of how to map database columns to fields in objects using setter methods and here we will show how the mapping to parameters in a constructor work. Further we will show how to use custom mapping adapters to map enumerations.
The ExchangeRateCalculator
class has a main
method that creates a new instance of
ExchangeRateCalculator
and calls run()
displaying all supported currencies and waits
for some user input.
The constructor of ExchangeRateCalculator
first sets up the database with default values then
registers a custom mapping adapter and initializes the currency cache with the values from the database:
private ExchangeRateCalculator() throws SQLException { DatabaseSetup.setup(); ExchangeRateDAO dao = QueryObjectFactory.createQueryObject(ExchangeRateDAO.class); Connection connection = DataSourceFactory.getDataSource().getConnection(); dao.setConnection(connection); currencyCache = dao.retrieveExchangeRates(); connection.close(); } private Map<CurrencyCode, Currency> currencyCache;
The currecy cache is defined as a map with a key of CurrencyCode
enumeration and value of Currency
.
Let's have a look at ExchangeRateDAO
the query interface definition to see how we can initialize
the currency cache:
private interface ExchangeRateDAO extends BaseQuery { @Query(sql = "select currency_code {enum%%1,enum%%*}, " + "exchange_rate {double%%2} from exchange_rate") Map<CurrencyCode, Currency> retrieveExchangeRates() throws SQLException; }
The SQL select statement simply selects two columns currency_code
and exchange_rate
from
the exchange_rate table. We want to use the currency_code
column as the key for values in the result Map
.
This is done using two percent characters followed by a star and because enumeration mapping uses a custom mapping adapter
we need to specify the type "enum": {enum%%*}
.
The value in the map is of type Currency
and we need to use the constructor:
public Currency(CurrencyCode currencyCode, double amount)
It takes two parameters of type CurrencyCode
and double
. To map to a parameter in the constructor
you need to define a result mapping with type followed by two percent charaters and followed by the parameter index:
{enum%%1}
and {double%%2}
. The type must to be defined to enable QueryObjectFactory to
select the correct constructor.
QueryObjectFactory will try to find a matching constructor in Currency
that has two parameters of type Enumeration
and double
. If no matching constructor can be found an exception is thrown.
The result set column currency_code
is mapped to both the key in the map and the first parameter in the constructor
of Currency
. This is done using {enum%%1,enum%%*}
.
To enable mapping of enumerations we use the EnumMappingGenerator
custom mapping adapter. This adapter is pre-registered.
The EnumMappingGenerator
custom mapping adapter uses the valueOf(String)
and name()
methods
of an enumeration to map to a string database column.
Registration of custom mapping adapter applies to all code generation in a application. To unregister an adapter use
QueryObjectFactory.registerMapper(String typeName)
.