最近、Spring Frameworkを使用したプロジェクトで便利なBeanマッピングライブラリを使用したので紹介します。
1. 概要
Beanマッピングとは、一つのBeanを他のBeanにフィールド値をコピーすることで、アプリケーション層とドメイン層で、データの受け渡しをする場合に使用することが多いです。
MVCにおける例としては、コントローラで受け取ったFormオブジェクトと、Entityオブジェクトを相互に変換するケースがよくあります。
今回はConvention(規約)ベースを謳っているModelMapperを使用してみました。
2. 利用するメリット
ModelMapperを使用した場合と使用しない場合のコード例を挙げます。
2.1. 煩雑になり、プログラムの見通しが悪くなる例
User user = userService.findById(userId);
XxxOutput output = new XxxOutput();
output.setUserId(user.getUserId());
output.setFirstName(user.getFirstName());
output.setLastName(user.getLastName());
output.setTitle(user.getTitle());
output.setBirthDay(user.getBirthDay());
output.setGender(user.getGender());
output.setStatus(user.getStatus());
2.2. ModelMapperを使用した場合の例
上記のコードはModelMapperを使用すると以下のように記載できます。
ModelMapper modelMapper = new ModelMapper();
User user = userService.findById(userId);
XxxOutput output = modelMapper.map(user, XxxOutput.class);
なお変換元、および変換先のBeanのフィールドにはgetter,setterが必要になりますが
lombok を使用することで記述が楽になるでしょう。
3. 利用方法
3.1. ライブラリの利用
Gradleを使用する場合、build.gradle のdepenciesに以下の行を追加します。
2017/1 現在最新バージョンは 0.7.5です。
compile ('org.modelmapper:modelmapper:0.7.5')
3.2. 単独での利用
単独で使用するとき、以下のように、org.modelmapper.ModelMapper のインスタンスを作成します。
ModelMapper modelMapper = new ModelMapper();
3.3. Springにbeanとして登録
beanマッピングは、アプリケーションにおいて頻繁に使用するので、Springが提供する@Beanとしてシングルトンオブジェクトで使用すると良いです。
AppConfig.java
@Configuration
@ComponentScan("com.example")
public class AppConfig {
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
このように登録することで、コントローラー内では@AutoWiredによるインジェクションが利用できます。
@Autowired
private ModelMapper modelMapper;
4. 実装例
4.1. Bean間のフィールド名、型が同じ場合のマッピング
デフォルトの動作として、ModelMapperは対象のBean間のフィールド名が同じであればマッピングできます。
変換元のBean定義
import lombok.Data;
@Data
public class Source {
private int id;
private String name;
}
変換先のBean定義
import lombok.Data;
@Data
public class Destination {
private int id;
private String name;
}
以下のように、 Mapper の map メソッドを使ってBeanマッピングを行います。 下記メソッドを実行した後、Destinationオブジェクトが新たに作成され、sourceの各フィールドの値が作成されたDestinationオブジェクトにコピーされます。
ModelMapper modelMapper = new ModelMapper();
Source source = new Source();
source.setId(1);
source.setName("SourceName");
Destination destination = modelMapper.map(source, Destination.class);
System.out.println(destination.getId());
System.out.println(destination.getName());
上記のコードを実行すると以下のように出力され、作成されたオブジェクトにコピー元のオブジェクトの値が設定されていることが分かります。
1
SourceName
既に存在しているdestinationオブジェクトに、sourceオブジェクトのフィールドをコピーしたい場合は、
ModelMapper modelMapper = new ModelMapper();
Source source = new Source();
source.setId(1);
source.setName("SourceName");
Destination destination = new Destination();
destination.setId(2);
destination.setName("DestinationName");
modelMapper.map(source, destination);
System.out.println(destination.getId());
System.out.println(destination.getName());
上記のコードを実行すると以下のように出力され、コピー元のオブジェクトの値がコピー先に反映されていることが分かります。
1
SourceName
DestinationクラスのフィールドでSourceクラスに存在しないものは、コピー前後で値は変わりません。
変換元のBean定義
import lombok.Data;
@Data
public class Source {
private int id;
private String name;
}
変換先のBean定義
import lombok.Data;
@Data
public class Destination {
private int id;
private String name;
private String title;
}
マッピング例
ModelMapper modelMapper = new ModelMapper();
Source source = new Source();
source.setId(1);
source.setName("SourceName");
Destination destination = new Destination();
destination.setId(2);
destination.setName("DestinationName");
destination.setTitle("DestinationTitle");
modelMapper.map(source, destination);
System.out.println(destination.getId());
System.out.println(destination.getName());
System.out.println(destination.getTitle());
上記のコードを実行すると以下のように出力されます。
Sourceクラスにはtitleフィールドが ないため、Destinationオブジェクトのtitleフィールドは、コピー前のフィールド値から変更がないことを確認しましょう。
1
SourceName
DestinationTitle
4.2. ネストしたオブジェクトのマッピング
ネストしたオブジェクトについてもいい感じにマッピングしてくれます。
変換元のBean定義
import lombok.Data;
@Data
public class User {
private int id;
private String name;
}
import lombok.Data;
@Data
public class Source {
String zipCode;
User user;
}
変換先のBean定義
import lombok.Data;
@Data
public class User {
private int id;
private String name;
}
import lombok.Data;
@Data
public class Destination {
String zipCode;
User user;
}
マッピング例
ModelMapper modelmapper = new ModelMapper();
User srcUser = new User();
srcUser.setId(1);
srcUser.setName("srcName");
Source src = new Source();
src.setUser(srcUser);
src.setZipCode("100-0001");
Destination dest = modelmapper.map(src, Destination.class);
System.out.println(dest.toString());
上記のコードを実行すると以下のように出力されます。
Destination(zipCode=100-0001, user=User(id=1, name=srcName))
4.3. Mapオブジェクトからのマッピング
Mapからbeanへのマッピングも利用機会が多いと思われます。
元となるMapは Map<String,Object>
とし、keyとなるStringにフィールド名、valueに値を入れます。
変換先のBean定義
import lombok.Data;
@Data
public class Destination {
private int id;
private String name;
}
マッピング例
ModelMapper modelmapper = new ModelMapper();
Map<String, Object> src = new HashMap<>();
src.put("id", 1);
src.put("name", "Jane Doe");
User destination = modelmapper.map(src, User.class);
System.out.println(destination.getId());
System.out.println(destination.getName());
上記のコードを実行すると以下のように出力されます。
1
Jane Doe
4.4. 配列のマッピング
ModelMapperを使うと配列の変換を簡便に記述できます。
ModelMapper modelmapper = new ModelMapper();
int[] intArray = {1, 2, 3, 4, 5};
double[] dblArray = modelmapper.map(intArray, double[].class);
System.out.println(Arrays.toString(dblArray));
上記のコードを実行すると以下のように出力されます。
[1.0, 2.0, 3.0, 4.0, 5.0]
double[] dblArray = {1.0, 2.0, 3.5, 4.0, 5.0};
int[] intArray = modelmapper.map(dblArray, int[].class);
System.out.println(Arrays.toString(intArray));
上記のコードを実行すると以下のように出力されます。
[1, 2, 3, 4, 5]
また、文字列の配列もうまいこと変換してくれます。
ModelMapper modelmapper = new ModelMapper();
String[] strArray = {"1", "2", "3", "4", "5"};
Integer[] intArray = modelmapper.map(strArray, Integer[].class);
double[] dblArray = modelmapper.map(strArray, double[].class);
System.out.println(Arrays.toString(intArray));
System.out.println(Arrays.toString(dblArray));
上記のコードを実行すると以下のように出力されます。
[1, 2, 3, 4, 5]
[1.0, 2.0, 3.0, 4.0, 5.0]
上記の例のほかにも、異なるフィールド名へのマッピングやマッピング時に値の変換を挟むことなどもできるようです。
詳細は、ModelMapper のマニュアルを参考にしてください。
参考