Производительность Java Mapping Frameworks

Производительность Java Mapping Frameworks

1. Introductionс

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

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

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

2. Структуры отображения

2.1. Dozerс

Dozer is a mapping framework that uses recursion to copy data from one object to another. Фреймворк способен не только копировать свойства между компонентами, но также может автоматически конвертировать различные типы.

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


    net.sf.dozer
    dozer
    5.5.1

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

Документацию по фреймворку можно найтиhere.

2.2. Орика

Orika is a bean to bean mapping framework that recursively copies data from one object to another.

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

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


    ma.glasnost.orika
    orika-core
    1.5.2

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

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

2.3. MapStructс

MapStruct - это генератор кода, который автоматически генерирует классы bean mapper.

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

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

3
    org.mapstruct
    mapstruct-processor
    1.2.0.Final

Документацию по фреймворку можно найтиhere.

2.4. ModelMapperс

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

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

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


  org.modelmapper
  modelmapper
  1.1.0

2.5. JMapperс

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

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

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

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

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


    com.googlecode.jmapper-framework
    jmapper-core
    1.6.0.1

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

Всю структуру модели можно найтиhere.

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. DozerConverterс

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



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

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

public class DozerConverter implements Converter {
    private final Mapper mapper;

    public DozerConverter() {
        DozerBeanMapper mapper = new DozerBeanMapper();
        mapper.addMapping(
          DozerConverter.class.getResourceAsStream("/dozer-mapping.xml"));
        this.mapper = mapper;
    }

    @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с

Определение структуры карты довольно просто, поскольку оно полностью основано на генерации кода:

@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.example.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 требуется только предоставить классы, которые мы хотим отобразить:

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, дополнительную информацию о том, как его использовать, можно найти в этомarticle.

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

5.1. AverageTimeс

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

image

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

5.2. Throughputс

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

image

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

5.3. SingleShotTimeс

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

image

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

5.4. SampleTimeс

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

image

Все тесты показали, что MapStruct и JMapper - хорошие варианты в зависимости от сценария, хотя MapStruct дал значительно худшие результаты дляSingleShotTime..

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

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

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

6.1. AverageTimeс

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

image

6.2. Throughputс

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

image

6.3. SingleShotTimeс

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

image

6.4. SampleTimeс

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

image

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

6.5. Заключение

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

7. Summaryс

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

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