【Spring】BeanUtils.copyPropertiesの落とし穴


BeanUtils.copyProperties

SpringにはBeanUtils.copyPropertiesという便利なメソッドが用意されている。
一方のBeanからもう一方のBeanへ、同じ名前のフィールドの中身をコピーしてくれるメソッドだ。第一引数がコピー元、第二引数がコピー先になる。
クラスが別々だったとしても、同じフィールド名があればコピーは可能である。

Book クラス

public class Book {

    String code;

    String name;

    int price;
}

使用例


Book book1 = new Book();

book1.setCode("A");
book1.setName("三国志");
book1.setPrice(500);

Book book2 = new Book();

BeanUtils.copyProperties(book1, book2);

こうすることで、book2にはbook1codenamepriceの値が丸々コピーされる。

落とし穴1: 異なるクラスのオブジェクトにコピーする場合にフィールド名が変わると死ぬ

例えば以下のFormとDtoがあるとしよう。

CreateBookForm クラス

public class CreateBookForm {

    String code;

    String name;

    int price;
}

BookDto クラス

public class BookDto {

    String code;

    String name;

    int price;

    String updateModule;

}

Form -> Dtoのマッピングに無邪気にBeanUtils.copyProperties(createBookForm, bookDto)などと書くと、死亡率が高まる。

フロントエンドの都合でCreateBookFormのフィールド名がcodecdと変わったとする。

CreateBookForm クラス(変更後)

public class CreateBookForm {

    String cd;

    String name;

    int price;
}

もうこれでcd -> codeのコピーは実施されない。フィールド名が異なるからだ。
じゃあBookDtoのフィールド名も変えればいいじゃん!って意見もあるかもだが、レイヤー分離のために用意しているクラスで変更が影響し合うのはイケてない。

このメソッドを使う場合、以下の原則を守るべきだと自分は考える。

  • 異なるクラスのオブジェクト同士ではコピーさせない!フィールド名が永久に変更されないという保証はどこにもない!ただし継承関係のあるクラスは除く。
  • どうしても異なるクラスのオブジェクト同士でコピーさせる必要がある場合は、コピーされることを確認する単体テストを書く!

落とし穴2: 同姓同名のメソッドが別のライブラリに存在する

このメソッド、全く同じ名前のものがApache Commonsライブラリに存在するのである。
しかもこちらのメソッド、第二引数がコピー元、第一引数がコピー先となっている。Springの同名メソッドと引数の順番が逆なのである!!
IDEでライブラリ自動インポートを使っていると、あるクラスではSpringの、あるクラスではApacheのcopyPropertiesを呼んでしまう場合があり、気づかずハマることがあるので注意。
自分はハマった。

個人的には「コピー元 -> コピー先」で「左から右へ = 第一引数から第二引数へ」の印象が強いので、Springのものを常に使うようにしている。