MapStructのクイックガイド
1概要
この記事では、簡単に言うと、http://www.mapstruct.org/[MapStruct]の使用法を探ります。
このAPIには、2つのJava Beans間で自動的にマッピングされる機能が含まれています。 MapStructを使えば、インターフェースを作成するだけでよく、ライブラリはコンパイル時に自動的に具象実装を作成します。
2 MapStructとTransfer Objectのパターン
ほとんどのアプリケーションで、POJOを他のPOJOに変換する定型コードがたくさんあります。
たとえば、一般的な種類の変換は、持続性をサポートするエンティティとクライアント側に送信されるDTOの間で発生します。
そのため、MapStructが
–
手動でBeanマッパーを作成するのは時間がかかります。ライブラリはBeanマッパークラスを自動的に生成することができます。
3 Maven
Mavenの__pom.xmlに以下の依存関係を追加しましょう。
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.1.0.Final</version>
</dependency>
Mapstruct
および彼の最新の安定版リリースhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.mapstruct%22%20AND%20a%3A%22mapstruct-processor%22[processor]は両方ともMaven Centralから入手可能です。リポジトリ
maven-compiler-plugin
プラグインの設定部分に
annotationProcessorPaths
セクションを追加しましょう。
mapstruct-processor
は、ビルド中にマッパー実装を生成するために使用されます。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.1.0.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
4基本マッピング
4.1. POJO
を作成する
まず簡単なJava POJOを作成しましょう。
public class SimpleSource {
private String name;
private String description;
//getters and setters
}
public class SimpleDestination {
private String name;
private String description;
//getters and setters
}
4.2. マッパーインタフェース
@Mapper
public interface SimpleSourceDestinationMapper {
SimpleDestination sourceToDestination(SimpleSource source);
SimpleSource destinationToSource(SimpleDestination destination);
}
MapStructによって作成されているため、
SimpleSourceDestinationMapper
の実装クラスは作成されていません。
4.3. 新しいマッパー
mvnクリーンインストール
を実行することでMapStruct処理を開始できます。
これは
/target/generated-sources/annotations/
の下に実装クラスを生成します。
これが、MapStructが自動作成するクラスです。
public class SimpleSourceDestinationMapperImpl
implements SimpleSourceDestinationMapper {
@Override
public SimpleDestination sourceToDestination(SimpleSource source) {
if ( source == null ) {
return null;
}
SimpleDestination simpleDestination = new SimpleDestination();
simpleDestination.setName( source.getName() );
simpleDestination.setDescription( source.getDescription() );
return simpleDestination;
}
@Override
public SimpleSource destinationToSource(SimpleDestination destination){
if ( destination == null ) {
return null;
}
SimpleSource simpleSource = new SimpleSource();
simpleSource.setName( destination.getName() );
simpleSource.setDescription( destination.getDescription() );
return simpleSource;
}
}
4.4. テストケース
最後に、生成されたすべてのものについて、
SimpleSource
の値が
SimpleDestination
の値と一致することを示すテストケースを作成しましょう。
public class SimpleSourceDestinationMapperTest {
private SimpleSourceDestinationMapper mapper
= Mappers.getMapper(SimpleSourceDestinationMapper.class);
@Test
public void givenSourceToDestination__whenMaps__thenCorrect() {
SimpleSource simpleSource = new SimpleSource();
simpleSource.setName("SourceName");
simpleSource.setDescription("SourceDescription");
SimpleDestination destination = mapper.sourceToDestination(simpleSource);
assertEquals(simpleSource.getName(), destination.getName());
assertEquals(simpleSource.getDescription(),
destination.getDescription());
}
@Test
public void givenDestinationToSource__whenMaps__thenCorrect() {
SimpleDestination destination = new SimpleDestination();
destination.setName("DestinationName");
destination.setDescription("DestinationDescription");
SimpleSource source = mapper.destinationToSource(destination);
assertEquals(destination.getName(), source.getName());
assertEquals(destination.getDescription(),
source.getDescription());
}
}
5依存性注入によるマッピング
次に、単に__Mappers.getMapper(YourClass.class)を呼び出して、MapStructでマッパーのインスタンスを取得しましょう。
もちろん、これはインスタンスを取得するための非常に手動の方法です。それよりはるかに良い方法は、必要な場所に直接マッパーをインジェクトすることです(私たちのプロジェクトがDependency Injectionソリューションを使用する場合)。
-
幸いにもMapStructはSpringとCDIの両方をしっかりサポートしています** (
コンテキストと依存性注入
)
MapperでSpring IoCを使用するには、
@ Mapper
に値
spring
を付けて
__componentModel
attributeを追加する必要があります。CDIの場合は
cdi__になります。
5.1. マッパーを変更する
以下のコードを
SimpleSourceDestinationMapper
に追加します。
@Mapper(componentModel = "spring")
public interface SimpleSourceDestinationMapper
6. 異なるフィールド名を持つフィールドのマッピング
前の例から、MapStructはBeanが同じフィールド名を持っているので、Beanを自動的にマップできました。では、これからマップしようとしているBeanのフィールド名が異なるとしたらどうでしょうか。
この例では、
Employee
と
EmployeeDTO
という新しいBeanを作成します。
6.1. 新しいPOJO
public class EmployeeDTO {
private int employeeId;
private String employeeName;
//getters and setters
}
public class Employee {
private int id;
private String name;
//getters and setters
}
6.2. マッパーインタフェース
異なるフィールド名をマッピングするときは、ソースフィールドをターゲットフィールドに設定し、それを行うために
@ Mappings
アノテーションを追加する必要があります。このアノテーションは、target属性とsource属性を追加するために使用する
@ Mapping
アノテーションの配列を受け入れます。
MapStructでは、ドット表記を使用してBeanのメンバーを定義することもできます。
@Mapper
public interface EmployeeMapper {
@Mappings({
@Mapping(target="employeeId", source="entity.id"),
@Mapping(target="employeeName", source="entity.name")
})
EmployeeDTO employeeToEmployeeDTO(Employee entity);
@Mappings({
@Mapping(target="id", source="dto.employeeId"),
@Mapping(target="name", source="dto.employeeName")
})
Employee employeeDTOtoEmployee(EmployeeDTO dto);
}
6.3. テストケース
ここでも、ソースとデスティネーションの両方のオブジェクト値が一致することをテストする必要があります。
@Mapper
public interface EmployeeMapper {
@Mappings({
@Mapping(target="employeeId", source="entity.id"),
@Mapping(target="employeeName", source="entity.name")
})
EmployeeDTO employeeToEmployeeDTO(Employee entity);
@Mappings({
@Mapping(target="id", source="dto.employeeId"),
@Mapping(target="name", source="dto.employeeName")
})
Employee employeeDTOtoEmployee(EmployeeDTO dto);
}
その他のテストケースはhttps://github.com/eugenp/tutorials/tree/master/mapstruct[Githubプロジェクト]にあります。
7. 子BeanとBeanのマッピング
次に、あるBeanを他のBeanへの参照とマップする方法を説明します。
7.1. POJO
を修正する
Employee
オブジェクトに新しいBean参照を追加しましょう。
public class EmployeeDTO {
private int employeeId;
private String employeeName;
private DivisionDTO division;
//getters and setters omitted
}
public class Employee {
private int id;
private String name;
private Division division;
//getters and setters omitted
}
public class Division {
private int id;
private String name;
//default constructor, getters and setters omitted
}
7.2. マッパーを変更する
ここでは、
Division
を
DivisionDTO
に、またはその逆に変換するメソッドを追加する必要があります。 MapStructが、オブジェクト型の変換が必要であり、変換するメソッドが同じクラスに存在することを検出した場合は、自動的にそれを使用します。
これをマッパーに追加しましょう。
DivisionDTO divisionToDivisionDTO(Division entity);
Division divisionDTOtoDivision(DivisionDTO dto);
7.3. テストケースを修正する
既存のものにいくつかのテストケースを修正して追加しましょう。
@Test
public void givenEmpDTONestedMappingToEmp__whenMaps__thenCorrect() {
EmployeeDTO dto = new EmployeeDTO();
dto.setDivision(new DivisionDTO(1, "Division1"));
Employee entity = mapper.employeeDTOtoEmployee(dto);
assertEquals(dto.getDivision().getId(),
entity.getDivision().getId());
assertEquals(dto.getDivision().getName(),
entity.getDivision().getName());
}
8型変換を使ったマッピング
MapStructには、いくつかの既成の暗黙の型変換もあります。この例では、Stringの日付を実際の
Date
オブジェクトに変換します。
暗黙的な型変換の詳細については、http://mapstruct.org/documentation/1.0/reference/html/#implicit-type-conversionversion[MapStruct reference guide]を参照してください。
8.1. 豆を変更する
従業員の開始日を追加します。
public class Employee {
//other fields
private Date startDt;
//getters and setters
}
public class EmployeeDTO {
//other fields
private String employeeStartDt;
//getters and setters
}
8.2. マッパーを変更する
マッパーを変更して、開始日に
dateFormat
を指定します。
@Mappings({
@Mapping(target="employeeId", source = "entity.id"),
@Mapping(target="employeeName", source = "entity.name"),
@Mapping(target="employeeStartDt", source = "entity.startDt",
dateFormat = "dd-MM-yyyy HH:mm:ss")})
EmployeeDTO employeeToEmployeeDTO(Employee entity);
@Mappings({
@Mapping(target="id", source="dto.employeeId"),
@Mapping(target="name", source="dto.employeeName"),
@Mapping(target="startDt", source="dto.employeeStartDt",
dateFormat="dd-MM-yyyy HH:mm:ss")})
Employee employeeDTOtoEmployee(EmployeeDTO dto);
8.3. テストケースを修正する
変換が正しいことを確認するために、もう少しテストケースを追加しましょう。
private static final String DATE__FORMAT = "dd-MM-yyyy HH:mm:ss";
@Test
public void givenEmpStartDtMappingToEmpDTO__whenMaps__thenCorrect() throws ParseException {
Employee entity = new Employee();
entity.setStartDt(new Date());
EmployeeDTO dto = mapper.employeeToEmployeeDTO(entity);
SimpleDateFormat format = new SimpleDateFormat(DATE__FORMAT);
assertEquals(format.parse(dto.getEmployeeStartDt()).toString(),
entity.getStartDt().toString());
}
@Test
public void givenEmpDTOStartDtMappingToEmp__whenMaps__thenCorrect() throws ParseException {
EmployeeDTO dto = new EmployeeDTO();
dto.setEmployeeStartDt("01-04-2016 01:00:00");
Employee entity = mapper.employeeDTOtoEmployee(dto);
SimpleDateFormat format = new SimpleDateFormat(DATE__FORMAT);
assertEquals(format.parse(dto.getEmployeeStartDt()).toString(),
entity.getStartDt().toString());
}
9抽象クラスを使ったマッピング
時々、私たちは@Mapping能力を超えるようにマッパーをカスタマイズしたいかもしれません。
たとえば、型変換に加えて、以下の例のように何らかの方法で値を変換することがあります。
このような場合、MapStructによって生成されるべき抽象クラスを作成し、カスタマイズしたいメソッドを実装して抽象クラスのままにすることができます。
** 9.1. ベーシックモデル
この例では、次のクラスを使用します。
public class Transaction {
private Long id;
private String uuid = UUID.randomUUID().toString();
private BigDecimal total;
//standard getters
}
そしてそれにマッチするDTO:
public class TransactionDTO {
private String uuid;
private Long totalInCents;
//standard getters and setters
}
ここで注意が必要なのは、
BigDecimal
total
の金額を
Long totalInCents__に変換することです。
9.2. マッパーの定義
これを実現するには、
__Mapper
__を抽象クラスとして作成します。
@Mapper
abstract class TransactionMapper {
public TransactionDTO toTransactionDTO(Transaction transaction) {
TransactionDTO transactionDTO = new TransactionDTO();
transactionDTO.setUuid(transaction.getUuid());
transactionDTO.setTotalInCents(transaction.getTotal()
.multiply(new BigDecimal("100")).longValue());
return transactionDTO;
}
public abstract List<TransactionDTO> toTransactionDTO(Collection<Transaction> transactions);
}
ここでは、単一オブジェクト変換用に完全にカスタマイズされたマッピング方法を実装しました。
一方、
__Collection
を
List
abstractにマッピングするためのメソッドは残したので、
MapStruct
__でそれを実装します。
9.3. 生成結果
単一の
__Transaction
を
TransactionDTO
にマッピングするためのメソッドをすでに実装しているので、
Mapstruct
__が2番目のメソッドでそれを使用することを期待します。以下が生成されます。
@Generated
class TransactionMapperImpl extends TransactionMapper {
@Override
public List<TransactionDTO> toTransactionDTO(Collection<Transaction> transactions) {
if ( transactions == null ) {
return null;
}
List<TransactionDTO> list = new ArrayList<>();
for ( Transaction transaction : transactions ) {
list.add( toTransactionDTO( transaction ) );
}
return list;
}
}
12行目でわかるように、
__MapStruct
__は、生成されたメソッド内の実装を使用します。
10結論
この記事ではMapStructの概要を説明しました。MapSライブラリの基本の大部分とそれをアプリケーションで使用する方法を紹介しました。
これらの例とテストの実装はhttps://github.com/eugenp/tutorials/tree/master/mapstruct[Github]プロジェクトにあります。これはMavenプロジェクトなので、そのままインポートして実行するのは簡単なはずです。