Изпълнение на Java Mapping Frameworks

1. Въведение

Създаването на големи Java приложения, съставени от множество слоеве, изисква използването на множество модели като персистентен модел, модел на домейн или така наречените DTO. Използването на множество модели за различни слоеве на приложението ще изисква от нас да предоставим начин за картографиране между зърната.

Правейки това ръчно, можете бързо да създадете много шаблонни кодове и да отнемете много време. За наш късмет има множество рамки за картографиране на обекти за Java.

В този урок ще сравним производителността на най-популярните рамки за картографиране на Java.

2. Картографски рамки

2.1. Булдозер

Dozer е рамка за картографиране, която използва рекурсия за копиране на данни от един обект в друг . Рамката е в състояние не само да копира свойства между зърната, но може и автоматично да конвертира между различни типове.

За да използваме рамката на Dozer, трябва да добавим такава зависимост към нашия проект:

 com.github.dozermapper dozer-core 6.5.0 

Повече информация за използването на рамката на Dozer можете да намерите в тази статия.

Документацията на рамката можете да намерите тук.

2.2. Орика

Orika е рамка за картографиране bean to bean, която рекурсивно копира данни от един обект в друг .

Общият принцип на работа на Orika е подобен на Dozer. Основната разлика между двете е фактът, че Orika използва генериране на байт кодове . Това позволява да се генерират по-бързи картографи с минимални режийни разходи.

За да го използвате,трябва да добавим такава зависимост към нашия проект:

 ma.glasnost.orika orika-core 1.5.4 

По-подробна информация за използването на Orika можете да намерите в тази статия.

Действителната документация на рамката може да бъде намерена тук.

2.3. MapStruct

MapStruct е генератор на код, който автоматично генерира класове на картографски компоненти .

MapStruct също има способността да конвертира между различни типове данни. Повече информация за това как да го използвате можете да намерите в тази статия.

За да добавите MapStructкъм нашия проект трябва да включим следната зависимост:

 org.mapstruct mapstruct 1.3.1.Final 

Документацията на рамката можете да намерите тук.

2.4. ModelMapper

ModelMapper е рамка, която има за цел да опрости картографирането на обекти, като определи как обектите се картографират помежду си въз основа на конвенциите. Той осигурява API, безопасен за типа и рефакторинг.

Повече информация за рамката може да се намери в документацията.

За да включим ModelMapper в нашия проект, трябва да добавим следната зависимост:

 org.modelmapper modelmapper 2.3.8 

2.5. JMapper

JMapper е рамката за картографиране, която има за цел да осигури лесно за използване, високоефективно картографиране между Java Beans.

Рамката има за цел да приложи принципа на СУХО, като използва анотации и релационно картографиране.

Рамката позволява различни начини за конфигуриране: базирани на анотации, XML или API.

Повече информация за рамката може да се намери в нейната документация.

За да включим JMapper в нашия проект, трябва да добавим неговата зависимост:

 com.googlecode.jmapper-framework jmapper-core 1.6.1.CR2 

3. Модел за тестване

За да можем да тестваме правилно картографирането, трябва да имаме източници и целеви модели. Създадохме два модела за тестване.

Първият е просто обикновен POJO с едно поле String , това ни позволи да сравняваме рамки в по-прости случаи и да проверяваме дали нещо се променя, ако използваме по-сложни зърна.

Простият модел на източника изглежда по-долу:

public class SourceCode { String code; // getter and setter }

И дестинацията му е доста подобна:

public class DestinationCode { String code; // getter and setter }

Примерът от реалния живот на изходния боб изглежда така:

public class SourceOrder { private String orderFinishDate; private PaymentType paymentType; private Discount discount; private DeliveryData deliveryData; private User orderingUser; private List orderedProducts; private Shop offeringShop; private int orderId; private OrderStatus status; private LocalDate orderDate; // standard getters and setters }

И целевият клас изглежда по-долу:

public class Order { private User orderingUser; private List orderedProducts; private OrderStatus orderStatus; private LocalDate orderDate; private LocalDate orderFinishDate; private PaymentType paymentType; private Discount discount; private int shopId; private DeliveryData deliveryData; private Shop offeringShop; // standard getters and setters }

Цялата структура на модела можете да намерите тук.

4. Преобразуватели

За да опростим дизайна на настройката за тестване, създадохме интерфейса на конвертора :

public interface Converter { Order convert(SourceOrder sourceOrder); DestinationCode convert(SourceCode sourceCode); }

И всички наши персонализирани картографи ще внедрят този интерфейс.

4.1. OrikaConverter

Orika позволява пълна реализация на API, което значително опростява създаването на mapper:

public class OrikaConverter implements Converter{ private MapperFacade mapperFacade; public OrikaConverter() { MapperFactory mapperFactory = new DefaultMapperFactory .Builder().build(); mapperFactory.classMap(Order.class, SourceOrder.class) .field("orderStatus", "status").byDefault().register(); mapperFacade = mapperFactory.getMapperFacade(); } @Override public Order convert(SourceOrder sourceOrder) { return mapperFacade.map(sourceOrder, Order.class); } @Override public DestinationCode convert(SourceCode sourceCode) { return mapperFacade.map(sourceCode, DestinationCode.class); } }

4.2. DozerConverter

Dozer изисква XML файл за картографиране със следните раздели:

  com.baeldung.performancetests.model.source.SourceOrder com.baeldung.performancetests.model.destination.Order  status orderStatus    com.baeldung.performancetests.model.source.SourceCode com.baeldung.performancetests.model.destination.DestinationCode  

After defining the XML mapping, we can use it from code:

public class DozerConverter implements Converter { private final Mapper mapper; public DozerConverter() { this.mapper = DozerBeanMapperBuilder.create() .withMappingFiles("dozer-mapping.xml") .build(); } @Override public Order convert(SourceOrder sourceOrder) { return mapper.map(sourceOrder,Order.class); } @Override public DestinationCode convert(SourceCode sourceCode) { return mapper.map(sourceCode, DestinationCode.class); } }

4.3. MapStructConverter

MapStruct definition is quite simple as it's entirely based on code generation:

@Mapper public interface MapStructConverter extends Converter { MapStructConverter MAPPER = Mappers.getMapper(MapStructConverter.class); @Mapping(source = "status", target = "orderStatus") @Override Order convert(SourceOrder sourceOrder); @Override DestinationCode convert(SourceCode sourceCode); }

4.4. JMapperConverter

JMapperConverter requires more work to do. After implementing the interface:

public class JMapperConverter implements Converter { JMapper realLifeMapper; JMapper simpleMapper; public JMapperConverter() { JMapperAPI api = new JMapperAPI() .add(JMapperAPI.mappedClass(Order.class)); realLifeMapper = new JMapper(Order.class, SourceOrder.class, api); JMapperAPI simpleApi = new JMapperAPI() .add(JMapperAPI.mappedClass(DestinationCode.class)); simpleMapper = new JMapper( DestinationCode.class, SourceCode.class, simpleApi); } @Override public Order convert(SourceOrder sourceOrder) { return (Order) realLifeMapper.getDestination(sourceOrder); } @Override public DestinationCode convert(SourceCode sourceCode) { return (DestinationCode) simpleMapper.getDestination(sourceCode); } }

We also need to add @JMap annotations to each field of the target class. Also, JMapper can't convert between enum types on its own and it requires us to create custom mapping functions :

@JMapConversion(from = "paymentType", to = "paymentType") public PaymentType conversion(com.baeldung.performancetests.model.source.PaymentType type) { PaymentType paymentType = null; switch(type) { case CARD: paymentType = PaymentType.CARD; break; case CASH: paymentType = PaymentType.CASH; break; case TRANSFER: paymentType = PaymentType.TRANSFER; break; } return paymentType; }

4.5. ModelMapperConverter

ModelMapperConverter requires us to only provide the classes that we want to map:

public class ModelMapperConverter implements Converter { private ModelMapper modelMapper; public ModelMapperConverter() { modelMapper = new ModelMapper(); } @Override public Order convert(SourceOrder sourceOrder) { return modelMapper.map(sourceOrder, Order.class); } @Override public DestinationCode convert(SourceCode sourceCode) { return modelMapper.map(sourceCode, DestinationCode.class); } }

5. Simple Model Testing

For the performance testing, we can use Java Microbenchmark Harness, more information about how to use it can be found in this article.

We've created a separate benchmark for each Converter with specifying BenchmarkMode to Mode.All.

5.1. AverageTime

JMH returned the following results for average running time (the lesser the better) :

Framework Name Average running time (in ms per operation)
MapStruct 10 -5
JMapper 10 -5
Orika 0.001
ModelMapper 0.001
Dozer 0.002

This benchmark shows clearly that both MapStruct and JMapper have the best average working times.

5.2. Throughput

In this mode, the benchmark returns the number of operations per second. We have received the following results (more is better) :

Framework Name Throughput (in operations per ms)
MapStruct 133719
JMapper 106978
Orika 1800
ModelMapper 978
Dozer 471

In throughput mode, MapStruct was the fastest of the tested frameworks, with JMapper a close second.

5.3. SingleShotTime

This mode allows measuring the time of single operation from it's beginning to the end. The benchmark gave the following result (less is better):

Framework Name Single Shot Time (in ms per operation)
JMapper 0.015
MapStruct 0.450
Dozer 2.094
Orika 2.898
ModelMapper 4.837

Here, we see that JMapper returns better result than MapStruct.

5.4. SampleTime

This mode allows sampling of the time of each operation. The results for three different percentiles look like below:

Sample Time (in milliseconds per operation)
Framework Name p0.90 p0.999 p1.0
JMapper 10-4 0.001 2.6
MapStruct 10-4 0.001 3
Orika 0.001 0.010 4
ModelMapper 0.002 0.015 3.2
Dozer 0.003 0.021 25

All benchmarks have shown that MapStruct and JMapper are both good choices depending on the scenario.

6. Real-Life Model Testing

For the performance testing, we can use Java Microbenchmark Harness, more information about how to use it can be found in this article.

We have created a separate benchmark for each Converter with specifying BenchmarkMode to Mode.All.

6.1. AverageTime

JMH returned the following results for average running time (less is better) :

Framework Name Average running time (in ms per operation)
MapStruct 10 -4
JMapper 10 -4
Orika 0.004
ModelMapper 0.059
Dozer 0.103

6.2. Throughput

In this mode, the benchmark returns the number of operations per second. For each of the mappers we've received the following results (more is better) :

Framework Name Throughput (in operations per ms)
JMapper 7691
MapStruct 7120
Orika 281
ModelMapper 19
Dozer 10

6.3. SingleShotTime

This mode allows measuring the time of single operation from it's beginning to the end. The benchmark gave the following results (less is better):

Framework Name Single Shot Time (in ms per operation)
JMapper 0.253
MapStruct 0.532
Dozer 9.495
ModelMapper 16.288
Orika 18.081

6.4. SampleTime

This mode allows sampling of the time of each operation. Sampling results are split into percentiles, we'll present results for three different percentiles p0.90, p0.999, and p1.00:

Sample Time (in milliseconds per operation)
Framework Name p0.90 p0.999 p1.0
JMapper 10-3 0.008 64
MapStruct 10-3 0.010 68
Orika 0.006 0.278 32
ModelMapper 0.083 2.398 97
Dozer 0.146 4.526 118

While the exact results of the simple example and the real-life example were clearly different, but they do follow more or less the same trend. In both examples, we saw a close contest between JMapper and MapStruct for the top spot.

6.5. Conclusion

Based on the real-life model testing we performed in this section, we can see that the best performance clearly belongs to JMapper, although MapStruct is a close second. In the same tests, we see that Dozer is consistently at the bottom of our results table, except for SingleShotTime.

7. Summary

In this article, we've conducted performance tests of five popular Java bean mapping frameworks: ModelMapper, MapStruct, Orika, Dozer, and JMapper.

Както винаги, примерни кодове могат да бъдат намерени в GitHub.