How to set up ModelMapper

I want to use ModelMapper to convert an object to DTO and back. It mostly works, but how to set it up. He has so many options that it's hard to know where to start. What's the best practice?

I will answer it myself below, but if another answer is better, I will accept it.

+3


source to share


2 answers


First, here are some links

My impression of mm is that it is very well designed. The code is solid and a pleasure to read. However, the documentation is very concise and there are very few examples. Also the api is confusing because there seems to be 10 ways to do anything, and no indication of why you are doing it anyway.

There are two alternatives: Dozer is the most popular and Orika gets good reviews for its usability.

Assuming you still want to use mm, here's what I learned about it.

The main class ModelMapper

should be single in your application. For me this meant @Bean with Spring. It works out of the box for simple cases. For example, suppose you have two classes:

class DogData
{
    private String name;
    private int mass;
}

class DogInfo
{
    private String name;
    private boolean large;
}

      

with the appropriate getters / setters. You can do it:

    ModelMapper mm = new ModelMapper();
    DogData dd = new DogData();
    dd.setName("fido");
    dd.setMass(70);
    DogInfo di = mm.map(dd, DogInfo.class);

      

and "name" will be copied from dd to di.

There are many ways to customize mm, but first you need to understand how it works.

The mm object contains a TypeMap for each ordered pair of types such as <DogInfo, DogData> and <DogData, DogInfo> there will be two types.

Each TypeMap contains a PropertyMap with a list of mappings. So in the example, mm will automatically create a TypeMap <DogData, DogInfo> that contains a PropertyMap that has one mapping.

We can write this

    TypeMap<DogData, DogInfo> tm = mm.getTypeMap(DogData.class, DogInfo.class);
    List<Mapping> list = tm.getMappings();
    for (Mapping m : list)
    {
        System.out.println(m);
    }

      

and it will output

PropertyMapping[DogData.name -> DogInfo.name]

      

When you call mm.map () this is what it does,

  • see if TypeMap exists if not create TypeMap for <S, D> source / target types
  • call TypeMap Condition , if it returns FALSE, do nothing and STOP
  • call TypeMap Provider to create a new target
  • call TypeMap PreConverter if it has
  • Perform one of the following actions:
    • if TypeMap has a custom converter , call it
    • or generate a PropertyMap (based on the config flags plus any custom mappings that were added) and use that (Note: TypeMap also has additional custom Pre / PostPropertyConverters that I think will run at this point before and after each mapping.)
  • call TypeMap PostConverter if it has

Caveat: this flowchart is kind of a documentary , but I had to guess a lot, so it might not be all right!

You can customize every single step of this process. But the two most common are



  • step 5a. - write your own TypeMap converter or
  • step 5b. - write your own property mapping.

Here's an example of a custom TypeMap Converter :

    Converter<DogData, DogInfo> myConverter = new Converter<DogData, DogInfo>()
    {
        public DogInfo convert(MappingContext<DogData, DogInfo> context)
        {
            DogData s = context.getSource();
            DogInfo d = context.getDestination();
            d.setName(s.getName());
            d.setLarge(s.getMass() > 25);
            return d;
        }
    };

    mm.addConverter(myConverter);

      

Note converter is one-sided. You have to write another one if you want to customize DogInfo for DogData.

Here's an example of a custom PropertyMap :

    Converter<Integer, Boolean> convertMassToLarge = new Converter<Integer, Boolean>()
    {
        public Boolean convert(MappingContext<Integer, Boolean> context)
        {
            // If the dog weighs more than 25, then it must be large
            return context.getSource() > 25;
        }
    };

    PropertyMap<DogData, DogInfo> mymap = new PropertyMap<DogData, DogInfo>()
    {
        protected void configure()
        {
            // Note: this is not normal code. It is "EDSL" so don't get confused
            map(source.getName()).setName(null);
            using(convertMassToLarge).map(source.getMass()).setLarge(false);
        }
    };

    mm.addMappings(mymap);

      

The pm.configure function is really funky. This is not real code. This is dummy EDSL code that is interpreted in some way. For example, the parameter for a setter doesn't matter, it's just a placeholder. You can do many things here, for example

  • when (condition) .map (getter) .setter
  • when (condition) .skip (). setter - it's safe to ignore the field.
  • using (converter) .map (getter) .setter - custom field mapper
  • with (provider) .map (getter) .setter - custom field constructor

Note custom mappings are added to mappings by default, so you don't need to, for example, specify

            map(source.getName()).setName(null);

      

in your custom PropertyMap.configure ().

In this example, I had to write a Converter to map Integer to Boolean. In most cases, this will not be necessary because mm will automatically convert Integer to String, etc.

I am told that you can also create mappings using Java 8 lambda expressions . I tried but I couldn't figure it out.

Concluding Recommendations and Best Practices

By default, mm is used MatchingStrategies.STANDARD

, which is dangerous. It can easily pick the wrong display and cause strange, hard to find errors. What if, next year, someone else adds a new column to the database? So don't do it. Make sure you are using STRICT mode:

    mm.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

      

Always write unit tests and make sure all mappings are validated.

    DogInfo di = mm.map(dd, DogInfo.class);
    mm.validate();   // make sure nothing in the destination is accidentally skipped

      

Fix any validation errors with mm.addMappings()

as shown above.

Place all your mappings in the center where the mm singleton is created.

+20


source


I have been using it for the last 6 months, I will explain some of my thoughts on this:

Fist of all, it is recommended to use it as a unique instance (singleton, spring bean, ...) which is explained in the manual and I think everyone agrees on that.

ModelMapper

- great image library and wide flexibility. Because of its flexibility, there are many ways to get the same result, and why this should be a best practice guide for when to use one way or another to do the same.

Starting with a ModelMapper

little tricky, it has a very tricky learning curve and sometimes it's not easy to figure out how best to do something, or how to do something else. So, first you need to accurately read and understand the manual.

You can customize your mapping the way you want using the following settings:

Access level
Field matching
Naming convention
Name transformer
Name tokenizer 
Matching strategy

      



The default configuration is simply the best ( http://modelmapper.org/user-manual/configuration/ ), but if you want to customize it, you can.

Just one thing related to the Matching Strategy configuration, I think this is the most important configuration and one should be careful with it. I would use Strict

or Standard

, but never Loose

, why?

  • Due Loose is the most flexible and intelligent cartographer that can display some properties you might not expect. So, finally, be careful with this. I think it's better to create your own PropertyMap and use Converters if needed instead of setting it up like Loose.

Otherwise, it is important validate

that all properties match, you check everything that it works, and with ModelMapper it is more necessary because of smart mapping, this is done using reflection, so you will not have compiler help, it will continue compiling but the mapping will fail. without realizing it. This is one of the things I least like, but it should avoid templates and manual rendering.

Finally, if you are sure you are using ModelMapper in your project, you should use it using the way it suggests, do not mix it with manual mappings (for example), just use ModelMapper if you don’t know how to do something. what is possible (investigate, ...). It can sometimes be tricky to do this with a mapper model (I don't like it either), both by hand, but this is the price you have to pay to avoid template mappings in other POJOs.

+2


source







All Articles