weblog

技術的なメモ置き場。

【MapStruct】コンポーネントモデルを指定する

Mapperのコンポーネントモデルを指定することができる。
今回はSpring FrameworkでDIできるようにする。

環境

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

@Mapper のcomponentModel属性を使う

@Mapper のcomponentModel属性に spring を指定する。

@Mapper(componentModel = "spring")
public interface SpringMapper {
    Person toPerson(PersonEntity entity);
}

これでMapperを生成すると @Component が付与されたMapperが生成される。

import org.springframework.stereotype.Component;

/*
@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2018-03-07T22:13:08+0900",
    comments = "version: 1.2.0.Final, compiler: javac, environment: Java 9.0.1 (Oracle Corporation)"
)
*/
@Component
public class SpringMapperImpl implements SpringMapper {

    @Override
    public Person toPerson(PersonEntity entity) {
        if ( entity == null ) {
            return null;
        }

        Person person = new Person();

        person.setName( entity.getName() );
        person.setAge( entity.getAge() );

        return person;
    }
}

mapstruct.defaultComponentModelオプションを使う

@Mapperコンポーネントモデルを毎回指定するのは面倒なので、注釈処理にmapstruct.defaultComponentModelオプションを指定して一律設定することができる。 今回はGradleを使った場合。

...
compileJava {
    options.compilerArgs = [
        '-Amapstruct.defaultComponentModel=spring'
    ]
}
...

@MapperConfig を使う

MapperConfigクラスを使ってコンポーネントモデルを指定することもできる。

@MapperConfig(componentModel = "spring")
public interface SpringConfig {
}

作成したMapperConfigクラスをMapperに指定する。

@Mapper(config = SpringConfig.class)
public interface SpringMapper {
    Person toPerson(PersonEntity entity);
}

【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を持っていないので変わらない
}

Haskell リスト

定義

Haskellのリストは要素をカンマ区切りで並べ大括弧で囲む。
同じ型の要素しか格納できない。

haskell> [1, 2, 3]
[1,2,3]

ネスト

リストの中にリストを定義することができる。

haskell> [[1, 2], [3]]
[[1,2],[3]]

次元は揃える必要がある。エラーとなる。

haskell> [[1, 2], [3], 4]

<interactive>:179:1: error:
    • Non type-variable argument in the constraint: Num [a]
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        it :: forall a. (Num [a], Num a) => [[a]]

rangeを使用した生成

haskell> [1..10]
[1,2,3,4,5,6,7,8,9,10]
haskell> ['a'..'z']
"abcdefghijklmnopqrstuvwxyz"

ステップを指定すると2の倍数のみなどを生成できる。

haskell> [2,4..20]
[2,4,6,8,10,12,14,16,18,20]
haskell> [3,6..20]
[3,6,9,12,15,18]

減少するリストも生成が可能。

haskell> [20,18..2]
[20,18,16,14,12,10,8,6,4,2]

結合

++演算子 で結合できる。

haskell> [1, 2, 3] ++ [4, 5, 6]
[1,2,3,4,5,6]
haskell> ['a', 'b'] ++ ['c']
"abc"

先頭に追加

:演算子*1 で追加できる。

haskell> 0 : [1, 2, 3]
[0,1,2,3]

要素へのアクセス

要素番号指定

!!演算子 で要素番号を指定してアクセスできる。

haskell> [1, 2, 3] !! 0
1
haskell> [1, 2, 3] !! 2
3

先頭の要素

head関数 を使用する。

haskell> head [1, 2, 3]
1

末尾の要素

tail関数 を使用する。

haskell> last [1, 2, 3]
3

先頭以外の要素

tail関数 を使用する。

haskell> tail [1, 2, 3]
[2,3]

末尾以外の要素

init関数 を使用する。

haskell> init [1, 2, 3]
[1,2]

先頭から指定した数の要素

take関数 を使用する。

haskell> take 2 [1, 2, 3]
[1,2]
haskell> take 1 [1, 2, 3]
[1]
haskell> take 0 [1, 2, 3]
[]

先頭から指定した数の要素を除外した要素

drop関数 を使用する。

haskell> drop 1 [1, 2, 3]
[2,3]
haskell> drop 0 [1, 2, 3]
[1,2,3]
haskell> drop 3 [1, 2, 3]
[]

比較

< > <= >= == を使用して辞書順で比較する。
要素ごとに比較し、条件に一致した時点で比較が決まる。
素数が合わない場合も比較できる。

haskell> [1, 2, 3] < [2, 3, 4]
True
haskell> [9] > [1, 3]
True

Javaには無い機能。使いどころが不明。

長さ

length関数 を使用する。

haskell> length [1, 2, 3]
3

空判定

null関数 を使用する。

haskell> null []
True
haskell> null [1, 2, 3]
False

最大値

maximum関数 を使用する。

haskell> maximum [5, 2, 1, 6, 9]
9

最小値

minimum関数 を使用する。

haskell> minimum [5, 2, 1, 6, 9]
1

要素の和

sum関数 を使用する。

haskell> sum [1, 2, 3]
6

要素の積

product関数 を使用する。

haskell> product [1, 2, 3]
6
haskell> product [1, 2, 3, 4]
24

要素の存在確認

elem関数 を使用する。

haskell> elem 2 [1, 2, 3]
True
haskell> elem 5 [1, 2, 3]
False

*1:cons演算子とも呼ばれる