It’s been more than a month since my last post: summer was hot and sunny but I’m back to work now. This week’s post will be about beans conversion.
In the past, I’ve ranted about DTO and their indiscriminate use in applications. I do concede, however, that DTO are sometimes necessary: this article points out some contexts where they might be. Moreover, DTO even may not be enough, bringing the need for View Objects. Such is the case for layered architectures in big projects.
In all cases, conversion between each type of object quickly become not only a chore for the developer but also a nest of potential bugs. The stupidest the code to write, the more likely the developer will not think about it and it will be very hard to find the bug if there’s one. I’ve experienced it before: I remember not so fondly a time when I had to debug a code I did not develop only to discover 2 hours later the bug was caused because a setter had no effect. Using a tool in such a case (such as Eclipse or NetBeans getter/setter generation) is a good protection against those nasty potential bugs.
For bean to bean conversion, there are some tools available:
- Apache Commons Beanutils (historic)
BeantUtilsclass and its
copyProperties()methods (tied to Spring)
Personally, I use Dozer, a "Java Bean to Java Bean mapper" that is more powerful than a simple copy properties and highly configurable.
Note: I will use Dozer in the simplest way possible (no Spring). Using other configurations will of course require different steps.
Setting up Dozer only requires a few actions:
- add the Maven dependency to Dozer
<dependency> <groupId>net.sf.dozer</groupId> <artifactId>dozer</artifactId> <version>5.2.2</version> </dependency>
- add the XML Beans runtime dependency.
I think this is a mistake of the project since it’s a needed dependency for Dozer
<dependency> <groupId>org.apache.xmlbeans</groupId> <artifactId>xmlbeans</artifactId> <version>2.4.0</version> <scope>runtime</scope> </dependency>
- create a XML file called dozerBeanMapping.xml at the root of the classpath
<?xml version="1.0" encoding="UTF-8"?> <mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://dozer.sourceforge.net http://dozer.sourceforge.net/schema/beanmapping.xsd"> </mappings>
In the simplest of cases, you’ll probably don’t have much to do since your different types of object will have the same attributes, both name and type.
The first thing to do is to get an instance of the mapper itself. It is a best practice to make it a singleton: in fact, Dozer offers a singleton factory.
Thereafter, Dozer offers two simple alternatives: either it creates a bean for you (line 3 below) or it fills up an already created bean (line 7).
Mapper mapper = DozerBeanMapperSingletonWrapper.getInstance(); PersonTransferObject personTo = mapper.map(personDo, PersonTransferObject.class); /* PersonTransferObject personTo = new PersonTransferObject(); mapper.map(personDo, personTo) */;
The latter is very interesting when you are using frameworks (such as Struts) that will create beans and pass it in methods you’ll have to override.
Until now, Dozer hasn’t proved more useful than Commons Beanutils or Spring BeanUtils. Its power lies in its configuration abilities. Let’s take a simple example: imagine you need to pass a Transfer Object to a Struts form (I know Struts is outdated but I have to use it in my day-to-day work so please bear with me - the example is usable in other contexts anyway). Most of the time, Struts forms will have strict String attributes so they can be shown to the HTML page and parsed from the HTML page. In substance, a Struts form is a View Object.
Essentially, the problem at hand is how to map a bean with typed attributes to a bean with String attributes. Mapping numeric values is automated in Dozer, the real pain lies in the Date attributes. Since Dozer is configurable, just add the following in the mapping file:
<configuration> <date-format>dd/MM/yyyy</date-format> </configuration>
You can argue that, in some cases, you’ll want other formats: no problem, since it’s only the default. Date format can be overriden at class mapping level or even at field mapping level!
In internationalized applications, the previous configuration wouldn’t be enough, since each user should have the date displayed in its own locale. Still, Dozer anticipated this use case and provide you with the mean to choose a mapping.
Until yet, we relied on default mapping: provide two objects and since they have the same attributes, Dozer does its job. In order to choose between mappings, you’ll have to explicitly declare them in your mapping file:
<mapping> <class-a>ch.frankel.blog.dozer.domain.PersonTransferObject</class-a> <class-b>ch.frankel.blog.dozer.form.PersonForm</class-b> </mapping>
This configuration won’t change the previous example, it just declares the mapping explicitly. Now, in order to have contextual mapping, just add the map-id attribute of the mapping. And since I explained before you can change date parsing/formatting at mapping level, let’s do it too:
<mapping map-id="en" date-format="MM/dd/yyyy"> <class-a>ch.frankel.blog.dozer.domain.PersonTransferObject</class-a> <class-b>ch.frankel.blog.dozer.form.PersonForm</class-b> </mapping> <mapping map-id="fr" date-format="dd/MM/yyyy"> <class-a>ch.frankel.blog.dozer.domain.PersonTransferObject</class-a> <class-b>ch.frankel.blog.dozer.form.PersonForm</class-b> </mapping>
In order to use the desired mapping, just pass the appropriate identifier in the mapping call:
mapper.map(personTo, personForm, "en");
If all else fails, you always have the possibility to create your own converter. For example, imagine you want to hide the database primary key of your objects in the presentation layer, relying on a business key in it. Since the primary key is lost, and all you have is the business key, you have to retrieve the PK from the database tier when transforming your Transfer Object back to an Entity.
Custom converters can be configured for the entire application, for specific mapping or even for particular fields; meaning in the latter case you can have the cake and eat it too. For the case presented above, just use this in the configuration file:
<mapping> <class-a>ch.frankel.blog.dozer.domain.PersonTransferObject</class-a> <class-b>ch.frankel.blog.dozer.entity.PersonDataObject</class-b> <field custom-converter="ch.frankel.blog.dozer.converter.PersonPrimaryKeyConverter"> <a>businessId</a> <b>id</b> </field> </mapping>
The custom converter is just a implementation of
ch.frankel.blog.dozer.converter.PersonPrimaryKeyConverterwhich has a single method,
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue, Class<?> destinationClass, Class<?> sourceClass).
This small article hasn’t brushed the surface of what Dozer can do:
- collections mapping
- enumerations mapping
- field exclusion
- custom bean factories
- custom getter/setter methods
- reference mapping
- event framework
- expression language
Regarding the size and use of applications I’m designing, I tend to follow the KISS principle and in most cases, I let my entities be flow through the layers. In other contexts, I’m in favor of using automated conversion tools to reduce both bug probability and developer weariness. Dozer is the best tool I’ve used so far: it’s configurable enough to be used easily in simple use-cases and powerful enough to be used in complex ones. Moreover, its documentation is of excellent quality.
Sources for this article are available here in Eclipse/Maven format.
Posts Tagged ‘dozer’