Java bean mappingでプチ感動&プチ試行錯誤

はじめまして。酒井です。

現在、Javaでの開発プロジェクトに参加しています。
Java自体はスマホアプリ開発でも使っていたので馴染みはあるのですが、今回はWebサービスということで、色々と初めてで勝手が分からないことだらけで、チームにフォローしてもらいながら作業を進めています。

今回は、開発中に出会ったBeanマッパーを紹介します。

Beanマッピングとは、ヒトコトで言うと、BeanからBeanへのフィールドコピーです。
こちらの解説が簡潔で分かりやすかったです。

現プロジェクトの担当箇所にて、ある一連の処理で、中間のオブジェクトから出力に使うオブジェクトへ値をコピーする必要があり、「こんなの手で書くもんじゃないだろう」と思って、メンバーに聞いたりググったりしてみました。

MapStruct

まず試してみたのはMapStruct

リフレクションではなくコード生成するタイプのもので、高速に動作するのがウリ。

ClassAからClassBへのマッピングを行う場合、以下のようなinterfaceを定義します。

1
2
3
4
5
6
7
@Mapper
public interface AMapper {

AMapper INSTANCE = Mappers.getMapper(AMapper.class);

ClassB toB(ClassA a);
}
  • interface名および変換メソッド名は自由です。
  • INSTANCEはマッピングを行う際に利用します。

マッピングする方のコードは以下のようになります。

1
2
3
ClassA a = new ClassA();
// aに色々と設定
ClassB b = AMapper.INSTANCE.toB(a);

双方同じ名前のフィールド/プロパティ(xxxxというフィールドに対してsetXxxx()getXxxx()があるもの)でのコピーであれば、これだけで実現できます。AMapperの実装を書く必要もありません。素晴らしい。

ところが。

残念ながらMapStructは採用しませんでした。何故かと言うと・・・僕が怠惰だったからです。

ClassAが以下のようなフィールドを持っていて、

1
2
3
4
public class ClassA {
private int field1;
private int field2;
}

各フィールドに対するsetter/getterを書きたくなったのです。
Eclipseで自動生成してくれますが、そういうことじゃないんです。分かりますよね?(笑)

プロジェクトで既に利用されていたLombokを使うと、

1
2
3
4
5
@Data
public class ClassA {
private int field1;
private int field2;
}

アノテーション1つで解決です。こうでないと。

で、MapStructはLombokと相性が悪かったのです。LombokはJavaコンパイラに介入してコードを追加したりするのですが、MapStructがコードを生成するのはそれより前であるため、上記のクラス定義のまま、privateフィールドでsetterがない、という状況なので、エラーになってしまいました。

MapStructを使うために手でsetter/getterを書くか、それを避けるためにMapStructを諦めるか。[※]

MapStruct諦めました。

Orika

MapStructを諦めて、Orikaを試すことにしました。

“Java Bean mapper”でググると、Dozerというヤツの方が先に出てくるのですが、色々なところで速度比較が行われていて、遅いらしいということが分かったので、そこで比較対象として書かれていたOrikaに決めました。

Orikaでは、ClassAClassBの例のような双方同じ名前のフィールドでのコピーであれば、MapStructのように事前定義が必要なものはありません。

マッピングを行う側では、まずMapperFactoryのインスタンスを取得します。

1
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

上記は毎回行う必要はなく、シングルトンインスタンスとして保持しておきます。
プロジェクトではSpringを使っているので、DIで対応しました。

マッピングは以下のようにします。

1
2
3
4
5
MapperFacade mapper = mapperFactory.getMapperFacade();

ClassA a = new ClassA();
// aに色々と設定
ClassB b = mapper.map(a, ClassB.class);

マッピング元と先が決まっている場合、さらに効率のよいBoundMapperFacadeを利用できます。

1
2
3
4
5
BoundMapperFacade<ClassA, ClassB> mapper = mapperFactory.getMapperFacade(ClassA.class, ClassB.class);

ClassA a = new ClassA();
// aに色々と設定
ClassB b = mapper.map(a);

Javaでバリバリ開発してきた方には当たり前は話かも知れないのですが、長年関わってきた組み込み開発ではこれが必要な状況にならず、今まで出会わずに来たので、ちょっと感動。

以上です。


(2015-11-12追記)
両方を使う例はあるのですが、プロジェクトを分ける必要があります。