mcpField
M
mapstruct

mapstruct

github.com/claude-dev-suite開発4667 回インストール2 日前に更新
リポジトリ

mapstruct について

Java 開発者向けの MapStruct 参照スキルで、DTO とエンティティ間のマッピング、更新方法、よくある問題を扱います。Spring や Lombok との連携、マッピングの実践方法も紹介します。

主な機能

  1. 01オブジェクトとコレクションのマッピング
  2. 02フィールドとネストしたオブジェクトのマッピング
  3. 03部分更新と全件更新
  4. 04列挙型と条件付きマッピング
  5. 05マッピング前後のカスタム処理
  6. 06設定とトラブルシューティング

活用シーン

  1. 01エンティティをレスポンス DTO に変換
  2. 02DTO から既存エンティティを更新
  3. 03異なるフィールド名やネスト型をマッピング

MCP 対応

`mcp__documentation__fetch_docs` で MapStruct の詳細ドキュメントを取得できると記載されています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill mapstruct

インストール後、Claude Code で/mapstructを入力して読み込まれたか確認します。

詳細

MapStruct オブジェクトマッピング

詳細な知識:包括的なドキュメントについては、technology を mapstruct に設定して mcp__documentation__fetch_docs を使用してください。

基本的なマッパー

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {

    UserResponse toResponse(User user);

    List<UserResponse> toResponseList(List<User> users);

    @Mapping(target = "id", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    @Mapping(target = "updatedAt", ignore = true)
    @Mapping(target = "password", ignore = true)
    User toEntity(CreateUserRequest dto);
}

マッピングの更新

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {

    // Partial update - ignores null values
    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    void updateEntity(UpdateUserRequest dto, @MappingTarget User user);

    // Full update - sets all fields including nulls
    void updateEntityFull(UpdateUserRequest dto, @MappingTarget User user);
}

フィールドマッピング

@Mapper(componentModel = "spring")
public interface OrderMapper {

    // Different field names
    @Mapping(source = "customer.name", target = "customerName")
    @Mapping(source = "customer.email", target = "customerEmail")
    @Mapping(source = "items", target = "orderItems")
    OrderResponse toResponse(Order order);

    // Constant values
    @Mapping(target = "status", constant = "PENDING")
    @Mapping(target = "createdAt", expression = "java(java.time.LocalDateTime.now())")
    Order toEntity(CreateOrderRequest dto);

    // Expression
    @Mapping(target = "fullName", expression = "java(user.getFirstName() + \" \" + user.getLastName())")
    UserResponse toResponse(User user);

    // Date formatting
    @Mapping(source = "createdAt", target = "createdDate", dateFormat = "yyyy-MM-dd")
    OrderResponse toResponse(Order order);
}

ネストされたオブジェクト

@Mapper(componentModel = "spring", uses = {AddressMapper.class, ItemMapper.class})
public interface OrderMapper {

    // Uses AddressMapper for address field
    // Uses ItemMapper for items collection
    OrderResponse toResponse(Order order);
}

@Mapper(componentModel = "spring")
public interface AddressMapper {
    AddressResponse toResponse(Address address);
}

@Mapper(componentModel = "spring")
public interface ItemMapper {
    ItemResponse toResponse(Item item);
}

Enum マッピング

@Mapper(componentModel = "spring")
public interface StatusMapper {

    @ValueMappings({
        @ValueMapping(source = "ACTIVE", target = "ENABLED"),
        @ValueMapping(source = "INACTIVE", target = "DISABLED"),
        @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "UNKNOWN")
    })
    ExternalStatus toExternalStatus(InternalStatus status);
}

カスタムメソッド

@Mapper(componentModel = "spring")
public abstract class UserMapper {

    @Autowired
    protected RoleRepository roleRepository;

    public abstract UserResponse toResponse(User user);

    @Mapping(target = "roles", source = "roleIds")
    public abstract User toEntity(CreateUserRequest dto);

    // Custom mapping method
    protected Set<Role> mapRoles(Set<Long> roleIds) {
        if (roleIds == null) return new HashSet<>();
        return roleIds.stream()
            .map(id -> roleRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Role", "id", id)))
            .collect(Collectors.toSet());
    }
}

コレクションマッピング

@Mapper(componentModel = "spring")
public interface ProductMapper {

    ProductResponse toResponse(Product product);

    List<ProductResponse> toResponseList(List<Product> products);

    Set<ProductResponse> toResponseSet(Set<Product> products);

    // Page mapping
    default Page<ProductResponse> toResponsePage(Page<Product> products) {
        return products.map(this::toResponse);
    }
}

マッピング前後の処理

@Mapper(componentModel = "spring")
public abstract class UserMapper {

    @AfterMapping
    protected void afterMapping(@MappingTarget UserResponse response, User user) {
        response.setDisplayName(user.getFirstName() + " " + user.getLastName().charAt(0) + ".");
    }

    @BeforeMapping
    protected void beforeMapping(CreateUserRequest dto) {
        if (dto.getEmail() != null) {
            dto.setEmail(dto.getEmail().toLowerCase().trim());
        }
    }
}

条件付きマッピング

@Mapper(componentModel = "spring")
public abstract class UserMapper {

    @Condition
    public boolean isNotEmpty(String value) {
        return value != null && !value.trim().isEmpty();
    }

    // Only maps non-empty strings
    UserResponse toResponse(User user);
}

Maven の設定

<properties>
    <mapstruct.version>1.6.2</mapstruct.version>
    <lombok.version>1.18.30</lombok.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${mapstruct.version}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.13.0</version>
            <configuration>
                <source>17</source>
                <target>17</target>
                <annotationProcessorPaths>
                    
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${lombok.version}</version>
                    </path>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${mapstruct.version}</version>
                    </path>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok-mapstruct-binding</artifactId>
                        <version>0.2.0</version>
                    </path>
                </annotationProcessorPaths>
                <compilerArgs>
                    <arg>-Amapstruct.defaultComponentModel=spring</arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

主なアノテーション

アノテーション目的
@Mapperマッパーのインターフェース/抽象クラスを定義します
@Mappingフィールド単位のマッピング設定を行います
@MappingTarget既存のオブジェクトを更新します
@BeanMappingBean 単位のマッピング設定を行います
@AfterMappingマッピング後の処理を行うメソッドです
@BeforeMappingマッピング前の処理を行うメソッドです
@Condition条件付きマッピングを行います
@ValueMappingEnum 値をマッピングします

ベストプラクティス

  1. Spring のインジェクションには componentModel = "spring" を使用します
  2. フィールド数の少ない DTO には unmappedTargetPolicy = ReportingPolicy.IGNORE を使用します
  3. DTO からマッピングする場合は、必ず id、createdAt、updatedAt を除外します
  4. 部分更新には @MappingTarget を使用します
  5. アノテーションプロセッサーは次の順序にします:Lombok → MapStruct → Binding
  6. カスタムロジックにはインターフェースではなく抽象クラスを使用します
  7. 条件付きマッピングのロジックには @Condition を使用します

このスキルを使用しない場合

シナリオ代わりに使用するもの
Java 言語機能java スキル
Lombok アノテーションlombok スキル
Spring の設定backend-spring-boot スキル
JPA エンティティの操作JPA 専用スキル
単純なコピー手動マッピングまたは BeanUtils

アンチパターン

アンチパターン問題点適切な方法
エンティティからエンティティへのマッピング変更追跡が機能しなくなりますDTO からエンティティへマッピングします
@MappingTarget を使用しない更新が非効率になります部分更新に使用します
式に複雑なロジックを記述するテストが困難になりますカスタムメソッドを使用します
マッピングされない項目をすべて無視するフィールドが気付かれずに見落とされますWARN または ERROR ポリシーを使用します
監査フィールドを除外しないメタデータが上書きされますid とタイムスタンプを除外します
循環参照StackOverflowError が発生しますカスタムマッピングで循環を断ち切ります
カスタムロジックにインターフェースを使用する依存関係を注入できません抽象クラスを使用します
マッパーをテストしない実行時にマッピングエラーが発生しますマッパーのテストを作成します

よくある問題のトラブルシューティング

問題原因解決策
"Cannot find implementation"アノテーション処理に失敗しましたプロセッサーの設定を確認します
Lombok のフィールドが見つからないプロセッサーの順序が誤っていますMapStruct より前に Lombok を実行します
マッパーが autowire されないcomponentModel が誤っていますcomponentModel = "spring" を使用します
循環依存マッパー同士が相互参照しています@Lazy を使用するか、設計を見直します
UnmappedTargetProperty 警告マッピングが不足しています@Mapping を追加するか、ignore ポリシーを設定します
マッピング中に NullPointerException が発生するソースが null ですnull チェックまたは NullValuePropertyMappingStrategy を追加します
カスタムメソッドが呼び出されないシグネチャが誤っていますメソッドのパラメーターを正確に一致させます
生成されたコードが更新されないIDE のキャッシュプロジェクトをクリーンして再ビルドします

リファレンスドキュメント

詳細な知識:包括的なドキュメントについては、technology を mapstruct に設定して mcp__documentation__fetch_docs を使用してください。

コメント