Javaで便利な Beanマッピング ModelMapper

最近、Spring Frameworkを使用したプロジェクトで便利なBeanマッピングライブラリを使用したので紹介します。

## 1. 概要
Beanマッピングとは、一つのBeanを他のBeanにフィールド値をコピーすることで、アプリケーション層とドメイン層で、データの受け渡しをする場合に使用することが多いです。
MVCにおける例としては、コントローラで受け取ったFormオブジェクトと、Entityオブジェクトを相互に変換するケースがよくあります。
今回はConvention(規約)ベースを謳っているModelMapperを使用してみました。

## 2. 利用するメリット
ModelMapperを使用した場合と使用しない場合のコード例を挙げます。
### 2.1. 煩雑になり、プログラムの見通しが悪くなる例

```Java
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を使用すると以下のように記載できます。

```Java
ModelMapper modelMapper = new ModelMapper();

User user = userService.findById(userId);
XxxOutput output = modelMapper.map(user, XxxOutput.class);
```
なお変換元、および変換先のBeanのフィールドにはgetter,setterが必要になりますが
[**lombok**](https://projectlombok.org/) を使用することで記述が楽になるでしょう。
## 3. 利用方法
### 3.1. ライブラリの利用 
Gradleを使用する場合、build.gradle のdepenciesに以下の行を追加します。
2017/1 現在最新バージョンは 0.7.5です。

```groovy
  compile ('org.modelmapper:modelmapper:0.7.5')
```
### 3.2. 単独での利用
単独で使用するとき、以下のように、**org.modelmapper.ModelMapper** のインスタンスを作成します。

```Java
ModelMapper modelMapper = new ModelMapper();
```
### 3.3. Springにbeanとして登録
beanマッピングは、アプリケーションにおいて頻繁に使用するので、Springが提供する@Beanとしてシングルトンオブジェクトで使用すると良いです。
**AppConfig.java**

```java
@Configuration
@ComponentScan("com.example")
public class AppConfig {
  @Bean
  public ModelMapper modelMapper() {
    return new ModelMapper();
  }
}
```

このように登録することで、コントローラー内では@AutoWiredによるインジェクションが利用できます。

``` Java
  @Autowired
  private ModelMapper modelMapper;
```
## 4. 実装例

### 4.1. Bean間のフィールド名、型が同じ場合のマッピング
デフォルトの動作として、ModelMapperは対象のBean間のフィールド名が同じであればマッピングできます。

**変換元のBean定義**

```java
import lombok.Data;

@Data
public class Source {
    private int id;
    private String name;
}
```

**変換先のBean定義**

```java

import lombok.Data;

@Data
public class Destination {
    private int id;
    private String name;
}
```

以下のように、 Mapper の map メソッドを使ってBeanマッピングを行います。 下記メソッドを実行した後、Destinationオブジェクトが新たに作成され、sourceの各フィールドの値が作成されたDestinationオブジェクトにコピーされます。

```Java
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オブジェクトのフィールドをコピーしたい場合は、

``` java
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定義**

``` java
import lombok.Data;

@Data
public class Source {
    private int id;
    private String name;
}
```

**変換先のBean定義**

```java
import lombok.Data;

@Data
public class Destination {
    private int id;
    private String name;
    private String title;
}
```

**マッピング例**

```java
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定義**

``` java
import lombok.Data;

@Data
public class User {
    private int id;
    private String name;
}
```

```java
import lombok.Data;

@Data
public class Source {
  String zipCode;
  User user;
}
```

**変換先のBean定義**

``` java
import lombok.Data;

@Data
public class User {
    private int id;
    private String name;
}
```

```java
import lombok.Data;

@Data
public class Destination {
  String zipCode;
  User user;
}
```

**マッピング例**

``` java
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定義**

```java
import lombok.Data;

@Data
public class Destination {
    private int id;
    private String name;
}
```

**マッピング例**

``` java
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を使うと配列の変換を簡便に記述できます。

``` java
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]
```

``` java
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]
```

また、文字列の配列もうまいこと変換してくれます。

``` java
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](http://modelmapper.org/user-manual/
) のマニュアルを参考にしてください。


**参考**

* [はじめてのSpring Boot で ModelMapper を利用してみる](http://blog.beaglesoft.net/entry/%3Fp%3D773)
* [modelmapperを触ってみた](http://d.hatena.ne.jp/hikkisan/20121122/1353678142)
* [ModelMapper](http://modelmapper.org/)
* [Lombok 使い方メモ](http://qiita.com/opengl-8080/items/671ffd4bf84fe5e32557)
* [lombok](https://projectlombok.org/)