Arian
Arian

Reputation: 7719

Set a converter for ModelMapper only once

I have the follwoing code for converting an object of class User to its DTO of type GetUserDto:

public GetUserDto convertToDto(User user) {
    Converter<User, GetUserDto> converter = context -> {
        User source = context.getSource();
        GetUserDto target = new GetUserDto();
        target.setDescription(source.getDescription());
        target.setId(source.getId());
        target.setName(source.getName());
        target.setImageId(source.getImageId());
        return target;
    };
    modelMapper.createTypeMap(User.class, GetUserDto.class).setConverter(converter);
    return modelMapper.map(user, GetUserDto.class);
}

It works fine for the first time, but the subsequent calls to convertToDto throws:

java.lang.IllegalStateException: A TypeMap already exists for class com.boot.cut_costs.model.User and class com.boot.cut_costs.dto.user.ExtendedGetUserDto

I found some other posts regarding this, one suggested to null check TypeMap for modelMapper and not to create it again if it wasn't null. This is not a good solution as if you have thousands of calls to this method it adds unnecessary overhead to it.

Any good solution ?

Upvotes: 0

Views: 2513

Answers (1)

davidxxx
davidxxx

Reputation: 131346

I found some other posts regarding this, one suggested to null check TypeMap for modelMapper and not to create it again if it wasn't null. This is not a good solution as if you have thousands of calls to this method it adds unnecessary overhead to it.

If you look into the code of org.modelmapper.ModelMapper.getTypeMap() and org.modelmapper.ModelMapper.createTypeMap(), you will see that creating a TypeMap that is a higher overhead as simply invoking a method to retrieve a TypeMap.

Returning the object from a map if it exists is straight:

  public <S, D> TypeMap<S, D> getTypeMap(Class<S> sourceType, Class<D> destinationType) {
    Assert.notNull(sourceType, "sourceType");
    Assert.notNull(destinationType, "destinationType");
    return config.typeMapStore.<S, D>get(sourceType, destinationType, null);
  }

While the adding performs much more things.
It creates a proxy type, does a synchronized task and does some checks before creating a TypeMap instance and at last it puts it in the map :

  private <S, D> TypeMap<S, D> createTypeMapInternal(S source, Class<S> sourceType,
      Class<D> destinationType, String typeMapName, Configuration configuration) {
    if (source != null)
      sourceType = Types.<S>deProxy(source.getClass());
    Assert.state(config.typeMapStore.get(sourceType, destinationType, typeMapName) == null,
        String.format("A TypeMap already exists for %s and %s", sourceType, destinationType));
    return config.typeMapStore.create(source, sourceType, destinationType, typeMapName,
        (InheritingConfiguration) configuration, engine);
  }

that invokes also :

  public <S, D> TypeMap<S, D> create(S source, Class<S> sourceType, Class<D> destinationType,
      String typeMapName, InheritingConfiguration configuration, MappingEngineImpl engine) {
    synchronized (lock) {
      TypeMapImpl<S, D> typeMap = new TypeMapImpl<S, D>(sourceType, destinationType, typeMapName,
          configuration, engine);
      if (configuration.isImplicitMappingEnabled()
          && ImplicitMappingBuilder.isMatchable(typeMap.getSourceType())
          && ImplicitMappingBuilder.isMatchable(typeMap.getDestinationType()))
        new ImplicitMappingBuilder<S, D>(source, typeMap, config.typeMapStore,
            config.converterStore).build();
      typeMaps.put(TypePair.of(sourceType, destinationType, typeMapName), typeMap);
      return typeMap;
    }
  }

So do the check if a more efficient solution :

if (modelMapper.getTypeMap(User.class,GetUserDto.class) == null){
    modelMapper.createTypeMap(User.class, GetUserDto.class).setConverter(converter);
}
return modelMapper.map(user, GetUserDto.class);

Now, if you want really avoid unnecessary processing, you could create the TypeMap and set all converter associated to in a specific class once.

public void initTypeMaps(){
        modelMapper.createTypeMap(User.class, GetUserDto.class).setConverter(converterUserAndUserDto);
        modelMapper.createTypeMap(Other.class, GetOtherDto.class).setConverter(converterOtherAndOtherDto);
 ...
}

At last, if you have thousands of calls to this method in a short time and you want to reduce to the maximum the overhead, don't use ModelMapper and a converter but do the mapping between two classes at hand.

Upvotes: 3

Related Questions