Introduction to ModelMapper
ModelMapper is a powerful Java library for object mapping that simplifies the process of converting objects from one type to another. It is particularly useful for mapping DTOs (Data Transfer Objects) to entities and vice versa, often in the context of transferring data between different layers of an application. ModelMapper aims to make mapping easy by providing a simple and flexible API.
Key points about ModelMapper
ModelMapper is a powerful Java library that simplifies object mapping. Here are key points about ModelMapper:
-
Convention Over Configuration:
- ModelMapper uses a convention-based approach to automatically map objects. By default, it matches properties based on their names and types, reducing the need for explicit configurations. This convention-over-configuration principle makes mapping straightforward and intuitive.
- For example, if a
PersonDTO
has a propertyfirstName
and thePerson
class has a propertygivenName
, ModelMapper will automatically map these properties if their names and types match or if configured explicitly.
-
Type Maps:
- ModelMapper uses
TypeMap
objects to define and store mappings between source and destination types.TypeMap
can be customized to handle complex mappings, including nested properties and specific conversion logic. - You can create a
TypeMap
and add custom mappings using methods likeaddMappings
, where you specify the mapping logic for each property.
- ModelMapper uses
-
Converters and Providers:
- ModelMapper allows you to define custom
Converter
objects to handle specific type conversions that are not handled by default. Converters provide fine-grained control over the mapping process, allowing for custom transformation logic. - Providers can be used to control object instantiation. They can determine how new instances of destination objects are created during the mapping process, providing further customization.
- ModelMapper allows you to define custom
-
Validation and Configuration:
- ModelMapper offers validation to ensure mappings are correct and complete. The
validate
method can be used to verify that all source and destination properties are correctly mapped, catching potential issues early in the development process. - Global configuration settings allow you to customize the behavior of ModelMapper, such as enabling or disabling implicit mapping, setting strict mapping rules, and adjusting property matching strategies.
- ModelMapper offers validation to ensure mappings are correct and complete. The
-
Nested and Complex Mappings:
- ModelMapper excels at handling nested and complex mappings. It can automatically map nested objects and their properties, provided the property names and types match. This is particularly useful when dealing with complex object graphs, such as mapping DTOs with nested properties to entities.
- Additionally, ModelMapper supports mapping collections and arrays, making it easy to map lists of objects between source and destination types.
Installation
Adding ModelMapper to Your Project
To use ModelMapper, add the following dependency to your pom.xml
if you're using Maven:
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.4.4</version> <!-- or the latest version -->
</dependency>
For Gradle:
implementation 'org.modelmapper:modelmapper:2.4.4'
Basic Usage
Simple Bean Mapping
Let's start with a simple example of mapping between two Java beans, PersonDTO
and Person
.
Defining the Beans
public class PersonDTO {
private String firstName;
private String lastName;
private int age;
// Getters and Setters
}
public class Person {
private String givenName;
private String familyName;
private int age;
// Getters and Setters
}
Creating the Mapper and Performing the Mapping
import org.modelmapper.ModelMapper;
public class ModelMapperExample {
public static void main(String[] args) {
ModelMapper modelMapper = new ModelMapper();
PersonDTO personDTO = new PersonDTO();
personDTO.setFirstName("Amit");
personDTO.setLastName("Sharma");
personDTO.setAge(30);
Person person = modelMapper.map(personDTO, Person.class);
System.out.println("Person: " + person.getGivenName() + " " + person.getFamilyName() + ", Age: " + person.getAge());
PersonDTO mappedPersonDTO = modelMapper.map(person, PersonDTO.class);
System.out.println("PersonDTO: " + mappedPersonDTO.getFirstName() + " " + mappedPersonDTO.getLastName() + ", Age: " + mappedPersonDTO.getAge());
}
}
Output:
Person: Amit Sharma, Age: 30
PersonDTO: Amit Sharma, Age: 30
Explanation: This example demonstrates basic mapping between PersonDTO
and Person
objects using ModelMapper. The map
method is used to convert from one type to another.
Advanced Features
Custom Mappings
You can define custom mappings using the TypeMap
class.
Defining the Beans
public class EmployeeDTO {
private String empName;
private String empId;
// Getters and Setters
}
public class Employee {
private String name;
private String id;
// Getters and Setters
}
Creating the Mapper and Custom Mapping
import org.modelmapper.ModelMapper;
import org.modelmapper.TypeMap;
public class CustomMappingExample {
public static void main(String[] args) {
ModelMapper modelMapper = new ModelMapper();
TypeMap<EmployeeDTO, Employee> typeMap = modelMapper.createTypeMap(EmployeeDTO.class, Employee.class);
typeMap.addMappings(mapper -> {
mapper.map(EmployeeDTO::getEmpName, Employee::setName);
mapper.map(EmployeeDTO::getEmpId, Employee::setId);
});
EmployeeDTO employeeDTO = new EmployeeDTO();
employeeDTO.setEmpName("Vikas");
employeeDTO.setEmpId("E123");
Employee employee = modelMapper.map(employeeDTO, Employee.class);
System.out.println("Employee: " + employee.getName() + ", ID: " + employee.getId());
EmployeeDTO mappedEmployeeDTO = modelMapper.map(employee, EmployeeDTO.class);
System.out.println("EmployeeDTO: " + mappedEmployeeDTO.getEmpName() + ", ID: " + mappedEmployeeDTO.getEmpId());
}
}
Output:
Employee: Vikas, ID: E123
EmployeeDTO: Vikas, ID: E123
Explanation: This example demonstrates custom mappings using the TypeMap
class to specify the source and target properties explicitly.
Nested Mappings
ModelMapper supports nested mappings, which allow you to map nested objects.
Defining the Beans
public class AddressDTO {
private String street;
private String city;
// Getters and Setters
}
public class EmployeeDTO {
private String empName;
private String empId;
private AddressDTO address;
// Getters and Setters
}
public class Address {
private String streetName;
private String cityName;
// Getters and Setters
}
public class Employee {
private String name;
private String id;
private Address address;
// Getters and Setters
}
Creating the Mapper and Performing the Nested Mapping
import org.modelmapper.ModelMapper;
public class NestedMappingExample {
public static void main(String[] args) {
ModelMapper modelMapper = new ModelMapper();
AddressDTO addressDTO = new AddressDTO();
addressDTO.setStreet("MG Road");
addressDTO.setCity("Bangalore");
EmployeeDTO employeeDTO = new EmployeeDTO();
employeeDTO.setEmpName("Vikas");
employeeDTO.setEmpId("E123");
employeeDTO.setAddress(addressDTO);
Employee employee = modelMapper.map(employeeDTO, Employee.class);
System.out.println("Employee: " + employee.getName() + ", ID: " + employee.getId() +
", Address: " + employee.getAddress().getStreetName() + ", " + employee.getAddress().getCityName());
EmployeeDTO mappedEmployeeDTO = modelMapper.map(employee, EmployeeDTO.class);
System.out.println("EmployeeDTO: " + mappedEmployeeDTO.getEmpName() + ", ID: " + mappedEmployeeDTO.getEmpId() +
", Address: " + mappedEmployeeDTO.getAddress().getStreet() + ", " + mappedEmployeeDTO.getAddress().getCity());
}
}
Output:
Employee: Vikas, ID: E123, Address: MG Road, Bangalore
EmployeeDTO: Vikas, ID: E123, Address: MG Road, Bangalore
Explanation: This example demonstrates nested mappings, where nested objects and their properties are mapped using ModelMapper.
Custom Type Conversions
You can define custom-type conversions using Converter
objects.
Defining the Beans
public class ProductDTO {
private String name;
private String price;
// Getters and Setters
}
public class Product {
private String name;
private double price;
// Getters and Setters
}
Creating the Mapper and Custom Type Conversions
import org.modelmapper.Converter;
import org.modelmapper.ModelMapper;
import org.modelmapper.spi.MappingContext;
public class CustomConversionExample {
public static void main(String[] args) {
ModelMapper modelMapper = new ModelMapper();
Converter<String, Double> stringToDouble = new Converter<String, Double>() {
@Override
public Double convert(MappingContext<String, Double> context) {
return Double.parseDouble(context.getSource().replace("₹", "").trim());
}
};
Converter<Double, String> doubleToString = new Converter<Double, String>() {
@Override
public String convert(MappingContext<Double, String> context) {
return "₹ " + String.format("%.2f", context.getSource());
}
};
modelMapper.createTypeMap(ProductDTO.class, Product.class)
.addMappings(mapper -> mapper.using(stringToDouble).map(ProductDTO::getPrice, Product::setPrice));
modelMapper.createTypeMap(Product.class, ProductDTO.class)
.addMappings(mapper -> mapper.using(doubleToString).map(Product::getPrice, ProductDTO::setPrice));
ProductDTO productDTO = new ProductDTO();
productDTO.setName("Laptop");
productDTO.setPrice("₹ 50000");
Product product = modelMapper.map(productDTO, Product.class);
System.out.println("Product: " + product.getName() + ", Price: " + product.getPrice());
ProductDTO mappedProductDTO = modelMapper.map(product, ProductDTO.class);
System.out.println("ProductDTO: " + mappedProductDTO.getName() + ", Price: " + mappedProductDTO.getPrice());
}
}
Output:
Product: Laptop, Price: 50000.0
ProductDTO: Laptop, Price: ₹ 50000.00
Explanation: This example demonstrates custom-type conversions using Converter
objects to handle specific type conversions.
Complex and Nested Examples
Mapping Complex-Nested Objects
Defining the Beans
import java.util.List;
public class CompanyDTO {
private String name;
private CEO ceo;
private List<EmployeeDTO> employees;
// Getters and Setters
}
public class CEO {
private String name;
// Getters and Setters
}
public class Company {
private String companyName;
private CEO ceo;
private List<Employee> employees;
// Getters and Setters
}
Creating the Mapper
import org.modelmapper.ModelMapper;
import org.modelmapper.TypeMap;
public class ComplexNestedMappingExample {
public static void main(String[] args) {
ModelMapper modelMapper = new ModelMapper();
TypeMap<CompanyDTO,
Company> typeMap = modelMapper.createTypeMap(CompanyDTO.class, Company.class);
typeMap.addMappings(mapper -> {
mapper.map(CompanyDTO::getName, Company::setCompanyName);
mapper.map(CompanyDTO::getCeo, Company::setCeo);
mapper.map(CompanyDTO::getEmployees, Company::setEmployees);
});
CEO ceo = new CEO();
ceo.setName("Rajesh");
AddressDTO addressDTO1 = new AddressDTO();
addressDTO1.setStreet("MG Road");
addressDTO1.setCity("Bangalore");
EmployeeDTO employeeDTO1 = new EmployeeDTO();
employeeDTO1.setEmpName("Vikas");
employeeDTO1.setEmpId("E123");
employeeDTO1.setAddress(addressDTO1);
AddressDTO addressDTO2 = new AddressDTO();
addressDTO2.setStreet("Brigade Road");
addressDTO2.setCity("Bangalore");
EmployeeDTO employeeDTO2 = new EmployeeDTO();
employeeDTO2.setEmpName("Priya");
employeeDTO2.setEmpId("E124");
employeeDTO2.setAddress(addressDTO2);
CompanyDTO companyDTO = new CompanyDTO();
companyDTO.setName("Tech Solutions");
companyDTO.setCeo(ceo);
companyDTO.setEmployees(Arrays.asList(employeeDTO1, employeeDTO2));
Company company = modelMapper.map(companyDTO, Company.class);
System.out.println("Company: " + company.getCompanyName());
System.out.println("CEO: " + company.getCeo().getName());
for (Employee employee : company.getEmployees()) {
System.out.println("Employee: " + employee.getName() + ", ID: " + employee.getId() +
", Address: " + employee.getAddress().getStreetName() + ", " + employee.getAddress().getCityName());
}
CompanyDTO mappedCompanyDTO = modelMapper.map(company, CompanyDTO.class);
System.out.println("CompanyDTO: " + mappedCompanyDTO.getName());
System.out.println("CEO: " + mappedCompanyDTO.getCeo().getName());
for (EmployeeDTO employeeDTO : mappedCompanyDTO.getEmployees()) {
System.out.println("EmployeeDTO: " + employeeDTO.getEmpName() + ", ID: " + employeeDTO.getEmpId() +
", Address: " + employeeDTO.getAddress().getStreet() + ", " + employeeDTO.getAddress().getCity());
}
}
}
Output:
Company: Tech Solutions
CEO: Rajesh
Employee: Vikas, ID: E123, Address: MG Road, Bangalore
Employee: Priya, ID: E124, Address: Brigade Road, Bangalore
CompanyDTO: Tech Solutions
CEO: Rajesh
EmployeeDTO: Vikas, ID: E123, Address: MG Road, Bangalore
EmployeeDTO: Priya, ID: E124, Address: Brigade Road, Bangalore
Explanation: This example demonstrates mapping complex nested objects, including handling collections of nested objects. The CompanyDTO
contains a nested CEO
object and a list of EmployeeDTO
objects, which are mapped to their respective entities.
Using ModelMapper in Spring Boot
ModelMapper can be seamlessly integrated with Spring Boot applications. Here's how you can use ModelMapper in a Spring Boot project.
Adding ModelMapper and Spring Boot Dependencies
Add the following dependencies to your pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.2.0</version>
</dependency>
Creating the Spring Boot Application
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.modelmapper.ModelMapper;
@SpringBootApplication
public class ModelMapperSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(ModelMapperSpringBootApplication.class, args);
}
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
Defining the Beans
public class UserDTO {
private String firstName;
private String lastName;
private String email;
// Getters and Setters
}
public class User {
private String givenName;
private String familyName;
private String emailAddress;
// Getters and Setters
}
Creating the Mapper and Service
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private ModelMapper modelMapper;
// constructor-based DI
public UserService(ModelMapper modelMapper){
this.modelMapper = modelMapper;
}
public UserDTO convertToDto(User user) {
return modelMapper.map(user, UserDTO.class);
}
public User convertToEntity(UserDTO userDTO) {
return modelMapper.map(userDTO, User.class);
}
}
Creating a REST Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
private UserService userService;
// constructor-based DI
public UserController(UserService userService){
this.userService = userService;
}
@PostMapping
public UserDTO createUser(@RequestBody UserDTO userDTO) {
User user = userService.convertToEntity(userDTO);
// Simulate saving the user to the database
return userService.convertToDto(user);
}
}
Using the REST API
You can test the REST API using a tool like Postman. Make a POST request to http://localhost:8080/users
with the following JSON body:
{
"firstName": "Amit",
"lastName": "Sharma",
"email": "amit.sharma@example.com"
}
Output:
The response will be:
{
"firstName": "Amit",
"lastName": "Sharma",
"email": "amit.sharma@example.com"
}
Explanation: This example demonstrates how to integrate ModelMapper with a Spring Boot application. The ModelMapper
bean is defined in the Spring Boot application class and is used in the UserService
to map between UserDTO
and User
objects. The UserController
uses the UserService
to handle the REST API requests.
Conclusion
ModelMapper is a powerful and flexible library for object mapping in Java. This guide covered the basics of setting up ModelMapper, performing simple and nested mappings, custom-type conversions, and complex nested examples. Additionally, it showed how to integrate ModelMapper with a Spring Boot application. By leveraging ModelMapper, you can simplify and enhance your data transfer logic in Java applications. For more detailed information and advanced features, refer to the official ModelMapper documentation.
Comments
Post a Comment
Leave Comment