springでのアプリ開発で詰まったところをまとめていってます。
今回は、javaのorg.ModelMapperについてです。
1.この記事の内容
Bean同士のマッピングをしてくれるModelMapperで、
関係ないフィールドにもマッピングされたり、nullの値をマッピングさせない方法で少し躓いたので、まとめます。
2.そもそもModelMepperとは、、?
ModelMapperの役割的なことは、こちらの記事がわかりやすくまとめてくださっているので、読んでみてください。
簡単に要約すると、
・違う型のBean(インスタンス)同士の値をコピーしてくれるものです。
3. 違うフィールド名にも反映されてしまう
そんなModelMapperのぶち当たった課題
課題点
まず一つ目は、謎に他のフィールドにもマッピングされる問題。
私は、下記二つが、フィールドでマッピングさせたかったBeanです。
@Data
public class BeanA {
private Integer hogehogeId;
private Integer hogeId;
private Integer fugaId;
private String str;
}
@Data
public class BeanB {
//'hogehogeId'のBeanBバージョンがない
//他は同じフィールド名
private Integer hogeId;
private Integer fugaId;
private String str;
}
この二つのBeanの違いは、hogehogeIdがあるかないかだけです。
データの中身としてはこんな感じです。
{
"hogehoge_id": 111,
"hoge_id": 222,
"fuga_id": 333,
"str": "text"
}
{
"hoge_id": 999,
"uga_id": null,
"str": null
}
で、下記コードでマッピング処理をしました。
@service
@RequiredArgsConstructor
public class MappingService {
private final ModelMapper modelMapper;
public BeanA mapping() {
//BeanA(destination)
BeanA beanA = new BeanA();
beanA.setHogehogeId(111);
beanA.setHogeId(222);
beanA.setFugaId(333);
beanA.setStr("text");
//BeanB(source)
BeanB beanB = new BeanB();
BeanB.setHogeId(999) //hogeIdだけセット
//マッピング
modelMapper.map(beanB, beanA);
return beanA;
}
すると、結果がこうなりました。
{
"hogehoge_id": 999,
"hoge_id": 999,
"fuga_id": null,
"str": null
}
{
"hogehoge_id": null,
"hoge_id": 999,
"fuga_id": null,
"str": null
}
hogeIdだけが値がマッピングされて値が入ると思ったら、hogehogeIdにも同じ999がマッピングされていました。
解決方法
原因と考えられるのが、ModelMapperのデフォルトのConfigurationです。
config の中に、Matching strategy(マッチング戦略?)というものがあって、
デフォルト値はstandard
で、結構曖昧なマッチング戦略になっています。
hogeId
とhogehogeId
が似てるからマッピングされたのでしょうか。
具体的な解決策は、
ModelMapperの@Bean
アノテーションでのコンポーネントをspringコンテナに登録する際に、
このマッチング戦略の設定をしました。
public class ModelMapperBean{
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
//マッチング戦略を厳しいものに設定
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
//型の完全マッチなど
modelMapper.getConfiguration().setFullTypeMatchingRequired(true);
return modelMapper;
}
}
この設定にすることで、理想通りのマッピングになりました。
4.nullもマッピングされてしまう
続いて、二つ目の課題ですが、
課題点
先ほどの、マッピング前後をもう一度お見せします。
{
"hogehoge_id": 111,
"hoge_id": 222,
"fuga_id": 333,
"str": "text"
}
{
"hoge_id": 999,
"uga_id": null,
"str": null
}
マッピング↓
{
"hogehoge_id": null,
"hoge_id": 999,
"fuga_id": null,
"str": null
}
このnullです。
今回の要件的に、nullのものはマッピングを無視して、値がはいっているもののみをマッピングして、destenationに更新をかけるイメージでした。
しかし、当たり前ですが、sourceのnullをnullでdestinationにマッピングされてします。
いろいろ調べて、resource配下のapplication.yamlに
modelmapper:
skip-null-enabled: true
にすると、nullをコピーしない設定にできるとあったのですが、
なぜかうまくいきませんでした。
バージョンの問題かなと思ったりして、
こちらを参考に、最新のバージョンに変えたりしたのですが、反映されず、、、
(原因わかっていません。思いつく方いらっしゃいましたら、ご教授ください。。)
解決策
これも、課題1
の解決策と同様に、
ModelMapperのConfigurationに設定をすると解決しました。
あまり、 Beanの概念もわかっておらず、@Beanでのコンポーネント登録の際に、configもいじれることを知らなかったので、
課題1が解決した時に、もしやこの設定も!!?って感じに調べてみると、、
ありました!
public class ModelMapperBean{
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
//nullをスキップする設定
modelMapper.getConfiguration().setSkipNullEnabled(true);
return modelMapper;
}
}
この設定で、理想通りのマッピングがされました。
{
"hogehoge_id": 111,
"hoge_id": 999,
"fuga_id": 333,
"str": "text"
}
hoge_idだけがマッピングされました。
5.おわりに
他にも、いくつかconfig設定できるそうなので、このリファレンスもご参考ください。
どちらも、期待通りの挙動にできてよかったです。
初歩的な躓きですが、どなたかの参考に参考になれば幸いです。
誤っている箇所などあれば、なんなりとご指摘いただきたく。。
ご朗読、ありがとうございました。