Overview:
In this article, I would like to show you the performance of few libraries when we do the Entity to DTO conversion or vice versa.
Goal:
I have an Entity class as shown here.
public class Car {
private long id;
private String make;
private int numOfSeats;
private Date releaseDate;
private Engine engine;
//getters & setters
}
public class Engine {
private String type;
//getters & setters
}
I have below corresponding DTO classes.
public class CarDto {
private long id;
private String make;
private int numOfSeats;
private Date releaseDate;
private EngineDto engineDto;
//getters & setters
}
public class EngineDto {
private String type;
//getters & setters
}
Our goal is to convert that Entity to DTO 10 million times to see how long it takes to do the conversion using some common libraries. Why 10 million? We can not do just 1 conversion and measure the time. So, I simply chose 10 million conversions.
Entity Object Creation:
Our aim is to measure only the entity to dto conversion. So lets create a simple car entity instance as shown here upfront. We would be using the same Car entity and convert that into CarDto 10 million times.
//create Car entity
Car car = new Car();
car.setId(1);
car.setMake("Honda");
car.setNumOfSeats(4);
Date date = Date.from(LocalDate.of(2010, 10, 10).atStartOfDay(ZoneId.systemDefault()).toInstant());
car.setReleaseDate(date);
//create Engine enity
Engine engine = new Engine();
engine.setType("V6");
car.setEngine(engine);
// we use mapping libraries here
Model Mapper:
Lets first play with model mapper.
- Include the below maven dependency
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.0</version>
</dependency>
- Do the necessary type mapping.
ModelMapper modelMapper = new ModelMapper();
TypeMap<Car, CarDto> typeMap = modelMapper.createTypeMap(Car.class, CarDto.class);
typeMap.addMappings(mapper -> {
mapper.map(Car::getEngine, CarDto::setEngineDto);
});
- Lets test
long time1 = System.currentTimeMillis();
for (int i = 0; i < 10_000_000; i++) {
CarDto carDto = modelMapper.map(car, CarDto.class);
}
long time2 = System.currentTimeMillis();
System.out.println(time2 - time1);
- Result
Run 1: 60285 ms
Run 2: 58734 ms
Spring BeanUtils:
Lets do the same type conversion using Spring bean utils.
- BeanUtils is very simple to use.
long time1 = System.currentTimeMillis();
for (int i = 0; i < 10_000_000; i++) {
CarDto carDto = new CarDto();
EngineDto engineDto = new EngineDto();
BeanUtils.copyProperties(car.getEngine(), engineDto);
BeanUtils.copyProperties(car, carDto);
carDto.setEngineDto(engineDto);
}
long time2 = System.currentTimeMillis();
System.out.println(time2 - time1);
- Result
Run 1: 6045 ms
Run 2: 6372 ms
MapStruct:
Lets do the conversion using MapStruct library. MapStruct is simple to use. but not so easy as the other above libs.
- Include the below maven dependency.
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.3.0.Final</version>
</dependency>
- Add this below maven plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.0.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
- Create an CarMapper interface to do the mapping.
- If the source and target properties are different, we need to add the @Mapping with source and target property names
- MapStruct automatically detects the child beans.
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
@Mapping(source = "engine", target = "engineDto")
CarDto toDto(Car car);
EngineDto toDto(Engine engine);
}
- Lets test
long time1 = System.currentTimeMillis();
for (int i = 0; i < 10_000_000; i++) {
CarDto carDto = CarMapper.INSTANCE.toDto(car);
}
long time2 = System.currentTimeMillis();
System.out.println(time2 - time1);
- Result
Run 1: 174 ms
Run 2: 180 ms
The result is very impressive and it is in milliseconds. MapStruct is able to convert Entity object to Dto 10 million times within 200 milliseconds!!
MapStruct creates this class at compile time using the Mapper interface we have created.
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2019-09-01T02:17:45+0000",
comments = "version: 1.3.0.Final, compiler: javac, environment: Java 12.0.2 (Oracle Corporation)"
)
public class CarMapperImpl implements CarMapper {
@Override
public CarDto toDto(Car car) {
if ( car == null ) {
return null;
}
CarDto carDto = new CarDto();
carDto.setEngineDto( toDto( car.getEngine() ) );
carDto.setId( car.getId() );
carDto.setMake( car.getMake() );
carDto.setNumOfSeats( car.getNumOfSeats() );
carDto.setReleaseDate( car.getReleaseDate() );
return carDto;
}
@Override
public EngineDto toDto(Engine engine) {
if ( engine == null ) {
return null;
}
EngineDto engineDto = new EngineDto();
engineDto.setType( engine.getType() );
return engineDto;
}
}
Manual Conversion:
Lets ignore all the libraries and do the Entity to DTO conversion manually ourselves. Lets see how long it takes for the same test we have been doing.
long time1 = System.currentTimeMillis();
for (int i = 0; i < 10_000_000; i++) {
EngineDto engineDto = new EngineDto();
engineDto.setType(car.getEngine().getType());
CarDto carDto = new CarDto();
carDto.setId(car.getId());
carDto.setMake(car.getMake());
carDto.setNumOfSeats(car.getNumOfSeats());
carDto.setReleaseDate(car.getReleaseDate());
carDto.setEngineDto(engineDto);
}
long time2 = System.currentTimeMillis();
System.out.println(time2 - time1);
- Result
Run 1: 172 ms
Run 2: 188 ms
Code Samples:
All these test are available in GitHub.
Summary:
There are many libraries out there to do this conversion. I picked just few for comparison. Among the 3 I had chosen, MapStruct seems to do the job exceptionally well. It is because it does not use run time java reflection APIs. Instead the code for doing the above mapping is getting generated at compile time. In fact the code it creates it is very close to what we have had for the manual conversion. So the performance of MapStruct is very close to manual conversion.
Wow, performance is such a great, good article for comparing the difference between frameworks and other tools.
ok. this is all about performance only. I couldnt understand why mapping getengine and setenginedto and what about other fields?
ModelMapper modelMapper = new ModelMapper();
TypeMap<Car, CarDto> typeMap = modelMapper.createTypeMap(Car.class, CarDto.class);
typeMap.addMappings(mapper -> {
mapper.map(Car::getEngine, CarDto::setEngineDto);
});
Yes. Comparison in terms for performance only as these are all mapping lib.
ModelMapper implicitly gets all the fields from engine class and maps with that DTO class. So I ignored those fields.