Javaマッピングフレームワークのパフォーマンス

1. はじめに

複数の層で構成される大規模なJavaアプリケーションを作成するには、永続モデル、ドメインモデル、またはいわゆるDTOなどの複数のモデルを使用する必要があります。異なるアプリケーション層に複数のモデルを使用するには、Bean間のマッピング方法を提供する必要があります。

これを手動で行うと、すぐに多くの定型コードが作成され、多くの時間がかかります。幸いなことに、Javaには複数のオブジェクトマッピングフレームワークがあります。

このチュートリアルでは、最も人気のあるJavaマッピングフレームワークのパフォーマンスを比較します。

2マッピングフレームワーク

2.1. ドーザー

  • Dozerは、あるオブジェクトから別のオブジェクトにデータをコピーするために再帰を使用するマッピングフレームワークです。フレームワークは、Bean間でプロパティをコピーできるだけでなく、異なるタイプ間で自動的に変換することもできます。

Dozerフレームワークを使用するには、このような依存関係をプロジェクトに追加する必要があります。

<dependency>
    <groupId>net.sf.dozer</groupId>
    <artifactId>dozer</artifactId>
    <version>5.5.1</version>
</dependency>

Dozerフレームワークの使用法についてのより多くの情報はこのリンクで見つけることができます:/dozer[記事]。

フレームワークのドキュメントはhttp://dozer.sourceforge.net/documentation/gettingstarted.html[ここ]にあります。

2.2. オリカ

  • Orikaは、あるオブジェクトから別のオブジェクトに再帰的にデータをコピーする、BeanからBeanへのマッピングフレームワークです。

Orikaの仕事の一般的な原則は、Dozerと似ています。この2つの主な違いは、** Orikaがバイトコード生成を使用していることです。これにより、最小限のオーバーヘッドでより高速のマッパーを生成できます。

それを使うためには、 私たちのプロジェクトにそのような依存関係を追加する必要があります。

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

Orikaの使用法についてのより詳細な情報はこのリンクで見つけることができます:/orika-mapping[article]。

フレームワークの実際のドキュメントはhttps://orika-mapper.github.io/orika-docs/[ここ]にあります。

2.3. MapStruct

MapStructは、Beanマッパークラスを自動的に生成するコードジェネレータです。

MapStructには、異なるデータ型間で変換する機能もあります。

使い方の詳細については/mapstruct[article]を参照してください。

MapStruct____をプロジェクトに追加するには、次の依存関係を含める必要があります。

<dependency>3
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.2.0.Final</version>
</dependency>

フレームワークのドキュメントはhttp://mapstruct.org/[こちら]にあります。

2.4. モデルマッパー

  • ModelMapperは、慣例に基づいてオブジェクトのマッピング方法を決定することによって、オブジェクトマッピングを単純化することを目的としたフレームワークです。それはタイプセーフでリファクタリングセーフなAPIを提供します。

フレームワークについてのより多くの情報はhttp://modelmapper.org/[documentation]で見つけることができます。

ModelMapperをプロジェクトに含めるには、次の依存関係を追加する必要があります。

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

2.5. JMapper

JMapperは、Java Beans間で使いやすく高性能なマッピングを提供することを目的としたマッピングフレームワークです。

このフレームワークは、注釈とリレーショナルマッピングを使用してDRYの原則を適用することを目的としています。

このフレームワークでは、さまざまな設定方法が可能です。

注釈ベース、XMLベース、またはAPIベース。

フレームワークについての詳細はhttps://github.com/jmapper-framework/jmapper-core/wiki[documentation]にあります。

JMapperをプロジェクトに含めるには、その依存関係を追加する必要があります。

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

3テスト モデル

マッピングを正しくテストできるようにするには、ソースモデルとターゲットモデルが必要です。 2つのテストモデルを作成しました。

1つ目は String フィールドを1つだけ持つ単純なPOJOです。これにより、より単純なケースでフレームワークを比較し、より複雑なBeanを使用した場合に何かが変更されるかどうかを確認できます。

単純なソースモデルは以下のようになります。

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

そしてその目的地はかなり似ています:

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

実際のソースBeanの例は次のようになります。

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
}

全体のモデル構造はhttps://github.com/eugenp/tutorials/tree/master/performance-tests/src/main/java/com/baeldung/performancetests/model/source[ここ]にあります。

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マッピングファイルが必要です。以下のセクションがあります。

<mappings xmlns="http://dozer.sourceforge.net"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://dozer.sourceforge.net
  http://dozer.sourceforge.net/schema/beanmapping.xsd">

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

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はそれ自身でenum型を変換することはできず、カスタムマッピング関数を作成する必要があります。

@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 は、マッピングしたいクラスを提供するだけで済みます。

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を使用できます。使用方法の詳細については、/java-microbenchmark-harness[article]にあります。

__BenchmarkMode to Mode.All を指定して、それぞれの Converter__に個別のベンチマークを作成しました。

5.1. 平均時間

JMHは平均実行時間について以下の結果を返しました(少ないほど良い)。

リンク:/uploads/AVGTSimple-100x21.png%20100w[]

このベンチマークは、MapStructの平均作業時間がはるかに優れていることを明確に示しています。

5.2. スループット

このモードでは、ベンチマークは1秒あたりの操作数を返します。以下の結果が得られました( 多いほど良い )。

リンク:/uploads/THRPTRealLife2-100x19.png%20100w[]

繰り返しますが、MapStructはすべてのフレームワークの中で最速でした。

5.3. シングルショットタイム

このモードでは、開始から終了までの単一操作の時間を測定できます。ベンチマークは以下の結果をもたらしました(少ないほど良い)。

リンク:/uploads/SSTSimple-100x20.png%20100w[]

繰り返しますが、MapStruct が最速でしたが、ModelMapper は以前のテストよりも良い結果をもたらしました。

5.4. サンプル時間

このモードでは、各操作の時間をサンプリングできます。 3つの異なる百分位数の結果は以下のようになります。

リンク:/uploads/SampleTimeSimple-100x22.png%20100w[]

すべてのベンチマークは、MapStructが最高のパフォーマンスを発揮することを示しています。JMapperも__SingleShotTimeの結果が大幅に悪くなっていますが、非常に良い選択です。

6.実生活モデルのテスト

パフォーマンステストには、Java Microbenchmark Harnessを使用できます。使用方法の詳細については、/java-microbenchmark-harness[article]を参照してください。

__BenchmarkMode to Mode.All を指定して、それぞれの Converter__に個別のベンチマークを作成しました。

6.1. 平均時間

JMHは平均実行時間について以下の結果を返しました(少ないほど良い)。

リンク:/uploads/AVGTRealLife-100x20.png%20100w[]

6.2. スループット

このモードでは、ベンチマークは1秒あたりの操作数を返します。それぞれのマッパーについて、次のような結果が得られました(多いほど良い)

リンク:/uploads/THRPTRealLife-100x20.png%20100w[]

6.3. シングルショットタイム

このモードでは、開始から終了までの単一操作の時間を測定できます。ベンチマークは以下の結果をもたらしました(少ないほど良いです)。

リンク:/uploads/SSTRealLife-100x20.png%20100w[]

6.4. サンプル時間

このモードでは、各操作の時間をサンプリングできます。サンプリング結果はパーセンタイルに分割されています。3つの異なるパーセンタイル、p0.90、p0.999 、およびp1.00の結果を示します。

リンク:/uploads/SampleTimeRealLife-100x24.png%20100w[]

単純な例と実際の例の正確な結果は明らかに異なっていましたが、それらは同じ傾向に従います。両方の例とも、どちらのアルゴリズムが最も速く、どれが最も遅いのかという点で同様の結果を示しました。

最高のパフォーマンスは明らかにMapStructに属し、最悪のパフォーマンスはOrikaに属します。

7. まとめ

この記事では、5つの一般的なJava Beanマッピング・フレームワーク、ModelMapper MapStruct Orika Dozer、およびJMapperのパフォーマンステストを行いました。

いつものように、コードサンプルはhttps://github.com/eugenp/tutorials/tree/master/performance-tests[GitHubに追加]を見つけることができます。