weblog

技術的なメモ置き場。

【MapStruct】Beanマッピング(基本編)

基本編。

環境

  • MapStruct : 1.2.0.Final
  • Java : 9
  • JUnit : 4.12
  • AssertJ : 3.9.1

使用するBean

public class Person {
    private String name;
    private int age;
    // constructor/getter/setter
}

public class PersonEntity {
    private String name;
    private int age;
    // constructor/getter/setter
}

public class Address {
    private String address;
    private String zipCode;
    // constructor/getter/setter
}

public class Student {
    private String name;
    private int age;
    private String address;
    // constructor/getter/setter 
}

Mapperクラスの作成

Mapperクラスは、interfaceクラスまたはabstractクラスに @Mapping を付与して作成する。 MapStructはこのアノテーションを元にMapperの実装クラスを生成する。

interfaceクラスの場合

@Mapper
public interface FooMapper {
    @Mapping(...)
    Bar fooToBar(Foo foo);
}

abstractクラスの場合

@Mapper
public abstract FooMapper {
    @Mapping(...)
    public abstract Bar fooToBar(Foo foo);
}

Mapperクラスのインスタンス

Mapperクラスのインスタンスを生成するには、Mappers#getMapper を使用する。

FooMapper MAPPER = Mappers.getMapper(FooMapper.class);

他にも方法があるので公式ドキュメントを参照。 http://mapstruct.org/documentation/stable/reference/html/#retrieving-mapper

Mapperの作成

ここから実際にMapperを作成していく。

同一フィールド名を持つBeanへのマッピング

@Mapper
public interface BasicMapper {
    BasicMapper MAPPER = Mappers.getMapper(BasicMapper.class);

    Person toPerson(PersonEntity entity);
}

テストコード

@Test
public void test() {
    PersonEntity entity = new PersonEntity("マイケル", 23);
    
    // PersonEntity -> Person
    Person person = BasicMapper.MAPPER.toPerson(entity);
    
    assertThat(person.getName()).isEqualTo("マイケル");
    assertThat(person.getAge()).isEqualTo(23);
}

リストのマッピング

リストのマッピングをするには、単一Beanへのマッピングが定義してあることが条件。

@Mapper
public interface BasicMapper {
    BasicMapper MAPPER = Mappers.getMapper(BasicMapper.class);

    Person toPerson(PersonEntity entity);

    List<Person> toPersons(List<PersonEntity> entities);
}

テストコード

@Test
public void test() {
    List<PersonEntity> list = List.of(new PersonEntity("マイケル", 23), new PersonEntity("カリー", 30));

    // List -> List
    List<Person> persons = BasicMapper.MAPPER.toPersons(list);

    List<Person> expected = List.of(new Person("マイケル", 23), new Person("カリー", 30));
    assertThat(persons).hasSize(2).containsExactlyElementsOf(expected);
}

一部のフィールドをマッピング対象外にする

フィールドの一部をマッピング対象外にするには、 @Mapping アノテーションを利用する。 target属性に対象外にするフィールドと、ignore属性に true を設定する。

@Mapper
public interface BasicMapper {
    BasicMapper MAPPER = Mappers.getMapper(BasicMapper.class);

    @Mapping(target = "name", ignore = true)
    PersonEntity toPersonEntity(Person dto);
}

テストコード

@Test
public void test() {
    Person dto = new Person("マイケル", 23);
    PersonEntity entity = BasicMapper.MAPPER.toPersonEntity(dto);
    assertThat(entity.getName()).isNull();
    assertThat(entity.getAge()).isEqualTo(23);
}

カスタムメソッドを追加する

独自のマッピングをカスタムメソッドとして追加することができる。 interfaceクラスの場合はdefaultメソッドで定義する。

@Mapper
public interface BasicMapper {
    BasicMapper MAPPER = Mappers.getMapper(BasicMapper.class);

    default Person toPerson(PersonEntity entity) {
        Person dto = new Person();
        dto.setName(entity.getName() + "!!");
        dto.setAge(entity.getAge() + 100);
        return dto;
    }
}

テストコード

@Test
public void test() {
    PersonEntity entity = new PersonEntity("マイケル", 23);
    
    Person person = CustomMethodMapper.MAPPER.toPerson(entity);

    assertThat(person.getName()).isEqualTo("マイケル!!");
    assertThat(person.getAge()).isEqualTo(123);
}

複数のBeanから一つのBeanにマッピング

source属性に引数名.フィールド名、target属性にマッピング対象のフィールド名を指定する。

@Mapper
public interface BasicMapper {
    BasicMapper MAPPER = Mappers.getMapper(BasicMapper.class);

    @Mapping(source = "person.name", target = "name")
    @Mapping(source = "person.age", target = "age")
    @Mapping(source = "address.address", target = "address")
    Student personAndAddressToStudent(Person person, Address address);
}

テストコード

@Test
public void test() {
    Person person = new Person("マイケル", 23);
    Address address = new Address("ノースカロライナ州", "12345");
    
    // 2つのBean -> 1つのBean
    Student student = BasicMapper.MAPPER.personAndAddressToStudent(person, address);

    assertThat(student.getName()).isEqualTo("マイケル");
    assertThat(student.getAge()).isEqualTo(23);
    assertThat(student.getAddress()).isEqualTo("ノースカロライナ州");
}

Beanをアップデートする

マッピングするBeanとマッピングされるBeanを引数に指定する。そのとき更新対象のBeanの引数に @MappingTarget を付与する。

@Mapper
public interface BasicMapper {
    BasicMapper MAPPER = Mappers.getMapper(BasicMapper.class);

    void updateStudent(Person person, @MappingTarget Student student);
}

テストコード

@Test
public void test() {
    Person person = new Person("マイケル", 23);
    Student student = new Student("カリー", 30, "カリフォルニア州");

    // studentをpersonの内容でアップデート
    BasicMapper.MAPPER.updateStudent(person, student);
    
    assertThat(student.getName()).isEqualTo("マイケル");
    assertThat(student.getAge()).isEqualTo(23);
    assertThat(student.getAddress()).isEqualTo("カリフォルニア州"); // Personにaddressを持っていないので変わらない
}