MapStruct使用


  • なぜMapStructが必要なのですか?

  • まず、MapStructのようなフレームワークがどのようなシーンに適しているのか、なぜ市販されているのかについてお話しします.
    ソフトウェアアーキテクチャ設計では、階層構造が最も一般的であり、最も重要な構造でもあります.多くの人は3層アーキテクチャ、4層アーキテクチャなどに慣れていない.
    「コンピューター科学分野のいかなる問題も間接的な中間層を増やすことで解決でき、だめなら2層を加える」という人もいる.
    しかし、ソフトウェアアーキテクチャの階層化が進むにつれて、各階層間のデータモデルは相互変換の問題に直面し、典型的には、DO、DTO、VOなどのコードに様々なOを見ることができる.
    一般的に、同じデータモデルでは、異なる階層で異なるデータモデルを使用します.データストレージ層では、DOを使用してビジネスエンティティを抽象化します.ビジネスロジック層では、DTOを使用してデータ転送オブジェクトを表します.展示層に着くと,オブジェクトをVOにカプセル化してフロントエンドと対話する.
    では、データの先端からデータ永続化層(先端まで透過)にデータの先端から透過するには、オブジェクト間の相互変換、すなわち異なるオブジェクトモデル間でマッピングを行う必要がある.
    通常、get/setなどの方法でフィールドマッピング操作を1つずつ行うことができます.
    personDTO.setName(personDO.getName());
    
    personDTO.setAge(personDO.getAge());
    
    personDTO.setSex(personDO.getSex());
    
    personDTO.setBirthday(personDO.getBirthday());

    しかし,このようなマッピングコードの作成は冗長でエラーが発生しやすいタスクである.MapStructなどの類似のフレームワークの目標は,自動化によりこの作業をできるだけ多く簡略化することである.
  • MapStructの使用
  • MapStruct(https://mapstruct.org/)は、「コンフィギュレーションよりも約束が優れている」メソッドに基づくJava beanタイプ間のマッピングの実装を大幅に簡略化するコードジェネレータです.生成されたマッピングコードは、純粋なメソッド呼び出しを使用するため、迅速で、タイプが安全で、理解しやすい.
    コンフィギュレーションよりも優れたコンフィギュレーションは、コンフィギュレーションよりも優れており、コンフィギュレーションと呼ばれ、ソフトウェア開発者が決定する数を減らし、簡単なメリットを獲得し、活性を失わないようにするソフトウェア設計のモデルです.
    PersonDOとPersonDTOの2つのクラスが相互変換される必要があると仮定します.クラス定義は次のとおりです.
    public class PersonDO {
    
        private Integer id;
    
        private String name;
    
        private int age;
    
        private Date birthday;
    
        private String gender;
    
    }
    
    public class PersonDTO {
    
        private String userName;
    
        private Integer age;
    
        private Date birthday;
    
        private Gender gender;
    
    }

    MapStructを用いたbeanマッピングの方法を実証した.
    MapStructを使用するには、まず関連するjarパッケージに依存する必要があります.maven依存方式を使用すると、次のようになります.
    ...
    
    
    
        1.3.1.Final
    
    
    
    ...
    
    
    
        
    
            org.mapstruct
    
            mapstruct
    
            ${org.mapstruct.version}
    
        
    
    
    
    ...
    
    
    
        
    
            
    
                org.apache.maven.plugins
    
                maven-compiler-plugin
    
                3.8.1
    
                
    
                    1.8 
    
                    1.8 
    
                    
    
                        
    
                            org.mapstruct
    
                            mapstruct-processor
    
                            ${org.mapstruct.version}
    
                        
    
                        
    
                    
    
                
    
            
    
        
    
    

    MapStructはコンパイラで変換コードを生成する必要があるため、maven-compiler-pluginプラグインでmapstruct-processorへの参照を構成する必要があります.この部分は後述する.
    その後、マッピングを行うインタフェースを定義する必要があります.主なコードは次のとおりです.
     
    @Mapper
    
    interface PersonConverter {
    
        PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
    
        @Mappings(@Mapping(source = "name", target = "userName"))
    
        PersonDTO do2dto(PersonDO person);
    
    }

    注記@Mapperを使用してConverterインタフェースを定義し、do 2 dtoメソッドを定義します.メソッドのパラメータタイプはPersonDOで、パラメータタイプはPersonDTOです.このメソッドはPersonDOをPersonDTOに変換するために使用されます.
    テストコードは次のとおりです.
    public static void main(String[] args) {
    
        PersonDO personDO = new PersonDO();
    
        personDO.setName("Hollis");
    
        personDO.setAge(26);
    
        personDO.setBirthday(new Date());
    
        personDO.setId(1);
    
        personDO.setGender(Gender.MALE.name());
    
        PersonDTO personDTO = PersonConverter.INSTANCE.do2dto(personDO);
    
        System.out.println(personDTO);
    
    }

    出力結果:
    PersonDTO{userName='Hollis', age=26, birthday=Sat Aug 08 19:00:44 CST 2020, gender=MALE}

    私たちはMapStructを使ってPersonDOをPersonDTOに完璧に変えたことがわかります.
    上のコードから分かるように,MapStructの使い方は比較的簡単で,@Mapper注釈に主に依存している.
    しかし、多くの場合、互いに変換する必要がある2つのクラス間の属性名、タイプなどが完全に一致していない場合や、直接マッピングしたくない場合もあることを知っています.では、どのように処理すればいいのでしょうか.
    実はMapStructもこの方面でよくやっています.
  • MapStruct処理フィールドマッピング
  • まず、変換する2つのクラスのソースオブジェクト属性がターゲットオブジェクト属性のタイプと名前と一致すると、対応する属性が自動的にマッピングされることを明確に示すことができます.
    では、もし特別な状況に遭遇したらどう処理しますか?
    名前が一致しないマッピング方法
    上記の例では、PersonDOではユーザ名をnameで表し、PersonDTOではuserNameでユーザ名を表していますが、パラメータマッピングはどのように行われますか.
    @Mapping注記を使用するには、メソッド署名に注記を使用し、変換するソースオブジェクトの名前とターゲットオブジェクトの名前を指定するだけです.名前の値をuserNameにマッピングするには、次のようにします.
    @Mapping(source = "name", target = "userName")

    自動マッピング可能なタイプ
    名前の不一致に加えて、上記の例のようにPersonDOではStringタイプでユーザの性別を表し、PersonDTOではGenterの列挙でユーザの性別を表す特殊な場合がある.
    このときタイプが一致しない場合は,相互変換の問題にかかわる必要がある.
    実際には、MapStructは一部のタイプを自動的にマッピングし、例のようにStringタイプを自動的に列挙タイプに変換する必要はありません.
    一般的には、次の場合に自動タイプ変換を行います.
  • 基本タイプおよびそれらに対応する包装タイプ.
  • 基本タイプのパッケージタイプとStringタイプの間の
  • Stringタイプと列挙タイプの間の
  • カスタム定数
    マッピングの変換中に一定の値を定義する場合はconstantを使用します.
    @Mapping(source = "name", constant = "hollis")

    タイプが一致しないマッピング方法
    また、上記の例では、Personというオブジェクトに住所という属性を追加する必要がある場合、PersonoDTOではHomeAddressクラスを個別に定義して住所を表すのが一般的ですが、PersonクラスではStringタイプを使用して住所を表すのが一般的です.
    これはHomeAddressとStringの間でJSONを用いて相互変換する必要があり,この場合もMapStructはサポートできる.
    public class PersonDO {
    
        private String name;
    
        private String address;
    
    }
    
    
    
    public class PersonDTO {
    
        private String userName;
    
        private HomeAddress address;
    
    }
    
    @Mapper
    
    interface PersonConverter {
    
        PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
    
    
    
        @Mapping(source = "userName", target = "name")
    
        @Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")
    
        PersonDO dto2do(PersonDTO dto2do);
    
    
    
        default String homeAddressToString(HomeAddress address){
    
            return JSON.toJSONString(address);
    
        }
    
    }

    PersonConverterでメソッドを定義するだけです(PersonConverterはインタフェースなのでJDK 1.8以降のバージョンでdefaultメソッドを定義できます).このメソッドの役割はHomeAddressをStringタイプに変換することです.
    defaultメソッド:Java 8が導入した新しい言語特性は、キーワードdefaultで表記され、defaultで表記されるメソッドは、実装を提供する必要がありますが、サブクラスは実装を選択するか、実装しないかを選択できます.
    次に、dto 2 doメソッドでは、次の注釈によってタイプの変換を実現することができる.
    @Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")

    以上、よく使われるフィールドマッピングの方法を簡単に紹介しましたが、私自身が仕事でよく遭遇するいくつかのシーンでもあります.もっと多くの場合、公式の例を見ることができます(https://github.com/mapstruct/mapstruct-examples).
  • MapStructの性能
  •  
    前述したように多くのMapStructの使い方は、MapStructの使い方が簡単で、フィールドマッピングの機能が強いことがわかりますが、彼の性能はどうでしょうか.
    「なぜアリババはApache Beanutilsを使用して属性のcopyを禁止しているのか」の例を参考に、MapStructの性能テストを行います.
    1000、10000、100000、100000のマッピングをそれぞれ実行する時間は、0 ms、1 ms、3 ms、6 msである.
    MapStructの消費時間は他のいくつかのツールに比べて非常に短いことがわかります.
    では、なぜMapStructの性能がこんなに良いのでしょうか.
    実際、MapStructと他のいくつかのフレームワークの最大の違いは、他のマッピングフレームワークと比較して、MapStructがコンパイル時にbeanマッピングを生成することであり、高性能を確保し、問題を事前にフィードバックすることができ、開発者が徹底的にエラーチェックを行うことができることです.
    前にMapStructの依存を導入したとき、特にmaven-compiler-pluginにmapstruct-processorのサポートを追加したことを覚えていますか?
    さらに,我々はコードに多くのMapStructが提供する注釈を用い,コンパイル期間中にMapStructがbeanマッピングのコードを直接生成することができ,我々の代わりに多くのsetterとgetterを書いたことに相当する.
    コードに次のMapperを定義します.
    @Mapper
    
    interface PersonConverter {
    
        PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
    
        @Mapping(source = "userName", target = "name")
    
        @Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")
    
        @Mapping(target = "birthday",dateFormat = "yyyy-MM-dd HH:mm:ss")
    
        PersonDO dto2do(PersonDTO dto2do);
    
        default String homeAddressToString(HomeAddress address){
    
            return JSON.toJSONString(address);
    
        }
    
    }

    コードコンパイル後、PersonConverterImplが自動的に生成されます.
    @Generated(
        value = "org.mapstruct.ap.MappingProcessor",
        date = "2020-08-09T12:58:41+0800",
        comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)")
    
    class PersonConverterImpl implements PersonConverter {
    
        @Override
        public PersonDO dto2do(PersonDTO dto2do) {
            if ( dto2do == null ) {
               return null;
            }
            PersonDO personDO = new PersonDO();
            personDO.setName( dto2do.getUserName() );
    
            if ( dto2do.getAge() != null ) {
                personDO.setAge( dto2do.getAge() );
            }
    
            if ( dto2do.getGender() != null ) {
                personDO.setGender( dto2do.getGender().name() );
            }
    
            personDO.setAddress( homeAddressToString(dto2do.getAddress()) );
            return personDO;
    
        }
    
    }

    実行期間中にbeanをマッピングすると、PersonConverterImplのdto 2 doメソッドが直接呼び出され、特別なことは何もせず、メモリの中でsetやgetを行うだけでいいです.
    したがって,コンパイル期間中に多くのことをしたため,MapStructは実行期間中の性能が良好であり,問題の暴露をコンパイル期間に繰り上げることができるという利点もある.
    コード内のフィールドマッピングに問題がある場合、アプリケーションはコンパイルできず、開発者にこの問題を解決するように強制します.