◀ 9.

【Spring】@Dataとは

▶
この記事の要点
  • @Data は Spring 本体ではなく、Lombok(ロンボック)というライブラリのアノテーション。Spring Boot の開発でよく併用される
  • @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor をまとめて付けたのと同じ
  • getter / setter などの定型コードをコンパイル時に自動生成し、DTO やフォームクラスを短く書ける
  • JPA のエンティティに付けると、equals / hashCode / toString が関連エンティティをたどって無限ループや遅延読み込みエラーを起こすことがある
  • エンティティには @Getter / @Setter など必要なものだけを個別に付けるのが無難
  • 使うには依存関係の追加と、IDE の Lombok 対応(アノテーション処理)が必要

本稿は Spring Framework(Spring Boot)の開発でよく使われる @Data について説明します。

@Data とは

@Data は Lombok のアノテーションで、以下のアノテーションをすべて付けることと同義です。

アノテーション生成されるもの
@Getterすべてのフィールドの getter(getName()、boolean なら isActive())
@Setterfinal でないすべてのフィールドの setter
@ToString全フィールドを含む toString()
@EqualsAndHashCode全フィールド(static と transient を除く)を比較する equals() と hashCode()
@RequiredArgsConstructorfinal フィールドと @NonNull フィールドを引数に取るコンストラクタ

Lombok はコンパイル時にアノテーションを読み取り、これらのメソッドをクラスファイルに直接追加します。ソースコード上には現れませんが、普通に customer.getCustomerId() のように呼び出せます。

使用例

以下、JPA のエンティティに付けたサンプルです(Spring Boot 3 以降は javax.persistence ではなく jakarta.persistence を使います)。

package com.sample.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import lombok.Data;

@Data
@Entity
@Table(name="CUSTOMER")
public class Customer {

    @Id
    @Column
    private String customer_id;

    ....
}

@Data を付けない場合、同じことをするにはフィールドごとの getter / setter に加えて、toString、equals、hashCode を手で書く必要があり、フィールドが 10 個あれば 100 行を超えることも珍しくありません。

DTO(画面やAPI とのデータの受け渡し用クラス)であれば、@Data は特に便利です。

@Data
public class CustomerForm {
    private String name;
    private String email;
    private Integer age;
}

CustomerForm form = new CustomerForm();
form.setName("山田");
System.out.println(form);   // CustomerForm(name=山田, email=null, age=null)

導入方法

Spring Initializr でプロジェクトを作る場合は、依存関係で「Lombok」を選ぶだけです。既存のプロジェクトに追加する場合は次のように書きます。Spring Boot を使っていればバージョンは Spring Boot 側で管理されるので省略できます。

<!-- Maven (pom.xml) -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

// Gradle (build.gradle)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

IDE 側の対応も必要です。IntelliJ IDEA は Lombok プラグインが標準で同梱されており、「Annotation Processing」を有効にします。Eclipse / Spring Tool Suite は Lombok の jar を実行してインストーラーで IDE に組み込みます。IDE が対応していないと、コンパイルは通るのにエディタ上で「getter が無い」というエラー表示が出ます。

注意点・落とし穴

JPA エンティティに @Data を付ける問題

@Data は手軽ですが、JPA(Hibernate)のエンティティに付けると次の問題が起きることがあります。

  • 双方向の関連で StackOverflowError: 親が子のリスト、子が親を持つ関係で、toString や hashCode が互いを呼び合い無限に再帰する
  • LazyInitializationException: toString や equals が遅延読み込みの関連フィールドにアクセスし、トランザクション外だと例外になる。また、ログ出力しただけで想定外の SQL が大量に発行されることもある
  • equals / hashCode が変わる: 全フィールドで比較するため、保存前後で ID が変わったり値を更新したりすると、HashSet に入れたエンティティが見つからなくなる

エンティティでは、@Data の代わりに必要なものだけを付け、関連フィールドを toString から除外するのが一般的です。

@Entity
@Getter
@Setter
@NoArgsConstructor
@ToString(exclude = "orders")
public class Customer {
    @Id
    private Long id;
    private String name;

    @OneToMany(mappedBy = "customer")
    private List<Order> orders;
}

その他の注意点

  • コンストラクタ: @RequiredArgsConstructor は final フィールドが無ければ引数なしのコンストラクタになります。final フィールドがあると引数なしコンストラクタが無くなり、JPA や JSON ライブラリが必要とするデフォルトコンストラクタが不足することがあります。その場合は @NoArgsConstructor を併用します。また、自分でコンストラクタを書いたクラスでは @Data によるコンストラクタは生成されません
  • 継承: 親クラスを持つクラスでは、生成される equals / hashCode に親のフィールドが含まれません。必要なら @EqualsAndHashCode(callSuper = true) を指定します
  • 不変オブジェクト: setter を作らない読み取り専用のクラスには @Value を使います。Java 16 以降なら、単純なデータの入れ物は record で書くこともできます
  • パスワードなどの機密項目: toString に含まれるとログに出力されてしまうため、@ToString.Exclude を付けて除外します

生成内容の確認方法

Lombok が実際にどんなコードを生成したかは、IDE のアウトライン(構造)表示でメソッド一覧を見るか、delombok ツールで展開後のソースを出力すると確認できます。コンパイル後のクラスに対して javap -p Customer.class を実行してメソッド一覧を見る方法もあります。

関連

Post Share
子ページ

子ページはありません

同階層のページ
  1. @After
  2. @Autowired
  3. @Bean
  4. @Before
  5. @Column
  6. @Component
  7. @Configuration
  8. @Controller
  9. @Data
  10. @Entity
  11. @GeneratedValue
  12. @Id
  13. @Modifying
  14. @PathVariable
  15. @PropertySource
  16. @Repository
  17. @RequestBody
  18. @RequestMapping
  19. @ResponseBody
  20. @RestController
  21. @Service
  22. @SpringBootApplication
  23. @Table
  24. @Transactional
  25. @Value