21 Feb 2014

Copying objects of different classes with Dozer

  • Advance


Sometimes we need to copy the contents of a bean to another. If the source and target are of the same class we can use a copy constructor or an existing util class like org.apache.commons.beanutils.BeanUtils from Apache Commons BeanUtils or org.springframework.beans.BeanUtils from Spring Framework. This last solution is only recommended if you're in a Java application built with this framework.
However in many situations we need to copy some or all the attributes from an object of class A in an object of class B. It's in this case when we need to build a manual mapper using the getter and setter methods or a library like Dozer. It allows you to customize easily the mapping of the attributes of two objects.

  • Explanation


We have created a little project in the SVN repository where we have simulated a hierarchy of a value object with a collection of children and their matching business objects with a similar form. In the following lines we are going to explain how Dozer can help us to implement a mapper quickly and configuring it with only a XML file. The mapping classes are very similar in field naming but we have change the name of the identifiers intentionally. We have omitted the equals and hashCode methods to be clearer. Finally, the samples are shown as unit tests but the data bean initialization methods have been omitted.

Value objects


public class ChildVO implements Serializable {

 private static final long serialVersionUID = -3066509911454147138L;

 protected Integer persistenceId;
 
 protected Double price;
 
 protected ParentVO parentVO;

 public Integer getPersistenceId() {
  return persistenceId;
 }

 public void setPersistenceId(Integer persistenceId) {
  this.persistenceId = persistenceId;
 }

 public Double getPrice() {
  return price;
 }

 public void setPrice(Double price) {
  this.price = price;
 }

 public ParentVO getParentVO() {
  return parentVO;
 }

 public void setParentVO(ParentVO parentVO) {
  this.parentVO = parentVO;
 }

 /* ... */
}

public class ParentVO implements Serializable {

 private static final long serialVersionUID = 440303735656145958L;

 protected Integer persistenceId;
 
 protected String name;
 
 protected Set childVOs;

 public Integer getPersistenceId() {
  return persistenceId;
 }

 public void setPersistenceId(Integer persistenceId) {
  this.persistenceId = persistenceId;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public Set getChildVOs() {
  return childVOs;
 }

 public void setChildVOs(Set childVOs) {
  this.childVOs = childVOs;
 }

 /* ... */
}

Business objects


public class ChildBO implements Serializable {

 private static final long serialVersionUID = -3066509911454147138L;

 protected Integer businessId;
 
 protected Double price;
 
 protected ParentBO parentBO;
 
 public Integer getBusinessId() {
  return businessId;
 }

 public void setBusinessId(Integer businessId) {
  this.businessId = businessId;
 }

 public Double getPrice() {
  return price;
 }

 public void setPrice(Double price) {
  this.price = price;
 }

 public ParentBO getParentBO() {
  return parentBO;
 }

 public void setParentBO(ParentBO parentBO) {
  this.parentBO = parentBO;
 }

 /* ... */
}

public class ParentBO implements Serializable {

 private static final long serialVersionUID = 440303735656145958L;

 protected Integer businessId;
 
 protected String name;
 
 protected Set childBOs;

 public Integer getBusinessId() {
  return businessId;
 }

 public void setBusinessId(Integer businessId) {
  this.businessId = businessId;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public Set getChildBOs() {
  return childBOs;
 }

 public void setChildBOs(Set childBOs) {
  this.childBOs = childBOs;
 }

 /* ... */
}
  1. Mapper initialization
The mapper should be initialized to use it. Use the following snippet to configure it with a XML file.
 protected final DozerBeanMapper mapper;

 public DozerMappingTest() {
  List myMappingFiles = new ArrayList();
  myMappingFiles.add("dozerBeanMapping.xml");
  this.mapper = new DozerBeanMapper();
  this.mapper.setMappingFiles(myMappingFiles);
 }
  1. Map an object to another of the same class (simple copy)
The following example shows how to copy the properties of a object to another instance.

/**
 * This test shows the copy capability of the library. It doesn't need a XML configuration.
 */
@Test
public void testCopySimpleClass() {
 ChildVO vo = fillChildVO();
 
 ChildVO voPrime = mapper.map(vo, ChildVO.class);
 
 assertThat(vo, equalTo(voPrime));
}
  1. Map an object to another of the same class with a collection of children objects (deep copy)
The following example shows a deep copy of a hierarchy of objects.

/**
 * This test shows the copy with a child element in a collection. It doesn't need a XML configuration.
 */
@Test
public void testCopyParentClass() {
 ParentVO parentVo = fillParentVO();
 ChildVO childVo = fillChildVO(); 
 linkParentAndChildVO(parentVo, childVo);
  
 ParentVO parentVoPrime = mapper.map(parentVo, ParentVO.class);
 
 assertThat(parentVoPrime.getChildVOs(), notNullValue());
 assertThat(parentVoPrime.getChildVOs().size(), equalTo(1));
 assertThat(parentVo, equalTo(parentVoPrime));
}
  1. Map an object to another of different class (simple copy)
In this case the source and target objects are of different classes. The attributes with the same name and type are automatically mapped, but for the different ones we need to specify what are the matching names.

/**
 * This test shows the copy of the simple properties of a class to another. We need to add the following XML to run properly:
 * <mapping>
 *  <class-a>gazpachito.examples.dozer.persistence.ChildVO</class-a>
 *  <class-b>gazpachito.examples.dozer.facade.ChildBO</class-b>
 *  <field>
 *   <a>persistenceId</a>
 *   <b>businessId</b>
 *  </field>
 * </mapping>
 */
@Test
public void testMapSimpleClass() {
 ChildVO childVo = fillChildVO(); 
 
 ChildBO childBo = mapper.map(childVo, ChildBO.class);
 
 assertThat(childBo.getBusinessId(), notNullValue());
 assertThat(childBo.getPrice(), notNullValue());
 assertThat(childBo.getParentBO(), nullValue());
}
  1. Map an object to another of different class with a collection of children objects (deep copy)
For a deep copy we only have to complete the field mappings.

/**
 * This test shows the copy of the properties of the parent class and a child. We need the following XML:
 *  <mapping>
 *  <class-a>gazpachito.examples.dozer.persistence.ChildVO</class-a>
 *  <class-b>gazpachito.examples.dozer.facade.ChildBO</class-b>
 *  <field>
 *   <a>persistenceId</a>
 *   <b>businessId</b>
 *  </field>
 *  <field>
 *   <a>parentVO</a>
 *   <b>parentBO</b>
 *  </field>
 * </mapping>
 * <mapping>
 *  <class-a>gazpachito.examples.dozer.persistence.ParentVO</class-a>
 *  <class-b>gazpachito.examples.dozer.facade.ParentBO</class-b>
 *  <field>
 *   <a>persistenceId</a>
 *   <b>businessId</b>
 *  </field>
 *  <field>
 *   <a>childVOs</a>
 *   <b>childBOs</b>
 *  </field>
 * </mapping>
 */
@Test
public void testMapParentClass() {
 ParentVO parentVO = fillParentVO();
 ChildVO childVO = fillChildVO(); 
 linkParentAndChildVO(parentVO, childVO);
 
 ParentBO parentBo = mapper.map(parentVO, ParentBO.class);
 
 assertThat(parentBo, notNullValue());
 assertThat(parentBo.getBusinessId(), notNullValue());
 assertThat(parentBo.getName(), notNullValue());
 Set childBos = parentBo.getChildBOs();
 assertThat(childBos, notNullValue());
 assertThat(childBos.size(), equalTo(1));
 ChildBO childBo = parentBo.getChildBOs().iterator().next();
 assertThat(childBo.getBusinessId(), notNullValue());
 assertThat(childBo.getPrice(), notNullValue());
 assertThat(childBo.getParentBO(), notNullValue());
 assertThat(childBo.getParentBO(), equalTo(parentBo));
}
  1. Performance
We have compared the Dozer mapper with a manual mapper to measure the overheight of it usage. The profiling library used is JETM with a very basic configuration. The average results were the following:
  • Dozer mapper
|-----------------------|------|---------|-------|-----------|------------|
|   Measurement Point   |   #  | Average |  Min  |    Max    |    Total   |
|-----------------------|------|---------|-------|-----------|------------|
| DozerParentMapper:map | 1000 |  14,627 | 4,479 | 2.107,324 | 14.626,801 |
|-----------------------|------|---------|-------|-----------|------------|
  • Manual mapper
|------------------------|------|---------|-------|-----------|-----------|
|    Measurement Point   |   #  | Average |  Min  |    Max    |   Total   |
|------------------------|------|---------|-------|-----------|-----------|
| ManualParentMapper:map | 1000 |   2,752 | 0,151 | 2.368,851 | 2.751,547 |
|------------------------|------|---------|-------|-----------|-----------|

As you can see, the manual mapper is approximately six times faster but this test is a bit vague.

  • Conclusions

The usage of a mapping library can save us many time in development process but it adds a little overhead. You have to consider it usage but the flexibility of this library eases the mapping of complex classes. We recommend to read the development guide to take advantage of Dozer because this guide only covers a bit of the features of this great Java library.

Thanks to my partner Fernando for the idea to do a performance test.

No comments:

Post a Comment