ModelMapperで1対1に対応しないフィールドのマッピング
最近はSpring Bootで開発する際にModelMapperを使っているのですが、1対1に対応しないようなフィールドをマッピングしたいと思うことがあります。
その方法について試してみたところ、ちょっと苦戦してしまったので今後のためにメモしておきます。
ModelMapperとは
Spring Bootで開発するときにはPOJOでデータを表現し、以下のような場面で使うことになります。
- コントローラでは、バリデーション用にフォームのクラス
- Spring Data JPAを使うため、エンティティのクラス
- Rest APIのJSONと対応するクラス(Mapで代用も可)
これらのクラスで共通して使えるPOJOを作るというのは困難で、似たようなフィールドを持つクラスを作ることになります。
その時にデータを移し替える必要がでてきますが、こういった処理は本質的なロジックではないのに、時間や手間がかかります。
この処理を簡単に実行できるライブラリが、ModelMapperです。
ModelMapperの基本的な使い方については、以下のサイトが詳しいです。
Javaで便利な Beanマッピング ModelMapper
ちなみにSpring向けのIntegrationも用意されているようなので、クラス間でModelMapperを使いまわしたい場合にはこちらを使ったほうがよさそうですね。
なお、ModelMapperはSpring Boot(Spring IO Platform)のDependencyには含まれていないので、バージョン管理は自分で行う必要があります。
1対1に対応しないフィールドのマッピング例
今回は以下のデータを相互に変換します。
なお、fullnameは半角スペースでfirstnameとlastnameが結合されたデータとします。
@Data
class Single {
private String fullname;
private String common;
}
@Data
class Multi {
private String firstname;
private String lastname;
private String common;
}
SingleをMultiに変換する場合、fullnameをfirstnameとlastnameに分けてデータを格納します。
これは以下のようにmappingとconverterを設定をすることで実現できます。
ModelMapper mapper = new ModelMapper();
mapper.addMappings(new PropertyMap<Single, Multi>() {
@Override
protected void configure() {
using(new Converter<Single, String>(){
public String convert(MappingContext<Single, String> context) {
return context.getSource().getFullname().split(" ")[0];
}
}).map(source, destination.getFirstname());
using(new Converter<Single, String>(){
public String convert(MappingContext<Single, String> context) {
return context.getSource().getFullname().split(" ")[1];
}
}).map(source, destination.getLastname());
}
});
MultiをSingleに変換する場合、firstnameとlastnameを結合し、fullnameとして格納します。
これは以下のようにmappingとconverterを設定することで実現できます。
ModelMapper mapper = new ModelMapper();
mapper.addMappings(new PropertyMap<Multi, Single>() {
@Override
protected void configure() {
using(new Converter<Multi, String>(){
public String convert(MappingContext<Multi, String> context) {
return context.getSource().getFirstname() + " "
+ context.getSource().getLastname();
}
}).map(source, destination.getFullname());
}
});
サンプルのソースコード全体はGithubにあります。
この出力結果は以下のようになります。
Multi(firstname=First, lastname=Last, common=common)
Single(fullname=First Last, common=common)
まとめ
最初はドキュメントを読んでも分からず、試行錯誤や情報収集の末になんとか実装できました。
ModelMapperはフィールドが1対1に対応していなくてもマッピングや変換処理を定義できる柔軟性があることが分かったので、これからも積極的に使っていきたいと思います。