Перейти к основному содержимому

Производительность картографических фреймворков Java

· 10 мин. чтения

1. Введение

Создание больших приложений Java, состоящих из нескольких уровней, требует использования нескольких моделей, таких как модель сохраняемости, модель предметной области или так называемые DTO. Использование нескольких моделей для разных уровней приложения потребует от нас предоставления способа отображения между bean-компонентами.

Выполнение этого вручную может быстро создать много стандартного кода и занять много времени. К счастью для нас, для Java существует несколько фреймворков отображения объектов.

В этом уроке мы собираемся сравнить производительность самых популярных фреймворков для сопоставления Java.

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

2.1. бульдозер

Dozer — это платформа сопоставления, которая использует рекурсию для копирования данных из одного объекта в другой . Фреймворк может не только копировать свойства между bean-компонентами, но и автоматически преобразовывать их между разными типами.

Чтобы использовать фреймворк Dozer, нам нужно добавить в наш проект такую зависимость:

<dependency>
<groupId>com.github.dozermapper</groupId>
<artifactId>dozer-core</artifactId>
<version>6.5.0</version>
</dependency>

Подробнее об использовании фреймворка Dozer можно прочитать в этой статье .

Документацию по фреймворку можно найти здесь .

2.2. Орика

Orika — это фреймворк отображения bean-bean, который рекурсивно копирует данные из одного объекта в другой .

Общий принцип работы Орика аналогичен Дозеру. Основное различие между ними заключается в том, что Orika использует генерацию байт-кода . Это позволяет создавать более быстрые преобразователи с минимальными накладными расходами.

Чтобы использовать его, `` нам нужно добавить в наш проект такую зависимость:

<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.4</version>
</dependency>

Более подробную информацию об использовании Orika можно найти в этой статье .

Актуальную документацию фреймворка можно найти здесь .

2.3. MapStruct

MapStruct — это генератор кода, который автоматически генерирует классы сопоставления компонентов.

MapStruct также имеет возможность преобразования между различными типами данных. Более подробную информацию о том, как его использовать, можно найти в этой статье .

Чтобы добавить MapStruct `` в наш проект, нам нужно включить следующую зависимость:

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.3.1.Final</version>
</dependency>

Документацию по фреймворку можно найти здесь.

2.4. ModelMapper

ModelMapper — это платформа, целью которой является упрощение сопоставления объектов путем определения того, как объекты сопоставляются друг с другом на основе соглашений. Он предоставляет типобезопасный и безопасный для рефакторинга API.

Подробнее о фреймворке можно узнать в документации .

Чтобы включить ModelMapper в наш проект, нам нужно добавить следующую зависимость:

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

2.5. JMapper

JMapper — это инфраструктура сопоставления, целью которой является обеспечение простого в использовании и высокопроизводительного сопоставления между Java Beans.

Фреймворк направлен на применение принципа DRY с использованием аннотаций и реляционного отображения.

Фреймворк допускает различные способы настройки: на основе аннотаций, на основе XML или API.

Более подробную информацию о фреймворке можно найти в его документации .

Чтобы включить JMapper в наш проект, нам нужно добавить его зависимость:

<dependency>
<groupId>com.googlecode.jmapper-framework</groupId>
<artifactId>jmapper-core</artifactId>
<version>1.6.1.CR2</version>
</dependency>

3. Тестовая модель

Чтобы правильно протестировать сопоставление, нам нужны исходная и целевая модели. Мы создали две модели тестирования.

Первый — это просто простой POJO с одним полем String , это позволило нам сравнить фреймворки в более простых случаях и проверить, изменится ли что-нибудь, если мы используем более сложные bean-компоненты.

Простая исходная модель выглядит следующим образом:

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<Product> 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<Product> 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. Преобразователи

Чтобы упростить дизайн тестовой установки, мы создали интерфейс Converter :

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

И все наши пользовательские картографы будут реализовывать этот интерфейс.

4.1. OrikaConverter

Orika допускает полную реализацию API, это значительно упрощает создание маппера:

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. ДозерКонвертер

Для Dozer требуется XML-файл сопоставления со следующими разделами:

<mappings xmlns="http://dozermapper.github.io/schema/bean-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping
https://dozermapper.github.io/schema/bean-mapping.xsd">

<mapping>
<class-a>com.foreach.performancetests.model.source.SourceOrder</class-a>
<class-b>com.foreach.performancetests.model.destination.Order</class-b>
<field>
<a>status</a>
<b>orderStatus</b>
</field>
</mapping>
<mapping>
<class-a>com.foreach.performancetests.model.source.SourceCode</class-a>
<class-b>com.foreach.performancetests.model.destination.DestinationCode</class-b>
</mapping>
</mappings>

После определения сопоставления XML мы можем использовать его из кода:

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 довольно простое, так как оно полностью основано на генерации кода:

@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 требует дополнительной работы. После реализации интерфейса:

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);
}
}

Нам также нужно добавить аннотации @JMap к каждому полю целевого класса. Кроме того, JMapper не может самостоятельно преобразовывать типы перечислений и требует от нас создания пользовательских функций сопоставления:

@JMapConversion(from = "paymentType", to = "paymentType")
public PaymentType conversion(com.foreach.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. МодельMapperConverter

ModelMapperConverter требует, чтобы мы предоставили только те классы, которые мы хотим отобразить:

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. Простое тестирование модели

Для тестирования производительности мы можем использовать Java Microbenchmark Harness, больше информации о том, как его использовать, можно найти в этой статье .

Мы создали отдельный тест для каждого преобразователя , указав BenchmarkMode в Mode.All .

5.1. Среднее время

JMH вернул следующие результаты для среднего времени работы (чем меньше, тем лучше):

   | Имя фреймворка    | Среднее время работы (в мс на операцию)   | 
| MapStruct | 10 ^-5 |
| JMapper | 10 ^-5 |
| Орика | 0,001 |
| ModelMapper | 0,001 |
| бульдозер | 0,002 |

Этот тест ясно показывает, что и MapStruct, и JMapper имеют лучшее среднее время работы.

5.2. пропускная способность

В этом режиме бенчмарк возвращает количество операций в секунду. Мы получили следующие результаты ( чем больше, тем лучше ):

   | Имя фреймворка    | Пропускная способность (в операциях на мс)   | 
| MapStruct | 133719 |
| JMapper | 106978 |
| Орика | 1800 |
| ModelMapper | 978 |
| бульдозер | 471 |







В режиме пропускной способности MapStruct был самым быстрым из протестированных фреймворков, а JMapper занимал второе место.

5.3. SingleShotTime

Этот режим позволяет измерять время одной операции от ее начала до конца. Бенчмарк дал следующий результат (чем меньше, тем лучше):

   | Имя фреймворка    | Время одиночного срабатывания (в мс на операцию)   | 
| JMapper | 0,015 |
| MapStruct | 0,450 |
| бульдозер | 2.094 |
| Орика | 2.898 |
| ModelMapper | 4.837 |

Здесь мы видим, что JMapper возвращает лучший результат, чем MapStruct.

5.4. SampleTime

Этот режим позволяет замерять время каждой операции. Результаты для трех разных процентилей выглядят следующим образом:

   |     | Время выборки (в миллисекундах на операцию)   | 
| Имя фреймворка | р0,90 | p0,999 | р1.0 |
| JMapper | 10 ^-4 | 0,001 | 2,6 |
| MapStruct | 10 ^-4 | 0,001 | 3 |
| Орика | 0,001 | 0,010 | 4 |
| ModelMapper | 0,002 | 0,015 | 3.2 |
| бульдозер | 0,003 | 0,021 | 25 |

Все тесты показали, что MapStruct и JMapper являются хорошим выбором в зависимости от сценария .

6. Тестирование моделей в реальных условиях

Для тестирования производительности мы можем использовать Java Microbenchmark Harness, больше информации о том, как его использовать, можно найти в этой статье .

Мы создали отдельный бенчмарк для каждого Converter с указанием BenchmarkMode в Mode.All .

6.1. Среднее время

JMH вернул следующие результаты для среднего времени работы (чем меньше, тем лучше):

   | Имя фреймворка    | Среднее время работы (в мс на операцию)   | 
| MapStruct | 10 ^-4 |
| JMapper | 10 ^-4 |
| Орика | 0,004 |
| ModelMapper | 0,059 |
| бульдозер | 0,103 |

6.2. пропускная способность

В этом режиме бенчмарк возвращает количество операций в секунду. Для каждого из мапперов мы получили следующие результаты (чем больше, тем лучше):

   | Имя фреймворка    | Пропускная способность (в операциях на мс)   | 
| JMapper | 7691 |
| MapStruct | 7120 |
| Орика | 281 |
| ModelMapper | 19 |
| бульдозер | 10 |

6.3. SingleShotTime

Этот режим позволяет измерять время одной операции от ее начала до конца. Бенчмарк дал следующие результаты (чем меньше, тем лучше):

   | Имя фреймворка    | Время одиночного срабатывания (в мс на операцию)   | 
| JMapper | 0,253 |
| MapStruct | 0,532 |
| бульдозер | 9.495 |
| ModelMapper | 16.288 |
| Орика | 18.081 |

6.4. SampleTime

Этот режим позволяет замерять время каждой операции. Результаты выборки разбиты на процентили, мы представим результаты для трех разных процентилей p0,90, p0,999 и p1,00:

   |     | Время выборки (в миллисекундах на операцию)   | 
| Имя фреймворка | р0,90 | p0,999 | р1.0 |
| JMapper | 10 ^-3 | 0,008 | 64 |
| MapStruct | 10 ^-3 | 0,010 | 68 |
| Орика | 0,006 | 0,278 | 32 |
| ModelMapper | 0,083 | 2.398 | 97 |
| бульдозер | 0,146 | 4.526 | 118 |

Хотя точные результаты простого примера и примера из реальной жизни явно различались, они следуют более или менее одной и той же тенденции. В обоих примерах мы видели напряженную борьбу между JMapper и MapStruct за первое место.

6.5. Вывод

Основываясь на реальном тестировании модели, которое мы провели в этом разделе, мы видим, что лучшая производительность явно принадлежит JMapper, хотя MapStruct занимает второе место. В тех же тестах мы видим, что Dozer стабильно находится внизу нашей таблицы результатов, за исключением SingleShotTime .

7. Резюме

В этой статье мы провели тесты производительности пяти популярных фреймворков отображения Java-бинов: ModelMapper , MapStruct , Orika , Dozer и JMapper.

Как всегда, образцы кода можно найти на GitHub .