Como mapear beans con Orika

En varios proyectos en los que he trabajado últimamente hemos necesitado de alguna librería para simplificar y automatizar el mapeo de beans. En concreto he usado Dozer varias veces, y últimamente Orika, que es del que voy a implementar y explicar un pequeño ejemplo básico.

Concepto:
Durante el desarrollo de cualquier proyecto con cierto tamaño, nos encontramos con la necesidad de hacer algún tipo de ‘transformación’ o ‘mapping’ de unos beans a otros, que realmente son, en cuanto a datos, lo mismo. Por ejemplo, entities que para enviarlas a un servicio queremos pasarselas con un DTO o similar.

En estos casos, es necesario convertir continuamente esas entities en DTOs. Para hacer esto tendríamos que hacer código muy simple, pero muy repetitivo, seteando valores del uno en el otro. Esto, a la larga, a parte de ser tedioso, llena nuestro código de lineas ‘boilerplate’, que no aportan nada a cualquiera que quiera leerlo.
Para evitar esto, tenemos librerías para el mapeo de beans, como son Dozer u Orika. En el caso del primero, la configuración puede hacerse toda por xml, o por anotaciones en los beans. Por su parte, Orika, te provee de una manera de configurar el mapeo de beans mediante código, aparte de, según parece, tener un rendimiento mucho mejor que Dozer.

En el siguiente ejemplo voy a implementar un mapeo de beans utilizando Orika y explicando como configurar el mapeo, todo por código, sin usar ningún xml ni anotación. ¡Al lio!

Entorno usado:
Java JDK 1.8
Maven 3.2.5
Git 1.9.5
IDE Intellij 14.1.3 Ultimate version

Pasos:

1. Creamos un proyecto maven básico, usando el ‘quickstart’ de maven, o con el maven-archetype-edwise-quickstart. En el pom, añadimos la dependencia para la librería de Orika:

        <dependency>
            <groupId>ma.glasnost.orika</groupId>
            <artifactId>orika-core</artifactId>
            <version>1.4.6</version>
        </dependency>

2. Tendremos dos beans, que son los que usaremos en el ejemplo, mapeando los datos de uno a otro:

public class SourceEntity {
    private Long id;
    private String name;
    private String userSurname;
    private String[] options;
    private EntityType entityType;
    private LocalDateTime creationDateTime;
    private Integer[] nums;

    // getters and setters...

    // toString method implementation...

    public class EntityType {

        private int id;
        private String type;

        // getters and setters...
    }
}

public class DestinationDTO {
    private Long id;
    private String name;
    private String surname;
    private int type;
    private List<String> options;
    private LocalDate creationDate;
    private Integer firstNum;

    // getters and setters...

    // toString method implementation...
}

Como vemos, los beans tienes datos de varios tipos, y entre si no son exactamente iguales, ni en estructura, ni en tipo de datos… ahora veremos como podemos hacer el mapeo.

3. En Orika, para configurar el mapeo, usaremos un ‘builder’, ClassMapBuilder. Vamos a empezar con un mapeo básico, para explicarlo:

    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

    mapperFactory.classMap(SourceEntity.class, DestinationDTO.class)
            .field("userSurname", "surname")
            .byDefault()
            .register();

Lo primero es crear un MapperFactory, que será nuestra «configuración» de Orika.
Con el método ‘classMap‘ (al que le pasamos la clase origen y la clase destino), usaremos ese ClassMapBuilder para informar a Orika como debe mapear nuestros beans. En este caso le decimos solo que mapee el campo ‘userSurname’ en el campo destino ‘surname’.
Con el método ‘byDefault‘ le decimos que si hay otros campos que tengan nombres iguales, que los mapee directamente (esto afectaría, por ejemplo, al campo ‘name’).
Con ‘register‘ el mapeo queda registrado en nuestra aplicación.

4. Eso sería la parte fácil. Vamos a extenderlo un poco más, mapeando el resto de datos, usando varias opciones distintas de Orika:

    mapperFactory.classMap(SourceEntity.class, DestinationDTO.class)
            .exclude("id")
            .field("userSurname", "surname")
            .field("entityType.id", "type")
            .field("creationDateTime", "creationDate")
            .field("nums[0]", "firstNum")
            .byDefault()
            .register();

Aquí he añadido varios mapeos más:
Con el método ‘exclude‘ le decimos que no mapee el campo id.
En la linea 4, le decimos como mapear el ‘type’, cogiéndolo del ‘id’ del objeto EntityType.
En la linea 6, le decimos que obtenga el primer elemento del array ‘nums’ para mapearlo en ‘firstNum’.

5. Aún nos faltaría algo más, lo anterior daría un error. Tenemos un campo ‘creationDateTime’ que hay que mapear a ‘creationDate’, los cuales son respectivamente de tipos LocalDateTime y LocalDate. No son los mismos tipos, así que aquí necesitaremos implementar un converter de Orika:

package com.edwise.pocs.orikapoc.config;

// imports...

public class LocalDateTimeToLocalDateConverter
        extends CustomConverter<LocalDateTime, LocalDate> {
    public LocalDate convert(LocalDateTime source, 
                             Type<? extends LocalDate> destinationType) {
        return source.toLocalDate();
    }
}

El código es muy sencillo. Simplemente tenemos que heredar de CustomConverter, «tipandolo» con nuestras clases origen y destino, e implementando el método ‘convert‘, que devolverá el dato destino.

Y luego registramos el converter. Nuestra configuración completa de Orika quedaría entonces así:

    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

    ConverterFactory converterFactory = mapperFactory.getConverterFactory();
    converterFactory.registerConverter(new LocalDateTimeToLocalDateConverter());

    mapperFactory.classMap(SourceEntity.class, DestinationDTO.class)
            .exclude("id")
            .field("userSurname", "surname")
            .field("entityType.id", "type")
            .field("creationDateTime", "creationDate")
            .field("nums[0]", "firstNum")
            .byDefault()
            .register();

En este caso se registra globalmente. También podríamos registrarlo solo para ese campo de ese objeto en concreto.

6. Por último, vamos a realizar el mapeo: obtendremos un DestinationDTO a partir de un SourceEntity:

    MapperFacade mapper = mapperFactory.getMapperFacade();

    SourceEntity sourceEntity = new SourceEntity();
    sourceEntity
            .setId(12345L)
            .setName("entityName")
            .setUserSurname("surnameEntity")
            .setEntityType(new EntityType().setId(9999).setType("type9"))
            .setOptions(new String[]{"option1", "option2", "option3"})
            .setCreationDateTime(LocalDateTime.of(2015, 6, 16, 9, 10, 30))
            .setNums(new Integer[]{14, 25, 67});

    DestinationDTO destDTO = mapper.map(sourceEntity, DestinationDTO.class);

    System.out.println("Source: " + sourceEntity);
    System.out.println("Destination: " + destDTO);

Creamos un objeto SourceEntity, con todos sus campos. Y obteniendo un MapperFacadede Orika, llamamos al método ‘map‘, pasandole este objeto y la clase a la que queremos mapearlo.
Si ejecutamos el código, veremos que se ha mapeado correctamente tal como nosotros queríamos.
Hay mapeos que Orika hace directamente, aúnque los tipos sean distintos: por ejemplo, en el SourcEntity tenemos un array de String que Orika nos mapea directamente a un List de String en el DestinationDTO (por tener el mismo nombre no necesitamos ponerlo en la configuración)

En este ejemplo hemos hecho mapeo con varios tipos de datos y opciones, pero hay algunas más. Os recomiendo leer la guía de Orika en su página web, que está muy bien explicado y organizado.

Como siempre, podéis descargaros el ejemplo de mi github, proyecto orika-example. En el tenéis implementados unos tests (usando Junit y Hamcrest) para validar cada una de los mapeos, aparte del ejemplo completo en un método ‘main’.