Mapping with Orika – 与Orika一起绘制地图

最后修改: 2016年 8月 7日

1. Overview

1.概述

Orika is a Java Bean mapping framework that recursively copies data from one object to another. It can be very useful when developing multi-layered applications.

Orika是一个Java Bean映射框架,将数据从一个对象递归到另一个对象。在开发多层次的应用程序时,它可以非常有用。

While moving data objects back and forth between these layers it is common to find that we need to convert objects from one instance into another to accommodate different APIs.

当在这些层之间来回移动数据对象时,经常发现我们需要将对象从一个实例转换成另一个实例,以适应不同的API。

Some ways to achieve this are: hard coding the copying logic or to implement bean mappers like Dozer. However, it can be used to simplify the process of mapping between one object layer and another.

实现这一目标的一些方法是。对复制逻辑进行硬编码,或者实现像Dozer这样的bean mappers。然而,它可以用来简化一个对象层和另一个对象层之间的映射过程。

Orika uses byte code generation to create fast mappers with minimal overhead, making it much faster than other reflection based mappers like Dozer.

Orika 使用字节码生成来创建快速映射器,开销最小,这使得它比其他基于反射的映射器(如Dozer)快得多。

2. Simple Example

2.简单的例子

The basic cornerstone of the mapping framework is the MapperFactory class. This is the class we will use to configure mappings and obtain the MapperFacade instance which performs the actual mapping work.

映射框架的基本基石是MapperFactory类。我们将使用这个类来配置映射,并获得执行实际映射工作的MapperFacade实例。

We create a MapperFactory object like so:

我们像这样创建一个MapperFactory对象。

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

Then assuming we have a source data object, Source.java, with two fields:

然后假设我们有一个源数据对象,Source.java,有两个字段。

public class Source {
    private String name;
    private int age;
    
    public Source(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // standard getters and setters
}

And a similar destination data object, Dest.java:

还有一个类似的目的地数据对象,Dest.java

public class Dest {
    private String name;
    private int age;
    
    public Dest(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // standard getters and setters
}

This is the most basic of bean mapping using Orika:

这是使用Orika的最基本的豆类映射。

@Test
public void givenSrcAndDest_whenMaps_thenCorrect() {
    mapperFactory.classMap(Source.class, Dest.class);
    MapperFacade mapper = mapperFactory.getMapperFacade();
    Source src = new Source("Baeldung", 10);
    Dest dest = mapper.map(src, Dest.class);

    assertEquals(dest.getAge(), src.getAge());
    assertEquals(dest.getName(), src.getName());
}

As we can observe, we have created a Dest object with identical fields as Source, simply by mapping. Bidirectional or reverse mapping is also possible by default:

正如我们所观察到的,我们已经创建了一个Dest对象,其字段与Source相同,只是通过映射。默认情况下,双向或反向映射也是可能的。

@Test
public void givenSrcAndDest_whenMapsReverse_thenCorrect() {
    mapperFactory.classMap(Source.class, Dest.class).byDefault();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    Dest src = new Dest("Baeldung", 10);
    Source dest = mapper.map(src, Source.class);

    assertEquals(dest.getAge(), src.getAge());
    assertEquals(dest.getName(), src.getName());
}

3. Maven Setup

3.Maven设置

To use Orika mapper in our maven projects, we need to have orika-core dependency in pom.xml:

要在我们的maven项目中使用Orika mapper,我们需要在pom.xml中设置orika-core依赖项。

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

The latest version can always be found here.

最新的版本总是可以找到这里

3. Working With MapperFactory

3.使用MapperFactory工作

The general pattern of mapping with Orika involves creating a MapperFactory object, configuring it incase we need to tweak the default mapping behaviour, obtaining a MapperFacade object from it and finally, actual mapping.

使用Orika映射的一般模式包括创建一个MapperFactory对象,配置它以备我们需要调整默认的映射行为,从它那里获得一个MapperFacade对象,最后是实际映射。

We shall be observing this pattern in all our examples. But our very first example showed the default behaviour of the mapper without any tweak from our side.

我们将在所有的例子中观察这个模式。但我们的第一个例子显示了映射器的默认行为,不需要我们进行任何调整。

3.1. The BoundMapperFacade vs MapperFacade

3.1.BoundMapperFacadeMapperFacade

One thing to note is that we could choose to use BoundMapperFacade over the default MapperFacade which is quite slow. These are cases where we have a specific pair of types to map.

需要注意的一点是,我们可以选择使用BoundMapperFacade而不是默认的MapperFacade,后者相当慢。这些情况是我们有一对特定的类型需要映射。

Our initial test would thus become:

因此,我们的初步测试将成为。

@Test
public void givenSrcAndDest_whenMapsUsingBoundMapper_thenCorrect() {
    BoundMapperFacade<Source, Dest> 
      boundMapper = mapperFactory.getMapperFacade(Source.class, Dest.class);
    Source src = new Source("baeldung", 10);
    Dest dest = boundMapper.map(src);

    assertEquals(dest.getAge(), src.getAge());
    assertEquals(dest.getName(), src.getName());
}

However, for BoundMapperFacade to map bi-directionally, we have to explicitely call the mapReverse method rather than the map method we have looked at for the case of the default MapperFacade:

然而,为了让BoundMapperFacade进行双向映射,我们必须明确地调用mapReverse方法,而不是我们为默认MapperFacade的情况所看的映射方法。

@Test
public void givenSrcAndDest_whenMapsUsingBoundMapperInReverse_thenCorrect() {
    BoundMapperFacade<Source, Dest> 
      boundMapper = mapperFactory.getMapperFacade(Source.class, Dest.class);
    Dest src = new Dest("baeldung", 10);
    Source dest = boundMapper.mapReverse(src);

    assertEquals(dest.getAge(), src.getAge());
    assertEquals(dest.getName(), src.getName());
}

The test will fail otherwise.

否则测试将失败。

3.2. Configure Field Mappings

3.2.配置字段映射

The examples we have looked at so far involve source and destination classes with identical field names. This subsection tackles the case where there is a difference between the two.

到目前为止,我们所看的例子涉及到具有相同字段名的源类和目的类。本小节讨论的是两者之间存在差异的情况。

Consider a source object, Person ,with three fields namely name, nickname and age:

考虑一个源对象,Person,有三个字段,即namenicknameage

public class Person {
    private String name;
    private String nickname;
    private int age;
    
    public Person(String name, String nickname, int age) {
        this.name = name;
        this.nickname = nickname;
        this.age = age;
    }
    
    // standard getters and setters
}

Then another layer of the application has a similar object, but written by a french programmer. Let’s say that’s called Personne, with fields nom, surnom and age, all corresponding to the above three:

然后应用程序的另一层有一个类似的对象,但由一个法国程序员编写。假设这个对象叫做Personne,有nomsurnomage三个字段,都与上述三个字段对应。

public class Personne {
    private String nom;
    private String surnom;
    private int age;
    
    public Personne(String nom, String surnom, int age) {
        this.nom = nom;
        this.surnom = surnom;
        this.age = age;
    }
    
    // standard getters and setters
}

Orika cannot automatically resolve these differences. But we can use the ClassMapBuilder API to register these unique mappings.

Orika不能自动解决这些差异。但我们可以使用ClassMapBuilder API来注册这些独特的映射。

We have already used it before, but we have not tapped into any of its powerful features yet. The first line of each of our preceding tests using the default MapperFacade was using the ClassMapBuilder API to register the two classes we wanted to map:

我们之前已经使用了它,但是我们还没有挖掘出它的任何强大功能。我们前面使用默认的MapperFacade的每个测试的第一行是使用ClassMapBuilder API来注册我们想要映射的两个类:

mapperFactory.classMap(Source.class, Dest.class);

We could also map all fields using the default configuration, to make it clearer:

我们也可以使用默认配置来映射所有字段,以使其更加清晰。

mapperFactory.classMap(Source.class, Dest.class).byDefault()

By adding the byDefault() method call, we are already configuring the behaviour of the mapper using the ClassMapBuilder API.

通过添加byDefault()方法调用,我们已经在使用ClassMapBuilder API配置映射器的行为了。

Now we want to be able to map Personne to Person, so we also configure field mappings onto the mapper using ClassMapBuilder API:

现在我们希望能够将Personne映射到Person,所以我们也使用ClassMapBuilder API:在映射器上配置字段映射。

@Test
public void givenSrcAndDestWithDifferentFieldNames_whenMaps_thenCorrect() {
    mapperFactory.classMap(Personne.class, Person.class)
      .field("nom", "name").field("surnom", "nickname")
      .field("age", "age").register();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    Personne frenchPerson = new Personne("Claire", "cla", 25);
    Person englishPerson = mapper.map(frenchPerson, Person.class);

    assertEquals(englishPerson.getName(), frenchPerson.getNom());
    assertEquals(englishPerson.getNickname(), frenchPerson.getSurnom());
    assertEquals(englishPerson.getAge(), frenchPerson.getAge());
}

Don’t forget to call the register() API method in order to register the configuration with the MapperFactory.

不要忘记调用register() API方法,以便向MapperFactory注册配置。

Even if only one field differs, going down this route means we must explicitly register all field mappings, including age which is the same in both objects, otherwise the unregistered field will not be mapped and the test would fail.

即使只有一个字段不同,走这条路意味着我们必须明确注册所有字段的映射,包括在两个对象中都相同的age,否则未注册的字段将不会被映射,测试将失败。

This will soon become tedious, what if we only want to map one field out of 20, do we need to configure all of their mappings?

这很快就会变得乏味,如果我们只想映射20个字段中的一个字段怎么办,我们是否需要配置它们所有的映射?

No, not when we tell the mapper to use it’s default mapping configuration in cases where we have not explicitly defined a mapping:

不,当我们告诉映射器在我们没有明确定义映射的情况下使用它的默认映射配置时,就不是了。

mapperFactory.classMap(Personne.class, Person.class)
  .field("nom", "name").field("surnom", "nickname").byDefault().register();

Here, we have not defined a mapping for the age field, but nevertheless the test will pass.

这里,我们没有为age字段定义一个映射,但尽管如此,测试还是会通过。

3.3. Exclude a Field

3.3.排除一个字段

Assuming we would like to exclude the nom field of Personne from the mapping – so that the Person object only receives new values for fields that are not excluded:

假设我们想从映射中排除Personnenom字段–这样Person对象就只能接收未排除的字段的新值。

@Test
public void givenSrcAndDest_whenCanExcludeField_thenCorrect() {
    mapperFactory.classMap(Personne.class, Person.class).exclude("nom")
      .field("surnom", "nickname").field("age", "age").register();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    Personne frenchPerson = new Personne("Claire", "cla", 25);
    Person englishPerson = mapper.map(frenchPerson, Person.class);

    assertEquals(null, englishPerson.getName());
    assertEquals(englishPerson.getNickname(), frenchPerson.getSurnom());
    assertEquals(englishPerson.getAge(), frenchPerson.getAge());
}

Notice how we exclude it in the configuration of the MapperFactory and then notice also the first assertion where we expect the value of name in the Person object to remain null, as a result of it being excluded in mapping.

注意我们如何在MapperFactory的配置中排除它,然后注意第一个断言,我们希望Person对象中的name的值保持null,因为它在映射中被排除。

4. Collections Mapping

4.集合映射

Sometimes the destination object may have unique attributes while the source object just maintains every property in a collection.

有时,目标对象可能有独特的属性,而源对象只是在一个集合中维护每个属性。

4.1. Lists and Arrays

4.1.名单和数组

Consider a source data object that only has one field, a list of a person’s names:

考虑一个只有一个字段的源数据对象,即一个人名的列表。

public class PersonNameList {
    private List<String> nameList;
    
    public PersonNameList(List<String> nameList) {
        this.nameList = nameList;
    }
}

Now consider our destination data object which separates firstName and lastName into separate fields:

现在考虑我们的目标数据对象,它将firstNamelastName分离成不同的字段。

public class PersonNameParts {
    private String firstName;
    private String lastName;

    public PersonNameParts(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

Let’s assume we are very sure that at index 0 there will always be the firstName of the person and at index 1 there will always be their lastName.

让我们假设我们非常确定在索引0处总是有这个人的firstName,在索引1处总是有他们的lastName

Orika allows us to use the bracket notation to access members of a collection:

Orika允许我们使用括号符号来访问一个集合的成员。

@Test
public void givenSrcWithListAndDestWithPrimitiveAttributes_whenMaps_thenCorrect() {
    mapperFactory.classMap(PersonNameList.class, PersonNameParts.class)
      .field("nameList[0]", "firstName")
      .field("nameList[1]", "lastName").register();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    List<String> nameList = Arrays.asList(new String[] { "Sylvester", "Stallone" });
    PersonNameList src = new PersonNameList(nameList);
    PersonNameParts dest = mapper.map(src, PersonNameParts.class);

    assertEquals(dest.getFirstName(), "Sylvester");
    assertEquals(dest.getLastName(), "Stallone");
}

Even if instead of PersonNameList, we had PersonNameArray, the same test would pass for an array of names.

即使我们用PersonNameList代替PersonNameArray,对于一个名字的数组,同样的测试也会通过。

4.2. Maps

4.2.地图

Assuming our source object has a map of values. We know there is a key in that map, first, whose value represents a person’s firstName in our destination object.

假设我们的源对象有一个值的映射。我们知道该映射中有一个键,first,其值代表我们的目标对象中一个人的firstName

Likewise we know that there is another key, last, in the same map whose value represents a person’s lastName in the destination object.

同样,我们知道在同一地图中还有另一个键,last,其值代表目的地对象中一个人的lastName

public class PersonNameMap {
    private Map<String, String> nameMap;

    public PersonNameMap(Map<String, String> nameMap) {
        this.nameMap = nameMap;
    }
}

Similar to the case in the preceding section, we use bracket notation, but instead of passing in an index, we pass in the key whose value we want to map to the given destination field.

与上一节的情况类似,我们使用括号符号,但我们不是传入一个索引,而是传入我们想映射到给定目标字段的键值。

Orika accepts two ways of retrieving the key, both are represented in the following test:

Orika接受两种检索密钥的方式,这两种方式在下面的测试中有所体现。

@Test
public void givenSrcWithMapAndDestWithPrimitiveAttributes_whenMaps_thenCorrect() {
    mapperFactory.classMap(PersonNameMap.class, PersonNameParts.class)
      .field("nameMap['first']", "firstName")
      .field("nameMap[\"last\"]", "lastName")
      .register();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    Map<String, String> nameMap = new HashMap<>();
    nameMap.put("first", "Leornado");
    nameMap.put("last", "DiCaprio");
    PersonNameMap src = new PersonNameMap(nameMap);
    PersonNameParts dest = mapper.map(src, PersonNameParts.class);

    assertEquals(dest.getFirstName(), "Leornado");
    assertEquals(dest.getLastName(), "DiCaprio");
}

We can use either single quotes or double quotes but we must escape the latter.

我们可以使用单引号或双引号,但我们必须转义后者。

5. Map Nested Fields

5.地图嵌套字段</strong

Following on from the preceding collections examples, assume that inside our source data object, there is another Data Transfer Object (DTO) that holds the values we want to map.

根据前面的集合例子,假设在我们的源数据对象中,有另一个数据传输对象(DTO),持有我们想要映射的值。

public class PersonContainer {
    private Name name;
    
    public PersonContainer(Name name) {
        this.name = name;
    }
}
public class Name {
    private String firstName;
    private String lastName;
    
    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

To be able to access the properties of the nested DTO and map them onto our destination object, we use dot notation, like so:

为了能够访问嵌套DTO的属性并将它们映射到我们的目标对象上,我们使用点符号,像这样。

@Test
public void givenSrcWithNestedFields_whenMaps_thenCorrect() {
    mapperFactory.classMap(PersonContainer.class, PersonNameParts.class)
      .field("name.firstName", "firstName")
      .field("name.lastName", "lastName").register();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    PersonContainer src = new PersonContainer(new Name("Nick", "Canon"));
    PersonNameParts dest = mapper.map(src, PersonNameParts.class);

    assertEquals(dest.getFirstName(), "Nick");
    assertEquals(dest.getLastName(), "Canon");
}

6. Mapping Null Values

6.映射空值

In some cases, you may wish to control whether nulls are mapped or ignored when they are encountered. By default, Orika will map null values when encountered:

在某些情况下,你可能希望控制遇到空值时是映射还是忽略。默认情况下,Orika会在遇到空值时进行映射。

@Test
public void givenSrcWithNullField_whenMapsThenCorrect() {
    mapperFactory.classMap(Source.class, Dest.class).byDefault();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    Source src = new Source(null, 10);
    Dest dest = mapper.map(src, Dest.class);

    assertEquals(dest.getAge(), src.getAge());
    assertEquals(dest.getName(), src.getName());
}

This behavior can be customized at different levels depending on how specific we would like to be.

这种行为可以根据我们想要的具体程度在不同层次上进行定制。

6.1. Global Configuration

6.1.全局配置

We can configure our mapper to map nulls or ignore them at the global level before creating the global MapperFactory. Remember how we created this object in our very first example? This time we add an extra call during the build process:

在创建全局的MapperFactory之前,我们可以配置我们的映射器以映射空值或在全局层面上忽略它们。还记得我们在第一个例子中如何创建这个对象吗?这一次我们在构建过程中增加了一个额外的调用。

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

We can run a test to confirm that indeed, nulls are not getting mapped:

我们可以运行一个测试来确认,确实,空值没有被映射到。

@Test
public void givenSrcWithNullAndGlobalConfigForNoNull_whenFailsToMap_ThenCorrect() {
    mapperFactory.classMap(Source.class, Dest.class);
    MapperFacade mapper = mapperFactory.getMapperFacade();
    Source src = new Source(null, 10);
    Dest dest = new Dest("Clinton", 55);
    mapper.map(src, dest);

    assertEquals(dest.getAge(), src.getAge());
    assertEquals(dest.getName(), "Clinton");
}

What happens is that, by default, nulls are mapped. This means that even if a field value in the source object is null and the corresponding field’s value in the destination object has a meaningful value, it will be overwritten.

发生的情况是,默认情况下,空值被映射。这意味着,即使源对象中的字段值是null,而目标对象中相应字段的值有意义,它也将被覆盖。

In our case, the destination field is not overwritten if its corresponding source field has a null value.

在我们的案例中,如果其对应的源字段有null值,则目标字段不会被覆盖。

6.2. Local Configuration

6.2.本地配置

Mapping of null values can be controlled on a ClassMapBuilder by using the mapNulls(true|false) or mapNullsInReverse(true|false) for controlling mapping of nulls in the reverse direction.

null值的映射可以通过使用mapNulls(true|false)mapNullsInReverse(true|false)ClassMapBuilder上控制反向的nulls的映射。

By setting this value on a ClassMapBuilder instance, all field mappings created on the same ClassMapBuilder, after the value is set, will take on that same value.

通过在ClassMapBuilder实例上设置该值,在设置该值后,在同一个ClassMapBuilder上创建的所有字段映射都将采用该值。

Let’s illustrate this with an example test:

让我们用一个测试实例来说明这一点。

@Test
public void givenSrcWithNullAndLocalConfigForNoNull_whenFailsToMap_ThenCorrect() {
    mapperFactory.classMap(Source.class, Dest.class).field("age", "age")
      .mapNulls(false).field("name", "name").byDefault().register();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    Source src = new Source(null, 10);
    Dest dest = new Dest("Clinton", 55);
    mapper.map(src, dest);

    assertEquals(dest.getAge(), src.getAge());
    assertEquals(dest.getName(), "Clinton");
}

Notice how we call mapNulls just before registering name field, this will cause all fields following the mapNulls call to be ignored when they have null value.

注意我们如何在注册name字段之前调用mapNulls,这将导致在mapNulls调用之后的所有字段在有null值时被忽略。

Bi-directional mapping also accepts mapped null values:

双向映射也接受映射的空值。

@Test
public void givenDestWithNullReverseMappedToSource_whenMapsByDefault_thenCorrect() {
    mapperFactory.classMap(Source.class, Dest.class).byDefault();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    Dest src = new Dest(null, 10);
    Source dest = new Source("Vin", 44);
    mapper.map(src, dest);

    assertEquals(dest.getAge(), src.getAge());
    assertEquals(dest.getName(), src.getName());
}

Also we can prevent this by calling mapNullsInReverse and passing in false:

另外,我们可以通过调用mapNullsInReverse并传入false来防止这种情况。

@Test
public void 
  givenDestWithNullReverseMappedToSourceAndLocalConfigForNoNull_whenFailsToMap_thenCorrect() {
    mapperFactory.classMap(Source.class, Dest.class).field("age", "age")
      .mapNullsInReverse(false).field("name", "name").byDefault()
      .register();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    Dest src = new Dest(null, 10);
    Source dest = new Source("Vin", 44);
    mapper.map(src, dest);

    assertEquals(dest.getAge(), src.getAge());
    assertEquals(dest.getName(), "Vin");
}

6.3. Field Level Configuration

6.3.场级配置

We can configure this at the field level using fieldMap, like so:

我们可以使用fieldMap在字段级进行配置,像这样。

mapperFactory.classMap(Source.class, Dest.class).field("age", "age")
  .fieldMap("name", "name").mapNulls(false).add().byDefault().register();

In this case, the configuration will only affect the name field as we have called it at field level:

在这种情况下,配置将只影响name字段,因为我们已经在字段级别调用了它。

@Test
public void givenSrcWithNullAndFieldLevelConfigForNoNull_whenFailsToMap_ThenCorrect() {
    mapperFactory.classMap(Source.class, Dest.class).field("age", "age")
      .fieldMap("name", "name").mapNulls(false).add().byDefault().register();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    Source src = new Source(null, 10);
    Dest dest = new Dest("Clinton", 55);
    mapper.map(src, dest);

    assertEquals(dest.getAge(), src.getAge());
    assertEquals(dest.getName(), "Clinton");
}

7. Orika Custom Mapping

7.Orika自定义映射

So far, we have looked at simple custom mapping examples using the ClassMapBuilder API. We shall still use the same API but customize our mapping using Orika’s CustomMapper class.

到目前为止,我们已经使用ClassMapBuilder API看了简单的自定义映射例子。我们仍将使用相同的API,但使用Orika的CustomMapper类来定制我们的映射。

Assuming we have two data objects each with a certain field called dtob, representing the date and time of the birth of a person.

假设我们有两个数据对象,每个对象都有一个叫做dtob的特定字段,代表一个人的出生日期和时间。

One data object represents this value as a datetime String in the following ISO format:

一个数据对象将此值表示为datetime String,其ISO格式如下。

2007-06-26T21:22:39Z

and the other represents the same as a long type in the following unix timestamp format:

而另一个代表相同的long类型,其unix时间戳格式如下。

1182882159000

Clearly, non of the customizations we have covered so far suffices to convert between the two formats during the mapping process, not even Orika’s built in converter can handle the job. This is where we have to write a CustomMapper to do the required conversion during mapping.

显然,到目前为止,我们所涉及的自定义功能都不足以在映射过程中转换这两种格式,甚至Orika的内置转换器也无法处理这项工作。因此,我们必须写一个CustomMapper来完成映射过程中的转换。

Let us create our first data object:

让我们创建我们的第一个数据对象。

public class Person3 {
    private String name;
    private String dtob;
    
    public Person3(String name, String dtob) {
        this.name = name;
        this.dtob = dtob;
    }
}

then our second data object:

然后是我们的第二个数据对象。

public class Personne3 {
    private String name;
    private long dtob;
    
    public Personne3(String name, long dtob) {
        this.name = name;
        this.dtob = dtob;
    }
}

We will not label which is source and which is destination right now as the CustomMapper enables us to cater for bi-directional mapping.

我们现在不会标注哪个是源,哪个是目的,因为CustomMapper使我们能够满足双向映射的要求。

Here is our concrete implementation of the CustomMapper abstract class:

这里是我们对CustomMapper抽象类的具体实现。

class PersonCustomMapper extends CustomMapper<Personne3, Person3> {

    @Override
    public void mapAtoB(Personne3 a, Person3 b, MappingContext context) {
        Date date = new Date(a.getDtob());
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        String isoDate = format.format(date);
        b.setDtob(isoDate);
    }

    @Override
    public void mapBtoA(Person3 b, Personne3 a, MappingContext context) {
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        Date date = format.parse(b.getDtob());
        long timestamp = date.getTime();
        a.setDtob(timestamp);
    }
};

Notice that we have implemented methods mapAtoB and mapBtoA. Implementing both makes our mapping function bi-directional.

注意,我们已经实现了mapAtoBmapBtoA方法。实现这两个方法使得我们的映射功能是双向的。

Each method exposes the data objects we are mapping and we take care of copying the field values from one to the other.

每个方法都暴露了我们正在映射的数据对象,我们负责将字段值从一个复制到另一个

There in is where we write the custom code to manipulate the source data according to our requirements before writing it to the destination object.

在这里,我们根据我们的要求编写自定义代码来处理源数据,然后再将其写入目标对象。

Let’s run a test to confirm that our custom mapper works:

让我们运行一个测试来确认我们的自定义映射器工作。

@Test
public void givenSrcAndDest_whenCustomMapperWorks_thenCorrect() {
    mapperFactory.classMap(Personne3.class, Person3.class)
      .customize(customMapper).register();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    String dateTime = "2007-06-26T21:22:39Z";
    long timestamp = new Long("1182882159000");
    Personne3 personne3 = new Personne3("Leornardo", timestamp);
    Person3 person3 = mapper.map(personne3, Person3.class);

    assertEquals(person3.getDtob(), dateTime);
}

Notice that we still pass the custom mapper to Orika’s mapper via ClassMapBuilder API, just like all other simple customizations.

注意,我们仍然通过ClassMapBuilder API将自定义映射器传递给Orika的映射器,就像所有其他简单的自定义一样。

We can confirm too that bi-directional mapping works:

我们也可以确认,双向映射是有效的。

@Test
public void givenSrcAndDest_whenCustomMapperWorksBidirectionally_thenCorrect() {
    mapperFactory.classMap(Personne3.class, Person3.class)
      .customize(customMapper).register();
    MapperFacade mapper = mapperFactory.getMapperFacade();
    String dateTime = "2007-06-26T21:22:39Z";
    long timestamp = new Long("1182882159000");
    Person3 person3 = new Person3("Leornardo", dateTime);
    Personne3 personne3 = mapper.map(person3, Personne3.class);

    assertEquals(person3.getDtob(), timestamp);
}

8. Conclusion

8.结论

In this article, we have explored the most important features of the Orika mapping framework.

在这篇文章中,我们已经探索了Orika映射框架的最重要特征

There are definitely more advanced features that give us much more control but in most use cases, the ones covered here will be more than enough.

肯定有更多的高级功能给我们带来更多的控制,但在大多数使用情况下,这里涵盖的功能将是绰绰有余的。

The full project code and all examples can be found in my github project. Don’t forget to check out our tutorial on the Dozer mapping framework as well, since they both solve more or less the same problem.

完整的项目代码和所有示例可以在我的github项目中找到。不要忘记查看我们关于Dozer映射框架的教程,因为它们都或多或少地解决了同样的问题。