avatarRabinarayan Patra

Summary

Mastering ModelMapper in Spring Boot involves integrating the ModelMapper library to automate object-to-object mapping, reducing boilerplate code and enhancing maintainability.

Abstract

ModelMapper is a Java library that simplifies the conversion of objects from one type to another, which is particularly beneficial in layered architectures where manual data transferring can be error-prone and cumbersome. It operates on the principle of convention over configuration, automatically mapping properties between objects when they follow JavaBean naming conventions. The library can be easily set up in a Spring Boot application using Maven or Gradle and allows for advanced configurations such as custom property mappings, conditional mappings, and the use of converters for complex transformations. ModelMapper supports deep mapping, implicit mapping, and mapping inheritance, which contributes to its flexibility and efficiency in handling both simple and intricate mapping scenarios.

Opinions

  • ModelMapper is praised for reducing boilerplate code and increasing the readability and maintainability of the codebase.
  • The library is seen as a solution to the common problem of manual field mapping in complex applications, which can lead to errors and inefficiencies.
  • ModelMapper's advanced configuration options are highlighted as powerful tools for developers to tailor the mapping process to their specific needs.
  • The library's ability to handle patch mapping for partial updates is considered an elegant solution in the context of REST API development.
  • The internal workings of ModelMapper, which include the construction of a mapping graph and the application of configured converters and conditions, are viewed as robust and efficient.
  • ModelMapper's support for inheritance in mappings is appreciated for promoting the DRY principle, preventing repetition, and allowing for more maintainable code.
  • The extensive documentation and JavaDoc for ModelMapper are regarded as valuable resources for developers to fully leverage the library's capabilities.

Mastering ModelMapper in Spring Boot

Introduction to ModelMapper

ModelMapper is a powerful Java library designed to simplify the process of mapping objects from one type to another. It automates the tedious task of manually transferring data between objects, which is especially useful in layered architectures, such as MVC applications, where you often need to transfer data between model, DTO (Data Transfer Objects), and view layers.

Why ModelMapper?

In software development, especially in complex applications, the task of mapping fields from one class to another is common. Doing this manually not only leads to boilerplate code but also increases the risk of errors. ModelMapper addresses these issues by:

  • Reducing boilerplate code.
  • Increasing code readability and maintainability.
  • Providing a simple, convention-based mechanism to transform objects.

How ModelMapper Works

ModelMapper works on the principle of convention over configuration. It uses a set of conventions to intelligently map properties between objects. If properties in the source and destination objects follow JavaBean naming conventions and are compatible, ModelMapper will automatically map them without additional configuration.

Internals

At its core, ModelMapper constructs a mapping graph between source and destination types, identifying matching properties by name and type. When a mapping is executed, ModelMapper traverses this graph, copying values from the source to the destination.

Setting Up ModelMapper in Spring Boot

ModelMapper can be easily integrated into a Spring Boot application. Here’s how to add it to your project using Maven and Gradle.

Maven Setup

Add the following dependency to your pom.xml:

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>2.3.8</version>
</dependency>

Gradle Setup

For Gradle, include this in your build.gradle:

implementation 'org.modelmapper:modelmapper:2.3.8'

Ensure you’re using the latest version by checking the ModelMapper website.

Spring Boot Configuration

To use ModelMapper in Spring Boot, you can define a @Bean for ModelMapper in your configuration:

import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ModelMapperConfig {

    @Bean
    public ModelMapper modelMapper() {
        return new ModelMapper();
    }
}

This makes ModelMapper available for autowiring throughout your Spring Boot application.

Usage and Examples

ModelMapper shines when converting between different object types. Here’s a basic example of mapping between an Entity and a DTO:

public class UserEntity {
    private String name;
    private String email;
    // Getters and setters omitted for brevity
}

public class UserDTO {
    private String name;
    private String email;
    // Getters and setters omitted for brevity
}

// Autowire ModelMapper
@Autowired
private ModelMapper modelMapper;

public UserDTO convertEntityToDTO(UserEntity userEntity) {
    return modelMapper.map(userEntity, UserDTO.class);
}

Advanced Configuration

ModelMapper offers various configuration options to customize mappings. These include:

  • Property Mapping: Customize how individual properties are mapped.
  • Conditionals: Add conditions to control whether fields are mapped.
  • Converters: Use custom converters for complex mappings.

Example: Custom Property Mapping

modelMapper.typeMap(UserEntity.class, UserDTO.class).addMappings(mapper -> {
    mapper.map(src -> src.getName(), UserDTO::setName);
});

Advanced Configuration in Depth

ModelMapper’s power lies in its ability to be finely tuned through advanced configurations. Beyond basic object mapping, it offers several features that cater to complex mapping scenarios, such as conditional mapping, custom converters, and property mapping.

Property Mapping

Property mapping allows you to explicitly define how properties between two objects are mapped. This is especially useful when property names or structures don’t match.

modelMapper.typeMap(SourceClass.class, DestinationClass.class).addMappings(mapper -> {
    mapper.map(SourceClass::getSourceName, DestinationClass::setDestinationName);
});

Conditional Mapping

Conditional mapping lets you control whether a field should be mapped based on specific conditions.

modelMapper.typeMap(Source.class, Destination.class).addMappings(mapper -> {
    mapper.when(Conditions.isNotNull()).map(Source::getConditionalValue, Destination::setValue);
});

Using Converters for Complex Mappings

Converters are powerful tools within ModelMapper that allow for custom logic during the mapping process. This is particularly useful for complex transformations.

modelMapper.typeMap(Source.class, Destination.class).setConverter(context -> {
    Source source = context.getSource();
    Destination dest = new Destination();
    // Custom logic to populate dest from source
    return dest;
});

Patch Mapping for Partial Updates

Patch mapping is a technique used in APIs for partial updates of resources. Instead of updating an entire resource, only specified fields are updated. ModelMapper can be tailored to support patch mapping elegantly.

public void patchEntity(Destination source, Destination destination) {
    modelMapper.getConfiguration().setPropertyCondition(Conditions.isNotNull());
    modelMapper.map(source, destination);
}

Integration with Spring Boot REST APIs

In a Spring Boot application, ModelMapper can be utilized to facilitate the transformation of data between entities and DTOs within REST controllers. Here’s an example of how ModelMapper can be used in a Spring Boot REST controller:

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private ModelMapper modelMapper;
    
    @Autowired
    private UserService userService;

    @PatchMapping("/{id}")
    public ResponseEntity<UserDTO> updateUser(@PathVariable Long id, @RequestBody UserDTO userDto) {
        UserEntity userEntity = userService.findById(id);
        modelMapper.map(userDto, userEntity);
        userService.save(userEntity);
        UserDTO updatedUserDto = modelMapper.map(userEntity, UserDTO.class);
        return ResponseEntity.ok(updatedUserDto);
    }
}

Understanding ModelMapper’s Internal Workings

Internally, ModelMapper employs a robust mapping engine that analyzes object structures through introspection. It then builds a mapping graph that connects source and destination properties. When a mapping is invoked, ModelMapper traverses this graph, applying any configured converters, conditions, and property mappings. This approach ensures efficient and accurate mappings, even in complex scenarios.

Additional Features and Methods in ModelMapper

Deep Mapping

ModelMapper excels at deep mapping, where nested objects need to be transformed from one structure to another. It automatically handles nested properties without requiring explicit instructions, provided the property names and types in the source and destination objects match.

Custom Property Mapping

For scenarios where property names do not match or custom logic is needed, ModelMapper allows for specific property mappings:

modelMapper.typeMap(Source.class, Destination.class).addMappings(mapper -> {
    mapper.map(src -> src.getNested().getDeepProperty(), Destination::setDifferentName);
});

This is particularly useful when working with complex object graphs or when integrating systems where data structures differ significantly.

Conditional Skip

There might be cases where you want to skip mapping certain fields under specific conditions. ModelMapper provides a way to conditionally skip fields:

modelMapper.typeMap(Source.class, Destination.class).addMappings(mapper -> {
    mapper.skip(Destination::setFieldToSkip);
});

Value Converters

ModelMapper allows for the specification of custom value converters if you need to perform specific transformations:

Converter<String, String> toUpperCaseConverter = context -> context.getSource() == null ? null : context.getSource().toUpperCase();

modelMapper.typeMap(Source.class, Destination.class).addMappings(mapper -> 
    mapper.using(toUpperCaseConverter).map(Source::getName, Destination::setName));

Pre/Post Converters

Pre and post converters offer hooks that execute before and after the mapping process, allowing for additional customization or validation:

modelMapper.typeMap(Source.class, Destination.class).setPreConverter(context -> {
    // Pre-conversion logic here
    return context;
});

modelMapper.typeMap(Source.class, Destination.class).setPostConverter(context -> {
    // Post-conversion logic here
    return context;
});

Implicit Mapping

ModelMapper performs implicit mapping when possible, relying on the structure of the source and destination types to automatically determine how data should be transferred. This reduces the need for explicit configuration while still allowing for customization as needed.

Mapping Inheritance

ModelMapper supports mapping inheritance, allowing for a base configuration to be extended by more specific mappings. This is particularly useful for DRY (Don’t Repeat Yourself) principles in complex mapping scenarios where base object mappings are common but need slight adjustments for specific cases.

modelMapper.createTypeMap(BaseSource.class, BaseDestination.class)
           .addMapping(BaseSource::getBaseProperty, BaseDestination::setBaseProperty);

modelMapper.createTypeMap(SpecificSource.class, SpecificDestination.class)
           .includeBase(BaseSource.class, BaseDestination.class);

Conclusion

Exploring these additional features and methods reveals the depth and flexibility of ModelMapper. Whether dealing with simple or complex mapping requirements, ModelMapper provides the tools necessary for efficient and effective data transformation. Leveraging these capabilities can lead to cleaner code, reduced boilerplate, and a more maintainable codebase in Java and Spring Boot applications.

ModelMapper’s extensive documentation and JavaDoc serve as excellent resources for developers seeking to maximize their use of the library, ensuring that they can tackle a wide array of data mapping challenges with confidence.

This blog post has aimed to provide a comprehensive overview of ModelMapper, illustrating its importance, functionality, and integration into Spring Boot applications. Through practical examples and explanations of advanced configurations, developers can harness ModelMapper to enhance code quality and efficiency in their projects.

Modelmapper
Spring Boot
Dto
Entity
Mapping
Recommended from ReadMedium