7.6. Beanマッピング(MapStruct)¶
7.6.1. Overview¶
AccountForm
オブジェクトを、ドメイン層のAccount
オブジェクトに変換する場合を考える。AccountForm
オブジェクトを、Account
オブジェクトにBeanマッピングし、ドメイン層では、Account
オブジェクトを使用する。Tip
MapStructはJava コンパイラにプラグインするAnnotation Processorで、コードジェネレーターとして動作する。
ユーザが作成するインタフェースをインプットとして、コンパイル時にそのインタフェースに従ってBeanマッピングを行うコードを生成する。
MapStructを使用した場合と使用しない場合のコード例を挙げる。
煩雑になり、プログラムの見通しが悪くなる例
Source source = userService.findById(userId); Target target = new Target(); target.setId(source.getId()); target.setPerson(new Person(source.getPersonForm().getCode(), source.getPersonForm().getName())); target.setOrders(new HashSet<>(source.getOrders()));
MapStructを使用した場合の例
@Mapper public interface BeanMapper { Target map(Source source); }
Source source = userService.findById(userId); Target target = beanMapper.map(source);
7.6.2. How to use¶
7.6.2.1. マッパーの作成方法¶
7.6.2.1.1. MapStructを使用するための設定¶
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version> <!-- (1) -->
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amapstruct.defaultComponentModel=spring</arg> <!-- (2) -->
</compilerArgs>
</configuration>
</plugin>
項番 | 説明 |
---|---|
(1)
|
mapstruct.version は依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでプロパティとして変数を定義する必要は無い。 |
(2)
|
生成されるマッパーインタフェースの実装クラスに
org.springframework.stereotype.Component アノテーションを付与する設定。この設定を行わない場合は、MapStructを使用するためのBean定義で記載されているようにマッパーインタフェースに直接設定を記載することができる。
本ガイドラインでは上記のようにmaven-compiler-pluginに設定を追加している前提とする。
|
Note
開発プロジェクトをブランクプロジェクトから作成すると、上記の設定はpom.xmlに記載されている。
開発プロジェクトの作成方法については、「Webアプリケーション向け開発プロジェクトの作成」を参照されたい。
7.6.2.1.2. マッパーインタフェースの作成¶
@Mapper
アノテーションが付与されている必要がある。また、マッピング元をソース、マッピング先をターゲットと呼ぶ。@Mapper
アノテーションを付与することにより、コンパイル時にマッパーインタフェースの実装クラスが自動で生成される。この際、サポートされていない型のマッピングを行おうとするとコンパイルエラーが発生する。@Mapper // (1)
public interface BeanMapper {
Target map(Source source); // (2)
}
項番 | 説明 |
---|---|
(1)
|
対象のインタフェースに
@Mapper アノテーションを付与する。 |
(2)
|
マッピングメソッドを定義する。ソースとなるクラスを引数に、ターゲットとなるクラスを返り値にする。
|
Note
マッパーインタフェースはソースとターゲット両方のBeanを参照できる必要がある。
本ガイドラインではマッパーを使用するクラスと同じパッケージにマッパーインタフェースを配置する前提とする。
Note
マッピングメソッドを定義するファイルはインタフェースではなく抽象クラスにすることもできる。
抽象クラスにすることにより、インスタンスフィールドやprivateメソッドを活用することが可能となり、より柔軟なマッピング処理を記載することができる。
@Mapper public abstract class BeanMapper { public abstract Target map(Source source); }
7.6.2.1.3. MapStructを使用するためのBean定義¶
maven-compiler-pluginにMapStructを使用するための設定の(2)の設定をしていない場合は、@Mapper
アノテーションに下記の設定を行う必要がある。
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) // (1)
public interface BeanMapper {
}
項番 | 説明 |
---|---|
(1)
|
componentModelに
MappingConstants.ComponentModel.SPRING を指定することにより、生成されるクラスにorg.springframework.stereotype.Component アノテーションが付与され、component-scanの対象となる。 |
Note
生成されるクラスはtarget/generated-sources/annotations
配下のマッパーインタフェースと同じパッケージに配置される。そのため、マッパーインタフェースはcomponent-scanの対象となるパスに配置をする必要がある。
artifactId-web └── src │ └── main │ └── java │ └── com │ └── example │ └── project │ └── app │ └── welcome │ └── BeanMapper.java └── target └── generated-sources └── annotations └── com └── example └── project └── app └── welcome └── BeanMapperImpl.java
生成された実装クラスを呼び出すためには、フィールドにマッパーインタフェースをインジェクトすればよい。
@Inject
BeanMapper beanMapper;
7.6.2.2. マッピング方法¶
7.6.2.2.1. Bean間のフィールド名、型が同じ場合のマッピング¶
対象のBean間のフィールド名及び型が同じ場合、マッパーインタフェースにマッピングメソッドを定義するだけでよい。
ソース
public class Source { private int id; private String name; // omitted setter/getter }
ターゲット
public class Target { private int id; private String name; // omitted setter/getter }
マッパーインタフェース
@Mapper public interface BeanMapper { Target map(Source source); }
Source source = new Source(); source.setId(1); source.setName("SourceName"); Target target = beanMapper.map(source); System.out.println(target.getId()); System.out.println(target.getName());
1 SourceName
作成されたオブジェクトにソースのオブジェクトの値が設定されていることが分かる。
7.6.2.2.2. Bean間のフィールドの型が異なる場合のマッピング¶
ソースとターゲットでBeanのフィールドの型が異なる場合、MapStructでサポートされている型変換の場合は自動でマッピングできる。
例えば、以下のような変換はMappingメソッドを定義するだけで変換できる。
例 : String -> BigDecimal
ソース
public class Source { private String amount; // omitted setter/getter }
ターゲット
public class Target { private BigDecimal amount; // omitted setter/getter }
マッピングメソッド
@Mapper public interface BeanMapper { Target map(Source source); }
マッピング例
Source source = new Source(); source.setAmount("123.45"); Target target = beanMapper.map(source); System.out.println(target.getAmount());
上記のコードを実行すると以下のように出力される。
123.45
型が異なる場合でも値をコピーできていることが分かる。
7.6.2.2.3. Bean間のフィールド名が異なる場合のマッピング¶
@Mapping
アノテーションを設定することで変換できる。@Mapping
アノテーションにはマッピングするフィールド名を設定する。ソース
public class Source { private int id; private String name; // omitted setter/getter }
ターゲット
public class Target { private int targetId; private String targetName; // omitted setter/getter }
マッパーインタフェース
@Mapper public interface BeanMapper { @Mapping(target = "targetId", source = "id") // (1) @Mapping(target = "targetName", source = "name") // (1) Target map(Source source); }
項番 説明 (1)対象のメソッドに@Mapping
を付与し、target
にターゲットのフィールドを、source
にソースのフィールドを指定する。@Mapping
は対象のフィールドの分だけ設定する。マッピング例
Source source = new Source(); source.setId(1); source.setName("SourceName"); Target target = beanMapper.map(source); System.out.println(target.getTargetId()); System.out.println(target.getTargetName());
上記のコードを実行すると以下のように出力される。
1 SourceName
フィールド名が異なる場合でも値をコピーできていることが分かる。
7.6.2.2.4. Nestしたフィールドのマッピング¶
ソース
public class Source { private int id; private String name; private DeptSource deptSource; // omitted setter/getter }
ソースにネストしたBean
public class DeptSource { private String deptId; private String depetName // omitted setter/getter and other fields }
ターゲット
public class Target { private int id; private String name; private String deptId; private String deptName; // omitted setter/getter }
Source
オブジェクトが持つDeptSource
のdeptId
とdeptName
を、Target
オブジェクトが持つdeptId
とdeptName
にマップしたい場合、以下のように定義する。マッパーインタフェース
@Mapper public interface BeanMapper { @Mapping(target = "deptId", source = "deptSource.deptId") @Mapping(target = "deptName", source = "deptSource.deptName") Target map(Source source); }
Note
Source
オブジェクトが持つDeptSource
のフィールドを全てマッピングする場合は、以下のように"."
を設定することで全てのフィールドをマッピングすることができる。
@Mapper public interface BeanMapper { @Mapping(target = ".", source = "deptSource") // (1) Target map(Source source); }
項番 説明 (1) targetに"."
を指定すると、DeptSource
のフィールドを全てマッピングする。なお、"."
はsourceには設定ができない。
マッピング例
Source source = new Source(); source.setId(1); source.setName("John"); source.setDeptId("D01"); source.setDeptName("Mike") Target target = beanMapper.map(source); System.out.println(target.getId()); System.out.println(target.getName()); System.out.println(target.getDepartment().getDeptId()); System.out.println(target.getDepartment().getDeptName());
上記のコードを実行すると以下のように出力される。
1 John D01 Mike
7.6.2.2.5. 既存のBeanの更新¶
ターゲットのBeanを新たに生成せずに、既存のBeanを更新したい場合はメソッドを以下のように定義する。
@Mapper public interface BeanMapper { void map(Source source, @MappingTarget Target target); // (1) }
項番 説明 (1) 第2引数にターゲットのクラスを設定し、@MappingTarget
アノテーションを付与することにより既存のオブジェクトの更新ができる。
以下のように、定義されたMappingメソッドを使って既存のBeanの更新を行う。
Source source = new Source(); source.setId(1); source.setName("SourceName"); Target target = new Target(); target.setId(2); target.setName("TargetName"); beanMapper.map(source, target); System.out.println(target.getId()); System.out.println(target.getName());
上記のコードを実行すると以下のように出力される。
1 SourceName
ソースのオブジェクトの値がターゲットに反映されていることが分かる。
Note
ターゲットにマッピングされないフィールドが存在する場合、該当するフィールドの値はコピー前後で変わらない。ただし、マッピングされないフィールドが存在する場合はコンパイル時に警告が発生する。
これを抑制するには、下記のようにunmappedTargetPolicyの値をReportingPolicy.IGNORE
に設定するか、後述のフィールド除外設定で紹介するようにフィールドの除外設定を行う必要がある。
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) public interface BeanMapper { void map(Source source, @MappingTarget Target target); }
ソースにマッピングしないフィールドが存在する場合も同様に警告が発生する。この場合はunmappedSourcePolicyの値を上記と同様にReportingPolicy.IGNORE
に設定すればよい。
7.6.2.2.6. Collectionマッピング¶
@Mapping
の設定は不要である。以下にEmailクラスのListをフィールドemailsとして持つクラスを例にとって説明する
public class Email { private String email; public Email() { } public Email(String email) { this.email = email; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
ソース
import java.util.List; public class AccountForm { private List<Email> emails; public void setEmails(List<Email> emails) { this.emails = emails; } public List<Email> getEmails() { return emails; } }
ターゲット
import java.util.List; public class Account { private List<Email> emails; public void setEmails(List<Email> emails) { this.emails = emails; } public void addEmail(Email emali) { if (this.emails == null) { this.emails = new ArrayList<EmailDto>(); } this.emails.add(email); } public List<Email> getEmails() { return emails; } }
マッパーインタフェース
@Mapper public interface BeanMapper { Account map(AccountForm accountForm); }
マッピング例
AccountForm accountForm = new AccountForm(); List<Email> emailList = new ArrayList<Email>(); emailList.add(new Email("a@example.com")); emailList.add(new Email("b@example.com")); emailList.add(new Email("c@example.com")); accountForm.setEmails(emailList); Account account = beanMapper.map(accountForm); System.out.println(account.getEmails());
上記のコードを実行すると以下のように出力される。
[a@example.com, b@example.com, c@example.com]
ターゲットのBeanが持つCollectionフィールドに要素が存在しない場合は特に問題はないが、既に要素が存在する場合は注意が必要である。以下に既に要素が存在する場合の例を記載する。
マッパーインタフェース
@Mapper public interface BeanMapper { void map(AccountForm accountForm, @MappingTarget Account account); }
マッピング例
AccountForm accountForm = new AccountForm(); Account account = new Account(); List<Email> emailList = new ArrayList<Email>(); List<Email> emailsDest = new ArrayList<Email>(); emailList.add(new Email("a@example.com")); emailList.add(new Email("b@example.com")); emailList.add(new Email("c@example.com")); emailsDest.add(new Email("d@example.com")); emailsDest.add(new Email("e@example.com")); emailsDest.add(new Email("f@example.com")); accountForm.setEmails(emailList); account.setEmails(emailsDest); beanMapper.map(accountForm, account); System.out.println(account.getEmails());
デフォルトでは、ソースのCollectionフィールドに上書きされる。
[a@example.com, b@example.com, c@example.com]
設定値 説明 CollectionMappingStrategy.ACCESSOR_ONLY
デフォルト。ターゲットにadderが存在しても使用されることは無いCollectionMappingStrategy.SETTER_PREFERRED
ターゲットにsetterとadderがある場合にはsetterが優先して使用される。CollectionMappingStrategy.ADDER_PREFERRED
ターゲットにsetterとadderがある場合にはadderが優先して使用される。CollectionMappingStrategy.TARGET_IMMUTABLE
既存のBeanを更新する場合はターゲットのコレクションがクリアされない。
以下にCollectionMappingStrategy.ADDER_PREFERRED
を設定した場合の例を示す。
マッパーインタフェース
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) // (1) public interface BeanMapper { void map(AccountForm accountForm, @MappingTarget Account account); }
項番 説明 (1)ターゲットのBeanにsetterとadderがあり、adderを優先して使用したい場合はCollectionMappingStrategy.ADDER_PREFERRED
を設定する
先ほどのマッピング例に適用させると、下記のような結果になる。
[d@example.com, e@example.com, f@example.com, a@example.com, b@example.com, c@example.com]
ターゲットにadderが存在するため、ターゲットのリストにソースのリストの値が追加されている。
7.6.2.2.7. MapからBeanのマッピング¶
ターゲット
public class Target { private int id; private String name; // omitted setter/getter }
マッパーインタフェース
@Mapper public interface BeanMapper { @Mapping(target = "name", source = "mapName") // (1) Target map(Map<String, String> map); }
項番 説明 (1)ソースとなるMapのキーとターゲットとなるBeanのフィールド名が違う場合は、Bean間のフィールド名が異なる場合のマッピングと同様に@Mapping
アノテーション内に設定を記載する。マッピング例
Map<String, String> map = new HashMap<>(); map.put("id", "1"); map.put("mapName", "sourceName"); Target target = beanMapper.map(map); System.out.println(target.getId()); System.out.println(target.getName());
1 SourceName
7.6.2.2.8. 複数のBeanからのマッピング¶
ソース
public class Source1 { private int id; private String name; // omitted setter/getter }
public class Source2 { private int id; private String title; // omitted setter/getter }
ターゲット
public class Target { private int id; private String name; private String title; // omitted setter/getter }
マッパーインタフェース
@Mapper public interface BeanMapper { @Mapping(target = "id", source = "source1.id") // (1) Target map(Source1 source1, Source2 source2); //(2) }
項番 説明 (1)ソースとなるBeanに同じ名前のフィールドがある場合は、ソースのBeanを明示的に指定する必要がある。記載しない場合はコンパイルエラーが発生する。(2)マッピングメソッドの引数を複数にすることで、複数のBeanからマッピングができる。
7.6.2.2.9. マッピングメソッドにインタフェースを用いる場合¶
ソースのインタフェース
public class SourceInterface { public int getId(); }
ソース
public class SourceImpl1 implements SourceInterface { private int id; private String name; @Override public int getId(){ return this.id; } // omitted other setter/getter }
ターゲット
public class Target { private int id; private String name; // omitted setter/getter }
マッパーインタフェース
@Mapper public interface BeanMapper { Target map(SourceInterface sourceInterface); }
SourceInterface
を引数にしたマッピングメソッドに、SourceInterface
を実装したSourceImpl
のオブジェクトを渡す。
SourceImpl1 source1 = new SourceImpl1(); source1.setId(1); source1.setName("SourceName"); Tatget target = beanMapper.map(source1); System.out.println(target.getId()); System.out.println(target.getName());
上記のコードを実行すると以下のように出力される。
1
SourceInterface
にはgetNameが定義されていないため、SourceImple
で定義されていてもnameフィールドのマッピングは行われない。@SubclassMapping
アノテーションを使用することで実現できる。2つ目のソース
public class SourceImpl2 implements SourceInterface { private int id; private String title; @Override public int getId(){ return this.id; } // omitted other setter/getter }
マッパーインタフェース
@Mapper public interface BeanMapper { Target map1(SourceImpl1 source1); // (1) Target map2(SourceImpl2 source2); // (1) //(2) @SubclassMapping(source = SourceImpl1.class, target = Target.class) @SubclassMapping(source = SourceImpl2.class, target = Target.class) Target map(SourceInterface sourceInterface); }
項番 説明 (1)マッピングに対応させたいSourceInterface実装クラスに対応したマッピングメソッドを定義する。(2)@SubclassMapping
アノテーションを用いてsource
とtarget
それぞれにマッピングしたい実装クラスを指定する。これにより、map
メソッドにSourceImpl1
が渡された場合はmap1
メソッドが、SourceImpl2
が渡された場合はmap2
メソッドが呼び出されるようになる。マッピング例
SourceImpl1 source1 = new Source1Impl(); source1.setId(1); source1.setName("SourceName"); Source2Impl source2 = new Source2Impl(); source2.setId(2); source2.setTitle("SourceTitle"); Target target1 = beanMapper.map(source1); //(1) Target target2 = beanMapper.map(source2); //(1) System.out.println(target1.getId()); System.out.println(target1.getName()); System.out.println(target2.getId()); System.out.println(target2.getTitle());
項番 説明 (1)source1とsource2はインタフェースが共通で実装クラスが異なるが、同じマッピングメソッドを使用している。
上記のコードを実行すると以下のように出力される。
1 SourceName 2 SourceTitle
インタフェースを引数に持つマッピングメソッドを呼び出し、実装クラスのみに定義されているgetter/setterを用いてマッピングされていることが分かる。
7.6.2.2.10. フィールド除外設定¶
ソース
public class Source { private int id; private String name; // omitted setter/getter }
ターゲット
public class Target { private int id; private String name; // omitted setter/getter }
任意のフィールドをマッピングから除外したい場合は、@Mapping
に次のような設定を入れる。
@Mapper public interface BeanMapper { @Mapping(target = "name", ignore = true) // (1) void map(Source source, @MappingTarget Target target); }
項番 説明 (1) 除外したいフィールドをtarget
に設定し、ignore
にtrueを設定する。この例の場合、SourceオブジェクトからTargetオブジェクトをコピーする際にtargetのnameの値が上書きされない。
マッピング例
Source source = new Source(); source.setId(1); source.setName("SourceName"); Target target = new Target(); target.setId(2); target.setName("TargetName"); beanMapper.map(source, target); System.out.println(target.getId()); System.out.println(target.getName());
上記のコードを実行すると以下のように出力される。
1 TargetName
マッピング後、targetのnameフィールドは前の状態のままである。
7.6.3. How to extend¶
7.6.3.1. 独自のマッピングの定義方法¶
- defaultメソッドを用いた方法
@Named
アノテーションを用いた方法
7.6.3.1.1. defaultメソッドを用いた方法¶
ソース
public class Source { private String name; private Strint title // omitted setter/getter }
ターゲット
public class Target { private String name; private String title // omitted setter/getter }
マッパーインタフェース
@Mapper public interface BeanMapper { Target map(Source source); default String stringToUpper(String string) { // (1) if (string == null) { return null; } return string.toUpperCase(); } }
項番 説明 (1)defaultメソッドを定義し、独自のマッピングロジックを記載する。引数、返り値の型が一致するフィールドのマッピング全てに適用される。マッピング例
Source source = new Source(); source.setName("SourceName"); source.setTitle("SourceTitle"); Target target = new Target(); target.setName("TargetName"); target.setTitle("TargetTitle"); beanMapper.map(source, target); System.out.println(target.getName()); System.out.println(target.getTitle());
上記のコードを実行すると以下のように出力される。
SOURCENAME SOURCETITLE
nameフィールド、titleフィールドともに大文字にマッピングされる。
7.6.3.1.2. @Namedアノテーションを用いた方法¶
特定のフィールドのみ、独自に定義したマッピングロジックを適用させたい場合は@Named
アノテーションを使用する。
@Mapper public interface BeanMapper { @Mapping(target = "name", qualifiedByName = "toUpper") // (1) Target map(Source source); @Named("toUpper") // (2) default String stringToUpper(String string) { if (string == null) { return null; } return string.toUpperCase(); } }
項番 説明 (1) targetにnameを指定し、qualifiedByNameに下記の@Named
アノテーションで設定した名前を指定することにより、このフィールドのみに下記のマッピングロジックを反映できる。titleフィールドには@Named
アノテーションの名前を指定していないので、(2)で設定するロジックは反映されない。 (2) マッピングロジックを定義するデフォルトメソッドに@Named
アノテーションを付与し、任意の名前を設定する。
@Named
アノテーションを別のクラスに付与することで、独自マッピング定義をマッピングインタフェースの外で定義する事ができる。
ロジック定義用のクラス
@Named("StringConverter") // (1) public class StringLogic { @Named("UpperConverter") // (2) public String stringToUpper(String string) { if (string == null) { return null; } return string.toUpperCase(); } }
項番 説明 (1)クラスレベルに@Named
アノテーションを付与し、任意の名前を設定する。(2)メソッドレベルに@Named
アノテーションを付与し、任意の名前を設定する。マッパーインタフェース
@Mapper(uses = StringLogic.class) // (1) public interface BeanMapper { @Mapping(target = "name", qualifiedByName = {"StringConverter", "UpperConverter"}) // (2) Target map(Source source);. }
項番 説明 (1)@Mapper
アノテーションのuses
にロジック定義用のクラスを指定する。(2)qualifiedByNameに、マッピングロジックを定義したクラスの@Named
アノテーションで設定した名前を記載する。クラスレベルに設定した名前、メソッドレベルに設定した名前の順番で設定する。マッピング例
Source source = new Source(); source.setName("sourceName"); source.setTitle("sourceTitle"); Target target = beanMapper.map(source); System.out.println(target.getName()); System.out.println(target.getTitle());
上記のコードを実行すると以下のように出力される。
SOURCENAME sourceTitle
ソースのname
フィールドのみ独自マッピングが適用されていることが分かる。
この方法を使えばMapStructがサポートしていないデータ型のマッピングもできる。
例 : java.lang.String
<=> java.sql.Date
マッパーインタフェース
@Mapper public interface BeanMapper { Target map(Source source); default Date stringToDate(String string) { return Date.valueOf(string); } }
7.6.3.2. ソースのnullフィールドの制御¶
null
の場合について、@Mapping
アノテーションで挙動を制御できる。以下にソースのフィールドがnull
の場合にデフォルト値を設定する方法を記載する。
ソース
public class Source { private int id; private String name; // omitted setter/getter }
ターゲット
public class Target { private int id; private String name; // omitted setter/getter }
マッパーインタフェース
@Mapper public interface BeanMapper { @Mapping(target = "name", defaultValue = "DefaultName") // (1) void map(Source source, @MappingTarget Target target); }
項番 説明 (1)ソースのフィールドがnull
の場合、あらかじめ設定した値にするにはdefaultValue
に値を設定する。設定する値は文字列であり、ターゲットのフィールドの型に合わせて変換される。マッピング例
Source source = new Source(); source.setId(1); source.setName(null); Target target = new Target(); target.setId(2); target.setName("TargetName"); beanMapper.map(source, target); System.out.println(target.getId()); System.out.println(target.getName());
上記のコードを実行すると以下のように出力される。
1 DefaultName
ソースのname
フィールドはnull
のため、defaultValue
に設定された値がマッピングされている。
また、nullValuePropertyMappingStrategy
の値を設定することで、ソースのフィールドがnull
の場合の動作をカスタマイズできる。
設定値 | 説明 |
---|---|
NullValuePropertyMappingStrategy.SET_TO_NULL |
デフォルト値。nullをマッピングする。
|
NullValuePropertyMappingStrategy.SET_TO_DEFAULT |
プロパティの型に対応した、あらかじめ決められたデフォルト値をマッピングする。例えば、Stringの場合は
"" (空文字)をマッピングする。その他の型についてはNullValuePropertyMappingStrategyのJavadocを参照されたい。
|
NullValuePropertyMappingStrategy.IGNORE |
ソースのフィールドが
null の場合はマッピングから除外される。 |
以下にNullValuePropertyMappingStrategy.IGNOREを設定した場合の例を示す。
@Mapper public interface BeanMapper { @Mapping(target = "name", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) // (1) void map(Source source, @MappingTarget Target target); }
項番 説明 (1) ソースのフィールドがnull
の場合にマッピングから除外したい場合はnullValuePropertyMappingStrategy
にNullValuePropertyMappingStrategy.IGNORE
を設定する。
ソース
public class Source { private int id; private String name; // omitted setter/getter }
ターゲット
public class Target { private int id; private String name; // omitted setter/getter }
マッピング例
Source source = new Source(); source.setId(1); source.setName(null); Target target = new Target(); target.setId(2); target.setName("TargetName"); beanMapper.map(source, target); System.out.println(target.getId()); System.out.println(target.getName());
上記のコードを実行すると以下のように出力される。
1 TargetName
ソースのname
フィールドはnull
のため、マッピングから除外されている。
7.6.3.3. 条件付きマッピング¶
@Condition
アノテーションを使用して条件を定義することにより、その条件を満たすフィールドのみをマッピングするように設定できる。- defaultメソッドを用いた方法
@Named
アノテーションを用いた方法
あるマッパーインタフェース内で定義する、すべてのフィールドに対して、同一の条件を付与したい場合はdefaultメソッドで条件を定義することを推奨する。
7.6.3.3.1. defaultメソッドを用いた方法¶
null
でない、かつ、空文字でない場合にマッピングの対象とする例を示す。ソース
public class Source { private String name; private String title; // omitted setter/getter }
ターゲット
public class Target { private String name; private String title; // omitted setter/getter }
マッパーインタフェース
@Mapper public interface BeanMapper { void map(Source source, @MappingTarget Target target); @Condition // (1) default boolean isNotEmpty(String string) { return StringUtils.hasLength(string); } }
項番 説明 (1)@Condition
アノテーションをbooleanを返すメソッドに付与することで条件付きのマッピングを設定できる。返り値がtrueの場合は通常通りマッピングが行われ、falseの場合はnull
がマッピングされる。マッピング例
Source source = new Source(); source.setName(""); source.setTitle(""); Target target = new Target(); target.setName("TargetName"); target.setTitle("TargetTitle"); beanMapper.map(source, target); System.out.println(target.getName()); System.out.println(target.getTitle());
上記のコードを実行すると以下のように出力される。
null null
nameフィールド、titleフィールドともに定義した条件が適用される。
7.6.3.3.2. @Namedアノテーションを用いた方法¶
@Named
アノテーションを使用する。マッパーインタフェース
@Mapper public interface BeanMapper { @Mapping(target = "name", conditionQualifiedByName = "NotEmpty") // (1) void map(Source source, @MappingTarget Target target); @Named("NotEmpty") // (2) @Condition default boolean isNotEmpty(String value) { return StringUtils.hasLength(string); } }
項番 説明 (1)conditionQualifiedByNameに、(2)の@Named
アノテーションで設定した名前を指定する。これにより、特定のフィールドのみに下記の条件を反映できる。(2)条件を定義するデフォルトメソッドに@Named
アノテーションを付与し、任意の名前を設定する。
Note
独自のマッピングの定義方法に記載してある内容と同様に、@Named
アノテーションを使用すれば別クラスに条件のロジックを記載できる。
マッピング例
Source source = new Source(); source.setName(""); source.setTitle(""); Target target = new Target(); target.setName("TargetName"); target.setName("TargetTitle"); beanMapper.map(source, target); System.out.println(target.getName()); System.out.println(target.getTitle());
上記のコードを実行すると以下のように出力される。
null (空文字)
name
フィールドはnull
がマッピングされているが、title
フィールドは条件が適用されていないため空文字がそのままマッピングされている。null
をマッピングせずにマッピングから除外したい場合はソースのnullフィールドの制御に記載してあるように、nullValuePropertyMappingStrategy
にNullValuePropertyMappingStrategy.IGNORE
を設定すればよい。7.6.3.4. 文字列からの変換のフォーマット指定¶
java.time.LocalDateTime
への変換と、java.math.BigDecimal
からStringの変換について説明する。ソース
public class Source { private BigDecimal number private String date; // omitted setter/getter }
ターゲット
public class Target { private String number; private LocalDateTime date; // omitted setter/getter }
マッパーインタフェース
@Mapper public interface BeanMapper { @Mapping(target = "number", numberFormat = "000,000") // (1) @Mapping(target = "date", dateFormat = "uuuu-MM-dd HH:mm:ss") // (2) Target map(Source source); }
項番 説明 (1)targetにターゲットのフィールド名を指定し、numberFormatにDecimalFormat
形式のフォーマットを指定する。(2)targetにターゲットのフィールド名を指定し、dateFormatにSimpleDateFormat
形式のフォーマットを指定する。マッピング
Source source = new Source(); source.setNumber(new BigDecimal("123456")); source.setDate("2022-10-10 11:11:11"); Target target = beanMapper.map(source); System.out.println(target.getNumber()); System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(target.getDate()));
上記のコードを実行すると以下のように出力される。
123,456 2022-10-10 11:11:11
マッピングメソッドで指定したフォーマットが適用されていることが分かる。
7.6.3.5. 複数のマッパーインタフェースの共通設定¶
@MapperConfig
アノテーションを利用して、共通設定用のインタフェースを書くことで実現することができる。共通設定用のインタフェース
// (1) @MapperConfig( unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.IGNORE, //omitted ) public interface BeanMapperConfig { }
項番 説明 (1)共通設定用のインタフェースに@MapperConfig
アノテーションを付与する。@Mapper
アノテーションと同じ属性を設定できる。マッパーインタフェース
@Mapper(config = BeanMapperConfig.class) // (2) public interface BeanMapper { }
項番 説明 (2)@Mapper
アノテーションのconfigに共通設定用のインタフェースのクラス型を指定する。これによりこのマッパーインタフェースに(1)の設定内容が反映される。
7.6.4. Appendix¶
7.6.4.1. ターゲットに使用されるコンストラクタ¶
@Default
アノテーションが付与されている場合、そのコンストラクタが使用される。
public class Target { private int id; private String name; public Target() { } @Default // (1) public Target(int id, String name) { // omitted } }
項番 説明 (1)@Default
アノテーションが付与されているコンストラクタが使用される。@Default
アノテーションはMapStructから提供されておらず、独自アノテーションを作成する必要がある。アノテーションの作成例は マニュアル -Non-shipped annotations- を参照されたい。
- publicのコンストラクタが1つしか存在しない場合、そのコンストラクタが使用される。
public class Target { private int id; private String name; protected Target() { } // (1) public Target(int id, String name) { // omitted } }
項番 説明 (1) publicのコンストラクタが1つしか存在しないため、このコンストラクタが使用される。
- 引数の無いコンストラクタが使用される。
public class Target { private int id; private String name; // (1) public Target() { } public Target(int id, String name) { // omitted } }
項番 説明 (1) 引数が無いため、このコンストラクタが使用される。
上記のどの条件にも該当しない場合はコンパイルエラーが発生する。
public class Target { private int id; private String name; // (1) public Target(int id) { // omitted } // (1) public Target(int id, String name) { // omitted } }
項番 説明 (1) 独自にコンストラクタを定義しているが、上記3つのルールで使用するコンストラクタを決定できないためコンパイルエラーが発生する。
7.6.4.2. Lombokを使用する際の設定¶
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version> <!-- (1) -->
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version> <!-- (1) -->
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version> <!-- (2) -->
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amapstruct.defaultComponentModel=spring</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
項番 | 説明 |
---|---|
(1)
|
lombok.version は依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでプロパティとして変数を定義する必要は無い。 |
(2)
|
lombok-mapstruct-binding.version は依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでプロパティとして変数を定義する必要は無い。 |
Note
開発プロジェクトをブランクプロジェクトから作成すると、上記のLombokの設定はコメントアウトされた状態でpom.xmlに記載されている。