Java Object Mapping with Orika

In a layered application, you sometimes have similar class models for the same domain entities. One model could be mapped to a database (annotated for persistence), another model could be the XML representation of a RESTful API (annotated for XML/JSON marshaling) or generated from WSDL/XML Schema.

These class models differ in some of their instance variables, types and probably have more or less similar relationships among each other.

If you have such different models, you often need an efficient bidirectional mapping/conversion of objects. Writing conversion code manually is error-prone and leads to a lot of code that needs to be tested and maintained.

Therefore we used the project dozer (http://dozer.sourceforge.net/) for many years in our projects. Dozer is a Java bean to bean mapper that uses reflection to access the objects based on a XML configuration. Dozer is an old open-source project, available since 2005 and thus quite popular.

But the drawback of Dozer is, that - because of reflection - its performance is not as good as it could be and it does not fully support generics (although some generics-support is available). So it feels a little old-fashioned and cumbersome. We ran into some bugs with Dozer that could not be fixed easily because of its cluttered implementation and wild grown internal APIs.

The good news is that serveral alternatives exist for object-to-object mapping and my favorite is Orika (http://code.google.com/p/orika/). Although this project is quite new (since 2012), it has a very stable code base, offers all relevant features and is very easy to understand. Even in large projects that already use Dozer, a switch to Orika is possible within a short time. Our experiences with Orika in real projects are very good, so we think this framework is worth to be well-known and mentioned.

The main difference is its clear API and good performance, comparable with manually-written code. Orika is using byte code generation during runtime to access the objects under the hood.

You can see some benchmarks between Dozer and Orika here: http://blog.sokolenko.me/2013/05/dozer-vs-orika-vs-manual.html.

While Dozer has a configuration API based on XML and a programmatic API, Orika only has a programmatic declarative mapping configuration.

You can get started using Orika using the documentation (http://orika-mapper.github.io/orika-docs/intro.html).

You can easily configure Orika with the springframework, although there is no explicit support for it yet. Just implement an interface:

import ma.glasnost.orika.MapperFactory;
public interface MappingConfigurer {
  void configure(MapperFactory factory);
}

 

Orika and the springframework

You can create a fully configured MapperFacade based on multiple MappingConfigurers. A spring factory-bean can be implemented like:

 

import de.viaboxx.mapper.Mapper;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
public class MapperFactoryBean implements FactoryBean<Mapper> {
      private final MappingConfigurer[] configurers;
      @Autowired
      public MapperFactoryBean(MappingConfigurer... configurers) {
           super();
     this.configurers = configurers;
   }

   public Mapper getObject() {
     MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
     for (MappingConfigurer configurer : configurers) {
       configurer.configure(mapperFactory);
     }
     return new OrikaMapper(mapperFactory.getMapperFacade());
   }

   public Class<?> getObjectType() {
    return Mapper.class;
   }

  public boolean isSingleton() {
    return true;
  }
}

By the way:

  • Use a PassThroughConverter to avoid objects from being deep-copied. If the immutable instance is not supported by Orika out-of-the-box, such as a joda-time DateTime, you can register a PassThroughConverter for its class to copy it by reference.

Abstraction

If you want to use an abstraction to exchange the mapping framework, we suggest some interfaces, that are easy to implement with either Dozer or Orika.

1. The general Mapper. It has methods to map into a new instance and an existing instance.

 

public interface Mapper {
  /**
  * Create and return a new instance of type D mapped with the properties of
  * <code>sourceObject</code>.
  * @param sourceObject     the object to map from
  * @param destinationClass the type of the new object to return
  * @return a new instance of type D mapped with the properties of
  *         <code>sourceObject</code>
  */
  <S, D> D map(S sourceObject, Class<D> destinationClass);

  /**
  * Maps the properties of <code>sourceObject</code> onto
  * <code>destinationObject</code>.
  * @param sourceObject      the object from which to read the properties
  * @param destinationObject the object onto which the properties should be mapped
  */
  <S, D> void map(S sourceObject, D destinationObject);
}

2. The Mapper implementation for Orika:

import ma.glasnost.orika.MapperFacade;
public class OrikaMapper implements Mapper {
  private final MapperFacade mapperFacade;

  public OrikaMapper(MapperFacade mapperFacade) {
    this.mapperFacade = mapperFacade;
  }

  @Override
  public <S, D> D map(S sourceObject, Class<D> destinationClass) {
    return mapperFacade.map(sourceObject, destinationClass);
  }

  @Override
  public <S, D> void map(S sourceObject, D destinationObject) {
    mapperFacade.map(sourceObject, destinationObject);
  }
}

CustomMappers

In real projects you probably have to write many conversion code to bridge differences between the models. We recommend to use CustomMappers to do so.

factory.classMap(Person.class, Customer.class)
  .field("firstName", "name")
  .field("dateBirth", "dateOfBirth")
  .customize(new CustomMapper<Person, Customer>() {
    @Override
    public void mapAtoB(Person source, Customer target, MappingContext context) {
      // some mapping logic here...
    }

    @Override
    public void mapBtoA( Customer source, Person target, MappingContext c) {
      // mapping logic here...
   }
}).register();

Summary

If you need to map objects to objects, consider to use Orika.

Kommentare sind deaktiviert