MapStructを用いてのModelとDTOのマッピングについて

## 1.目的
　SpringBootのAPIにおいて、DBから取得した内容をModelに設定した後に、レスポンスに返す前にDTOへ設定する際の方法について残します。

## 2.環境
　　　eclipse（Version: 2022-12 (4.26.0))
　　　java（ver.17) 
　　　spring boot 3.1.1
　　　Gradle - Groovy
　　　mysql（Ver 8.0.33）

## 3.事前準備

### (1)　MapStructを紐付ける土台となるファイルを用意します。
※これを用意しないと実際のMappingがうまくいかない。
配置場所はプロジェクト直下や問わない。

```java:ConfigMapper.java

package com.talent.setting;

import org.mapstruct.MapperConfig;
import org.mapstruct.NullValueMappingStrategy;
import org.mapstruct.ReportingPolicy;
/**
 * MapStruct設定用
 */
@MapperConfig(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE,
        nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL)
public interface ConfigMapper {
}

```

### (2) DBの結果や値の設定が格納されているModelを準備します。
※以下は、
前回記事「MyBatisGeneratorを用いて、Model・Mapperの作成」で作成したものと同様。
（https://qiita.com/yu-F/items/a612dc9b0ee4008c8b31）
```java:TOnAirKanri.java

package com.model;

import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Generated;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

/**
 * オンエア管理テーブル情報（全列）
 */

@Schema(name = "tOnAirKanri", description = "オンエア管理テーブル情報（全列）")
@JsonTypeName("tOnAirKanri")
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-06-12T21:30:49.581521+09:00[Asia/Tokyo]")
public class TOnAirKanri {

  @JsonProperty("id")
  private String id;

  @JsonProperty("onairDay")
  private String onairDay;

  @JsonProperty("programId")
  private String programId;

  @JsonProperty("talentId")
  private String talentId;

  @JsonProperty("targetNentsuki")
  private Integer targetNentsuki;

  @JsonProperty("targetShu")
  private Integer targetShu;

  @JsonProperty("deleteFlg")
  private Integer deleteFlg;

  @JsonProperty("torokuDay")
  private String torokuDay;

  @JsonProperty("koushinDay")
  private String koushinDay;

  public TOnAirKanri id(String id) {
    this.id = id;
    return this;
  }

  /**
   * ID
   * @return id
  */
  @Size(max = 8) 
  @Schema(name = "id", description = "ID", required = false)
  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }

  public TOnAirKanri onairDay(String onairDay) {
    this.onairDay = onairDay;
    return this;
  }

  /**
   * オンエア日
   * @return onairDay
  */
  @Valid 
  @Schema(name = "onairDay", description = "オンエア日", required = false)
  public String getonairDay() {
    return onairDay;
  }

  public void setonairDay(String onairDay) {
    this.onairDay = onairDay;
  }

  public TOnAirKanri programId(String programId) {
    this.programId = programId;
    return this;
  }

  /**
   * 番組ID
   * @return programId
  */
  @NotNull @Size(max = 8) 
  @Schema(name = "programId", description = "番組ID", required = true)
  public String getProgramId() {
    return programId;
  }

  public void setProgramId(String programId) {
    this.programId = programId;
  }

  public TOnAirKanri talentId(String talentId) {
    this.talentId = talentId;
    return this;
  }

  /**
   * タレントID
   * @return talentId
  */
  @Size(max = 8) 
  @Schema(name = "talentId", description = "タレントID", required = false)
  public String getTalentId() {
    return talentId;
  }

  public void setTalentId(String talentId) {
    this.talentId = talentId;
  }

  public TOnAirKanri targetNentsuki(Integer targetNentsuki) {
    this.targetNentsuki = targetNentsuki;
    return this;
  }

  /**
   * 対象年月
   * minimum: 1990
   * maximum: 2100
   * @return targetNentsuki
  */
  @Min(1990) @Max(2100) 
  @Schema(name = "targetNentsuki", description = "対象年月", required = false)
  public Integer getTargetNentsuki() {
    return targetNentsuki;
  }

  public void setTargetNentsuki(Integer targetNentsuki) {
    this.targetNentsuki = targetNentsuki;
  }

  public TOnAirKanri targetShu(Integer targetShu) {
    this.targetShu = targetShu;
    return this;
  }

  /**
   * 対象週
   * minimum: 1
   * maximum: 5
   * @return targetShu
  */
  @Min(1) @Max(5) 
  @Schema(name = "targetShu", description = "対象週", required = false)
  public Integer getTargetShu() {
    return targetShu;
  }

  public void setTargetShu(Integer targetShu) {
    this.targetShu = targetShu;
  }

  public TOnAirKanri deleteFlg(Integer deleteFlg) {
    this.deleteFlg = deleteFlg;
    return this;
  }

  /**
   * 論理削除フラグ
   * minimum: 0
   * maximum: 9
   * @return deleteFlg
  */
  @Min(0) @Max(9) 
  @Schema(name = "deleteFlg", description = "論理削除フラグ", required = false)
  public Integer getDeleteFlg() {
    return deleteFlg;
  }

  public void setDeleteFlg(Integer deleteFlg) {
    this.deleteFlg = deleteFlg;
  }

  public TOnAirKanri torokuDay(String torokuDay) {
    this.torokuDay = torokuDay;
    return this;
  }

  /**
   * 登録日
   * @return torokuDay
  */
  
  @Schema(name = "torokuDay", description = "登録日", required = false)
  public String getTorokuDay() {
    return torokuDay;
  }

  public void setTorokuDay(String torokuDay) {
    this.torokuDay = torokuDay;
  }

  public TOnAirKanri koushinDay(String koushinDay) {
    this.koushinDay = koushinDay;
    return this;
  }

  /**
   * 更新日
   * @return koushinDay
  */
  
  @Schema(name = "koushinDay", description = "更新日", required = false)
  public String getKoushinDay() {
    return koushinDay;
  }

  public void setKoushinDay(String koushinDay) {
    this.koushinDay = koushinDay;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    TOnAirKanri tOnAirKanri = (TOnAirKanri) o;
    return Objects.equals(this.id, tOnAirKanri.id) &&
        Objects.equals(this.onairDay, tOnAirKanri.onairDay) &&
        Objects.equals(this.programId, tOnAirKanri.programId) &&
        Objects.equals(this.talentId, tOnAirKanri.talentId) &&
        Objects.equals(this.targetNentsuki, tOnAirKanri.targetNentsuki) &&
        Objects.equals(this.targetShu, tOnAirKanri.targetShu) &&
        Objects.equals(this.deleteFlg, tOnAirKanri.deleteFlg) &&
        Objects.equals(this.torokuDay, tOnAirKanri.torokuDay) &&
        Objects.equals(this.koushinDay, tOnAirKanri.koushinDay);
  }

  @Override
  public int hashCode() {
    return Objects.hash(id, onairDay, programId, talentId, targetNentsuki, targetShu, deleteFlg, torokuDay, koushinDay);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class TOnAirKanri {\n");
    sb.append("    id: ").append(toIndentedString(id)).append("\n");
    sb.append("    onairDay: ").append(toIndentedString(onairDay)).append("\n");
    sb.append("    programId: ").append(toIndentedString(programId)).append("\n");
    sb.append("    talentId: ").append(toIndentedString(talentId)).append("\n");
    sb.append("    targetNentsuki: ").append(toIndentedString(targetNentsuki)).append("\n");
    sb.append("    targetShu: ").append(toIndentedString(targetShu)).append("\n");
    sb.append("    deleteFlg: ").append(toIndentedString(deleteFlg)).append("\n");
    sb.append("    torokuDay: ").append(toIndentedString(torokuDay)).append("\n");
    sb.append("    koushinDay: ").append(toIndentedString(koushinDay)).append("\n");
    sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }
}

```

### (3) (2)の戻りの値を格納したいDTOを用意します。
※(2)の内容とは異なるフィールドで用意。

```java:OnAirKanriInfoDto.java

package com.talent.service.dto;

import java.io.Serializable;

import com.talent.service.dto.entity.Talent;
import com.talent.service.dto.entity.TvProgram;

import lombok.Data;

/**
 * オンエア管理情報DTO
 */
@Data
public class OnAirKanriInfoDto implements Serializable {
    private String id;
    private Talent talent;
    private TvProgram tvProgram;
    private Integer nentsuki;
    private Integer shu;
}
```

### (4) (2)と(3)の内容を紐付け用のメソッド定義を用意。
※今回は、Helperクラスに定義を記載する。

```java:ShukanTalentJohoBffHelper.java

package com.talent.service.helper;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

import com.model.TOnAirKanri;
import com.talent.service.dto.OnAirKanriInfoDto;
import com.talent.setting.ConfigMapper;

@Mapper(config = ConfigMapper.class)
public interface ShukanTalentJohoBffHelper {

    @Mapping(source = "model.talentId", target = "talent.id")
    @Mapping(source = "talentNm", target = "talent.name")
    @Mapping(source = "model.programId", target = "tvProgram.id")
    @Mapping(source = "programNm", target = "tvProgram.name")
    @Mapping(source = "model.onairDay", target = "tvProgram.onairDay")
    OnAirKanriInfoDto toOnairKanriInfoDto(TOnAirKanri model);

}

```

メソッド定義は、
→「 【対象のDTO】メソッド名（【元となるModel】別名);」

元となるModelのプロパティ名と対象のDTOのプロパティ名が異なる場合は、
「@Mapping(source = "【元となるModel.プロパティ名】", target = "【対象のDTO.プロパティ名】")

上記の定義をメソッドの上に用意する。

※DTOの内部のプロパティとModelの内部のプロパティ名が全く同様、ないしは値ごとに設定が無くても問題ない場合は、「@Mapping」は不要となる。（NULLで設定されたりする）


## 4.まとめ
MapStructはMybatisにおいては使用する機会が多く、一括で値が設定できるため、便利のため、使えるようになると簡潔にかけるようになります。