Dozerによるマッピングの手引き

Dozerによるマッピングのガイド

1. 概要

Dozerは、属性ごとに、あるオブジェクトから別のオブジェクトにデータを再帰的にコピーするJava Bean to Java Bean mapperです。

ライブラリは、Java Beansの属性名間のマッピングだけでなく、automatically converts between typesもサポートします(それらが異なる場合)。

ほとんどの変換シナリオはそのままでサポートされますが、Dozerではspecify custom conversions via XMLもサポートされています。

2. 簡単な例

最初の例では、ソースデータオブジェクトと宛先データオブジェクトがすべて同じ共通の属性名を共有していると仮定します。

これは、Dozerで実行できる最も基本的なマッピングです。

public class Source {
    private String name;
    private int age;

    public Source() {}

    public Source(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // standard getters and setters
}

次に、宛先ファイルDest.java

public class Dest {
    private String name;
    private int age;

    public Dest() {}

    public Dest(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // standard getters and setters
}

Dozerは内部で反射を使用するため、必ずinclude the default or zero argument constructorsにする必要があります。

また、パフォーマンスを向上させるために、マッパーをグローバルにして、テスト全体で使用する単一のオブジェクトを作成しましょう。

DozerBeanMapper mapper;

@Before
public void before() throws Exception {
    mapper = new DozerBeanMapper();
}

ここで、最初のテストを実行して、Sourceオブジェクトを作成するときに、それをDestオブジェクトに直接マップできることを確認しましょう。

@Test
public void givenSourceObjectAndDestClass_whenMapsSameNameFieldsCorrectly_
  thenCorrect() {
    Source source = new Source("example", 10);
    Dest dest = mapper.map(source, Dest.class);

    assertEquals(dest.getName(), "example");
    assertEquals(dest.getAge(), 10);
}

ご覧のとおり、Dozerマッピング後、結果はSourceオブジェクトと同じフィールド名を持つすべてのフィールドの値を含むDestオブジェクトの新しいインスタンスになります。

あるいは、mapperDestクラスを渡す代わりに、Destオブジェクトを作成し、mapperにその参照を渡すこともできます。

@Test
public void givenSourceObjectAndDestObject_whenMapsSameNameFieldsCorrectly_
  thenCorrect() {
    Source source = new Source("example", 10);
    Dest dest = new Dest();
    mapper.map(source, dest);

    assertEquals(dest.getName(), "example");
    assertEquals(dest.getAge(), 10);
}

3. Mavenセットアップ

Dozerがどのように機能するかについての基本的な理解ができたので、次の依存関係をpom.xmlに追加しましょう。


    net.sf.dozer
    dozer
    5.5.1

最新バージョンはhereで入手できます。

4. データ変換の例

既に知っているように、Dozerは、両方のクラスで同じ名前の属性を見つける限り、既存のオブジェクトを別のオブジェクトにマップできます。

ただし、常にそうであるとは限りません。したがって、マップされた属性のいずれかが異なるデータ型である場合、Dozerマッピングエンジンはautomatically perform a data type conversionになります。

この新しいコンセプトの実際を見てみましょう。

public class Source2 {
    private String id;
    private double points;

    public Source2() {}

    public Source2(String id, double points) {
        this.id = id;
        this.points = points;
    }

    // standard getters and setters
}

そして、宛先クラス:

public class Dest2 {
    private int id;
    private int points;

    public Dest2() {}

    public Dest2(int id, int points) {
        super();
        this.id = id;
        this.points = points;
    }

    // standard getters and setters
}

属性名は同じですが、their data types are differentであることに注意してください。

ソースクラスでは、idStringであり、pointsdoubleですが、宛先クラスでは、idpointsは両方ともです。 integers。

Dozerがどのように変換を正しく処理するかを見てみましょう。

@Test
public void givenSourceAndDestWithDifferentFieldTypes_
  whenMapsAndAutoConverts_thenCorrect() {
    Source2 source = new Source2("320", 15.2);
    Dest2 dest = mapper.map(source, Dest2.class);

    assertEquals(dest.getId(), 320);
    assertEquals(dest.getPoints(), 15);
}

“320”15.2Stringdoubleをソースオブジェクトに渡し、結果には32015,の両方がintegerになりました。 )宛先オブジェクトのss。

5. XMLによる基本的なカスタムマッピング

これまでに見てきたすべての例では、ソースデータオブジェクトと宛先データオブジェクトの両方が同じフィールド名を持っているため、簡単にマッピングできます。

ただし、実際のアプリケーションでは、マッピングする2つのデータオブジェクトに、共通のプロパティ名を共有するフィールドがない場合が数え切れないほどあります。

これを解決するために、Dozerにはcustom mapping configuration in XMLを作成するオプションがあります。

このXMLファイルでは、Dozerマッピングエンジンがどのソース属性をどの宛先属性にマッピングするかを決定するために使用するクラスマッピングエントリを定義できます。

例を見てみましょう。フランスのプログラマーによって作成されたアプリケーションから、オブジェクトに名前を付ける英語スタイルへのデータオブジェクトのマーシャリングを解除してみましょう。

namenickname、およびageフィールドを持つPersonオブジェクトがあります。

public class Person {
    private String name;
    private String nickname;
    private int age;

    public Person() {}

    public Person(String name, String nickname, int age) {
        super();
        this.name = name;
        this.nickname = nickname;
        this.age = age;
    }

    // standard getters and setters
}

マーシャリングを解除するオブジェクトの名前はPersonneで、フィールドnomsurnom、およびageがあります。

public class Personne {
    private String nom;
    private String surnom;
    private int age;

    public Personne() {}

    public Personne(String nom, String surnom, int age) {
        super();
        this.nom = nom;
        this.surnom = surnom;
        this.age = age;
    }

    // standard getters and setters
}

これらのオブジェクトは実際には同じ目的を達成しますが、言語の壁があります。 その障壁を支援するために、Dozerを使用してフランス語のPersonneオブジェクトをPersonオブジェクトにマップできます。

Dozerがこれを行うのを助けるために、カスタムマッピングファイルを作成するだけで済みます。これをdozer_mapping.xmlと呼びます。



    
        com.example.dozer.Personne
        com.example.dozer.Person
        
            nom
            name
        
        
            surnom
            nickname
        
    

これは、使用できるカスタムXMLマッピングファイルの最も単純な例です。

今のところ、子<mapping>を持つルート要素として<mappings>があることに気付くだけで十分です。これらの子は、クラスの発生率と同じ数だけ<mappings>内に持つことができます。カスタムマッピングが必要なペア。

<mapping></mapping>タグ内でソースクラスと宛先クラスを指定する方法にも注意してください。 この後に、カスタムマッピングが必要な送信元と宛先のフィールドペアごとに<field></field>が続きます。

最後に、カスタムマッピングファイルにフィールドageが含まれていないことに注意してください。 年齢を表すフランス語はまだ年齢です。これは、Dozerのもう1つの重要な特徴を示しています。

Properties that are of the same name do not need to be specified in the mapping XML file。 Dozerは、同じプロパティ名を持つすべてのフィールドをソースオブジェクトからターゲットオブジェクトに自動的にマップします。

次に、カスタムXMLファイルをsrcフォルダーのすぐ下のクラスパスに配置します。 ただし、クラスパスのどこに配置しても、Dozerはクラスパス全体を検索して指定されたファイルを探します。

マッピングファイルをmapperに追加するヘル​​パーメソッドを作成しましょう。

public void configureMapper(String... mappingFileUrls) {
    mapper.setMappingFiles(Arrays.asList(mappingFileUrls));
}

コードをテストしてみましょう。

@Test
public void givenSrcAndDestWithDifferentFieldNamesWithCustomMapper_
  whenMaps_thenCorrect() {
    configureMapper("dozer_mapping.xml");
    Personne frenchAppPerson = new Personne("Sylvester Stallone", "Rambo", 70);
    Person englishAppPerson = mapper.map(frenchAppPerson, Person.class);

    assertEquals(englishAppPerson.getName(), frenchAppPerson.getNom());
    assertEquals(englishAppPerson.getNickname(), frenchAppPerson.getSurnom());
    assertEquals(englishAppPerson.getAge(), frenchAppPerson.getAge());
}

テストに示されているように、DozerBeanMapperはカスタムXMLマッピングファイルのリストを受け入れ、実行時にそれぞれをいつ使用するかを決定します。

ここで、これらのデータオブジェクトを英語アプリとフランス語アプリの間でアンマーシャリングし始めると仮定します。 XMLファイルDozer is smart enough to map the objects both ways with only one mapping configurationに別のマッピングを作成する必要はありません。

@Test
public void givenSrcAndDestWithDifferentFieldNamesWithCustomMapper_
  whenMapsBidirectionally_thenCorrect() {
    configureMapper("dozer_mapping.xml");
    Person englishAppPerson = new Person("Dwayne Johnson", "The Rock", 44);
    Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(),englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}

したがって、このサンプルテストでは、Dozerのこの別の機能(the Dozer mapping engine is bi-directional)を使用します。したがって、宛先オブジェクトをソースオブジェクトにマップする場合は、XMLファイルに別のクラスマッピングを追加する必要はありません。

クラスパスの外部からカスタムマッピングファイルをロードすることもできます。必要に応じて、リソース名に「file:」プレフィックスを使用します。

Windows環境(以下のテストなど)では、もちろんWindows固有のファイル構文を使用します。

Linuxボックスでは、ファイルを/homeの下に保存してから、次のようにします。

configureMapper("file:/home/dozer_mapping.xml");

Mac OSの場合:

configureMapper("file:/Users/me/dozer_mapping.xml");

github projectから単体テストを実行している場合(必要です)、マッピングファイルを適切な場所にコピーして、configureMapperメソッドの入力を変更できます。

マッピングファイルは、GitHubプロジェクトのtest / resourcesフォルダーにあります。

@Test
public void givenMappingFileOutsideClasspath_whenMaps_thenCorrect() {
    configureMapper("file:E:\\dozer_mapping.xml");
    Person englishAppPerson = new Person("Marshall Bruce Mathers III","Eminem", 43);
    Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(),englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}

6. ワイルドカードとさらなるXMLのカスタマイズ

dozer_mapping2.xmlという2番目のカスタムマッピングファイルを作成しましょう。



    
        com.example.dozer.Personne
        com.example.dozer.Person
        
            nom
            name
        
        
            surnom
            nickname
        
    

以前は存在しなかった属性wildcard<mapping></mapping>要素に追加したことに注意してください。

デフォルトでは、wildcardtrueです。 Dozerエンジンに、ソースオブジェクトのすべてのフィールドを適切な宛先フィールドにマップするように指示します。

これをfalse,に設定すると、XMLで明示的に指定したフィールドのみをマップするようにDozerに指示します。

したがって、上記の構成では、2つのフィールドのみをマップし、ageを除外します。

@Test
public void givenSrcAndDest_whenMapsOnlySpecifiedFields_thenCorrect() {
    configureMapper("dozer_mapping2.xml");
    Person englishAppPerson = new Person("Shawn Corey Carter","Jay Z", 46);
    Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(),englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), 0);
}

最後のアサーションでわかるように、宛先ageフィールドは0のままでした。

7. 注釈によるカスタムマッピング

単純なマッピングの場合、およびマッピングするデータオブジェクトへの書き込みアクセス権もある場合、XMLマッピングを使用する必要はありません。

注釈を使用して異なる名前のフィールドをマッピングすることは非常に簡単であり、XMLマッピングよりもはるかに少ないコードを記述する必要がありますが、単純な場合にのみ役立ちます。

フィールドをまったく変更せずに、データオブジェクトをPerson2.javaPersonne2.javaに複製しましょう。

これを実装するには、ソースオブジェクトのgetterメソッドに@mapper(“destinationFieldName”)アノテーションを追加するだけです。 そのようです:

@Mapping("name")
public String getNom() {
    return nom;
}

@Mapping("nickname")
public String getSurnom() {
    return surnom;
}

今回はPersonne2をソースとして扱っていますが、Dozer Engineのbi-directional natureのため、問題ではありません。

XML関連のコードがすべて削除されたため、テストコードは短くなりました。

@Test
public void givenAnnotatedSrcFields_whenMapsToRightDestField_thenCorrect() {
    Person2 englishAppPerson = new Person2("Jean-Claude Van Damme", "JCVD", 55);
    Personne2 frenchAppPerson = mapper.map(englishAppPerson, Personne2.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(), englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}

双方向性もテストできます。

@Test
public void givenAnnotatedSrcFields_whenMapsToRightDestFieldBidirectionally_
  thenCorrect() {
    Personne2 frenchAppPerson = new Personne2("Jason Statham", "transporter", 49);
    Person2 englishAppPerson = mapper.map(frenchAppPerson, Person2.class);

    assertEquals(englishAppPerson.getName(), frenchAppPerson.getNom());
    assertEquals(englishAppPerson.getNickname(), frenchAppPerson.getSurnom());
    assertEquals(englishAppPerson.getAge(), frenchAppPerson.getAge());
}

8. カスタムAPIマッピング

フランスのアプリケーションからデータオブジェクトを非整列化する以前の例では、XMLと注釈を使用してマッピングをカスタマイズしました。

Dozerで使用できるもう1つの代替手段は、アノテーションマッピングに似ていますが、APIマッピングです。 XML構成を排除し、Javaコードを厳密に使用するため、これらは似ています。

この場合、次のように最も単純なケースで定義されたBeanMappingBuilderクラスを使用します。

BeanMappingBuilder builder = new BeanMappingBuilder() {
    @Override
    protected void configure() {
        mapping(Person.class, Personne.class)
          .fields("name", "nom")
            .fields("nickname", "surnom");
    }
};

ご覧のとおり、抽象メソッドconfigure()があり、構成を定義するためにオーバーライドする必要があります。 次に、XMLの<mapping></mapping>タグと同様に、必要な数のTypeMappingBuildersを定義します。

これらのビルダーは、マッピングするソースフィールドとデスティネーションフィールドをDozerに伝えます。 次に、異なるAPIを使用して、XMLマッピングファイルと同じようにBeanMappingBuilderDozerBeanMapperに渡します。

@Test
public void givenApiMapper_whenMaps_thenCorrect() {
    mapper.addMapping(builder);

    Personne frenchAppPerson = new Personne("Sylvester Stallone", "Rambo", 70);
    Person englishAppPerson = mapper.map(frenchAppPerson, Person.class);

    assertEquals(englishAppPerson.getName(), frenchAppPerson.getNom());
    assertEquals(englishAppPerson.getNickname(), frenchAppPerson.getSurnom());
    assertEquals(englishAppPerson.getAge(), frenchAppPerson.getAge());
}

マッピングAPIも双方向です。

@Test
public void givenApiMapper_whenMapsBidirectionally_thenCorrect() {
    mapper.addMapping(builder);

    Person englishAppPerson = new Person("Sylvester Stallone", "Rambo", 70);
    Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(), englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}

または、このビルダー構成で明示的に指定されたフィールドのみをマッピングすることを選択できます。

BeanMappingBuilder builderMinusAge = new BeanMappingBuilder() {
    @Override
    protected void configure() {
        mapping(Person.class, Personne.class)
          .fields("name", "nom")
            .fields("nickname", "surnom")
              .exclude("age");
    }
};

そして、age==0テストが戻ってきました:

@Test
public void givenApiMapper_whenMapsOnlySpecifiedFields_thenCorrect() {
    mapper.addMapping(builderMinusAge);
    Person englishAppPerson = new Person("Sylvester Stallone", "Rambo", 70);
    Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(), englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), 0);
}

9. カスタムコンバーター

マッピングで直面する可能性のあるもう1つのシナリオは、perform custom mapping between two objectsを実行する場合です。

フランス語のPersonneオブジェクトのように、ソースフィールド名と宛先フィールド名が異なるシナリオを見てきました。 このセクションでは、別の問題を解決します。

マーシャリングを解除するデータオブジェクトが、longやUnix時間などの日付と時刻のフィールドを次のように表す場合はどうなりますか。

1182882159000

しかし、私たち自身の同等のデータオブジェクトは、String:などのこのISO形式で同じ日付と時刻のフィールドと値を表します

2007-06-26T21:22:39Z

デフォルトのコンバーターは、次のようにlong値をStringに単純にマップします。

"1182882159000"

これは間違いなく私たちのアプリにバグを引き起こすでしょう。 では、どうやってこれを解決するのでしょうか。 マッピングXMLファイルのadding a configuration blockspecifying our own converterで解決します。

まず、リモートアプリケーションのPerson DTOをname,で複製し、次に生年月日、dtobフィールドで複製します。

public class Personne3 {
    private String name;
    private long dtob;

    public Personne3(String name, long dtob) {
        super();
        this.name = name;
        this.dtob = dtob;
    }

    // standard getters and setters
}

そしてここに私たち自身のものがあります:

public class Person3 {
    private String name;
    private String dtob;

    public Person3(String name, String dtob) {
        super();
        this.name = name;
        this.dtob = dtob;
    }

    // standard getters and setters
}

ソースDTOと宛先DTOのdtobのタイプの違いに注意してください。

また、マッピングXMLでDozerに渡す独自のCustomConverterを作成しましょう。

public class MyCustomConvertor implements CustomConverter {
    @Override
    public Object convert(Object dest, Object source, Class arg2, Class arg3) {
        if (source == null)
            return null;

        if (source instanceof Personne3) {
            Personne3 person = (Personne3) source;
            Date date = new Date(person.getDtob());
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
            String isoDate = format.format(date);
            return new Person3(person.getName(), isoDate);

        } else if (source instanceof Person3) {
            Person3 person = (Person3) source;
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
            Date date = format.parse(person.getDtob());
            long timestamp = date.getTime();
            return new Personne3(person.getName(), timestamp);
        }
    }
}

convert()メソッドをオーバーライドしてから、返したいものを返すだけです。 ソースオブジェクトと宛先オブジェクト、およびそれらのクラスタイプを利用できます。

ソースがマッピングしている2つのクラスのいずれかであると想定して、双方向性をどのように処理しているかに注目してください。

わかりやすくするために、新しいマッピングファイルdozer_custom_convertor.xmlを作成します。



    
        
            
                com.example.dozer.Personne3
                com.example.dozer.Person3
            
        
    

これは前のセクションで見た法線マッピングファイルです。<configuration></configuration>ブロックのみを追加しました。このブロック内に、それぞれのソースデータクラスと宛先データクラスで必要な数のカスタムコンバーターを定義できます。

新しいCustomConverterコードをテストしてみましょう。

@Test
public void givenSrcAndDestWithDifferentFieldTypes_whenAbleToCustomConvert_
  thenCorrect() {

    configureMapper("dozer_custom_convertor.xml");
    String dateTime = "2007-06-26T21:22:39Z";
    long timestamp = new Long("1182882159000");
    Person3 person = new Person3("Rich", dateTime);
    Personne3 person0 = mapper.map(person, Personne3.class);

    assertEquals(timestamp, person0.getDtob());
}

また、双方向であることを確認するためにテストすることもできます。

@Test
public void givenSrcAndDestWithDifferentFieldTypes_
  whenAbleToCustomConvertBidirectionally_thenCorrect() {
    configureMapper("dozer_custom_convertor.xml");
    String dateTime = "2007-06-26T21:22:39Z";
    long timestamp = new Long("1182882159000");
    Personne3 person = new Personne3("Rich", timestamp);
    Person3 person0 = mapper.map(person, Person3.class);

    assertEquals(dateTime, person0.getDtob());
}

10. 結論

このチュートリアルでは、introduced most of the basics of the Dozer Mapping libraryと、それをアプリケーションで使用する方法について説明します。

これらすべての例とコードスニペットの完全な実装は、Dozergithub projectにあります。