【MapStruct】Javaでオブジェクトの構成が複雑な場合のコピー(マッピング)方法

に公開

はじめに

この記事では、Javaのアノテーションプロセッサ型ライブラリである「MapStruct」を使う上での設定方法や実装例を載せています。

MapStructを使用するメリット

マッピング処理を1から手書きする必要が無く、高速かつ型安全に自動生成が実現可能!

開発環境

・Java:21
・SpringBoot:3.4.5
・MapStruct:1.5.5.Final
・Maven

pom.xmlの設定

<properties>
    <java.version>21</java.version>
    <mapstruct.version>1.5.5.Final</org.mapstruct.version>
</properties>
<!-- MapStruct用の依存関係 -->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>${mapstruct.version}</version>
</dependency>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.10.1</version>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

動作確認用のリクエスト内容

{
    "sampleNo": "サンプル_001",
    "requestA": {
        "requestNo": "A_001",
        "requestC" :{
            "requestNo": "C_001"
        }
    },
    "requestB": [
        {
            "requestNo": "B_001"
        },
        {
            "requestNo": "B_002"
        }
    ]
}

実装

パッケージ構成(サンプル)

src/main/java
    | - com/example/demo
          | - application
          |     | - dto
          |     |     | - RequestADto.java
          |     |     | - RequestBDto.java
          |     |     | - RequestCDto.java
          |     |     | - SampleRequestDto.java
          |     | - service(中身は脱線するため割愛)
          | - mapper
          |     | - SampleMapper.java
          | - presentation
          |     | - dto
          |     |    | - RequestA.java
          |     |    | - RequestB.java
          |     |    | - RequestC.java
          |     |    | - SampleRequest.java
          |     | - SampleController.java
          | - SampleMapStructApplication.java
     

マッピングをする上で超重要なこと

コピー元オブジェクト内の変数名と、コピー先オブジェクト内の変数名を一致させる必要がある。
例)
・コピー元オブジェクトのフィールド
private RequestA requestA;
・コピー先オブジェクトのフィールド
private RequestADto requestA;

コピー元オブジェクト

・SampleRequest.java

package com.example.demo.presentation.dto;

import java.util.List;
import lombok.Data;

@Data
public class SampleRequest {
	
	private String sampleNo;
	
	private RequestA requestA;
	
	private List<RequestB> requestB;

}

・RequestA.java

package com.example.demo.presentation.dto;

import lombok.Data;

@Data
public class RequestA {
	
	private String requestNo;
	
	private RequestC requestC;

}

・RequestB.java

package com.example.demo.presentation.dto;

import lombok.Data;

@Data
public class RequestB {
	
	private String requestNo;

}

・RequestC.java

package com.example.demo.presentation.dto;

import lombok.Data;

@Data
public class RequestC {
	
	private String requestNo;

}

コピー先オブジェクト

・SampleRequestDto.java

package com.example.demo.application.dto;

import java.util.List;
import lombok.Data;

@Data
public class SampleRequestDto {
	
	private String sampleNo;
	
	private RequestADto requestA;
	
	private List<RequestBDto> requestB;

}

・RequestADto.java

package com.example.demo.application.dto;

import lombok.Data;

@Data
public class RequestADto {
	
	private String requestNo;
	
	private RequestCDto requestC;

}

・RequestBDto.java

package com.example.demo.application.dto;

import lombok.Data;

@Data
public class RequestBDto {
	
	private String requestNo;

}

・RequestCDto.java

package com.example.demo.application.dto;

import lombok.Data;

@Data
public class RequestCDto {
	
	private String requestNo;

}

マッピング用ファイル

package com.example.demo.mapper;

import org.mapstruct.Mapper;
import com.example.demo.application.dto.SampleRequestDto;
import com.example.demo.presentation.dto.SampleRequest;

@Mapper(componentModel = "spring")
public interface SampleMapper {
	
	SampleRequestDto toSampleRequestCopyTo(SampleRequest sampleRequest);

}

使用方法の例(呼び出し元)

・SampleController.java

package com.example.demo.presentation;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.application.dto.SampleRequestDto;
import com.example.demo.mapper.SampleMapper;
import com.example.demo.presentation.dto.SampleRequest;

@RestController
public class SampleController {
	
	@Autowired
	SampleMapper sampleMapper;
	
	@PostMapping("/test-map-struct")
	public void testMapStruct(@RequestBody SampleRequest request) {
		
		SampleRequestDto sampleRequestDto = sampleMapper.toSampleRequestCopyTo(request);
		
		// Serviceなどへの後続処理
		
	}
}

アプリ起動に失敗する場合、コンパイルが必要な可能性あり!?

理由:
使用しているIDEや設定によっては、アノテーションプロセッサが動かないか、もしくはMapStructが自動で効かない場合があるため
・Eclipseの場合
アプリ上で右クリック → 実行 → Maven Build → ゴールに「clean compile」を設定 → 実行
<イメージ>

・成功するとtargetフォルダ配下に実装クラスが自動生成される
<イメージ>

自動生成された実装クラス

・SampleMapperImpl.java

package com.example.demo.mapper;

import com.example.demo.application.dto.RequestADto;
import com.example.demo.application.dto.RequestBDto;
import com.example.demo.application.dto.RequestCDto;
import com.example.demo.application.dto.SampleRequestDto;
import com.example.demo.presentation.dto.RequestA;
import com.example.demo.presentation.dto.RequestB;
import com.example.demo.presentation.dto.RequestC;
import com.example.demo.presentation.dto.SampleRequest;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.processing.Generated;
import org.springframework.stereotype.Component;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2025-05-10T21:42:36+0900",
    comments = "version: 1.5.5.Final, compiler: javac, environment: Java 21.0.3 (Eclipse Adoptium)"
)
@Component
public class SampleMapperImpl implements SampleMapper {

    @Override
    public SampleRequestDto toSampleRequestCopyTo(SampleRequest sampleRequest) {
        if ( sampleRequest == null ) {
            return null;
        }

        SampleRequestDto sampleRequestDto = new SampleRequestDto();

        sampleRequestDto.setSampleNo( sampleRequest.getSampleNo() );
        sampleRequestDto.setRequestA( requestAToRequestADto( sampleRequest.getRequestA() ) );
        sampleRequestDto.setRequestB( requestBListToRequestBDtoList( sampleRequest.getRequestB() ) );

        return sampleRequestDto;
    }

    protected RequestCDto requestCToRequestCDto(RequestC requestC) {
        if ( requestC == null ) {
            return null;
        }

        RequestCDto requestCDto = new RequestCDto();

        requestCDto.setRequestNo( requestC.getRequestNo() );

        return requestCDto;
    }

    protected RequestADto requestAToRequestADto(RequestA requestA) {
        if ( requestA == null ) {
            return null;
        }

        RequestADto requestADto = new RequestADto();

        requestADto.setRequestNo( requestA.getRequestNo() );
        requestADto.setRequestC( requestCToRequestCDto( requestA.getRequestC() ) );

        return requestADto;
    }

    protected RequestBDto requestBToRequestBDto(RequestB requestB) {
        if ( requestB == null ) {
            return null;
        }

        RequestBDto requestBDto = new RequestBDto();

        requestBDto.setRequestNo( requestB.getRequestNo() );

        return requestBDto;
    }

    protected List<RequestBDto> requestBListToRequestBDtoList(List<RequestB> list) {
        if ( list == null ) {
            return null;
        }

        List<RequestBDto> list1 = new ArrayList<RequestBDto>( list.size() );
        for ( RequestB requestB : list ) {
            list1.add( requestBToRequestBDto( requestB ) );
        }

        return list1;
    }
}

Discussion