markdown

はじめに (対象読者・この記事でわかること)

この記事は、JavaでJPAを始めたばかりの初学者~中級者向けです。
「ManyToOneのアノテーションだけで関連先のsetterが使えるはず!」と記事を鵜呑みにして実装してみたら、コンパイルエラー(Unresolved reference)に遭遇した方に贈ります。
この記事を読むことで、

  • Lombokなし/ありで@Entityクラスにフィールドを追加したときの挙動の違い
  • IDEが補完してくれる「幻想のsetter」と実際に存在するsetterの違い
  • 正しいManyToOneマッピング+αの記述パターン

が一度に理解できます。

前提知識

  • Javaの基本的な文法(クラス・フィールド・メソッド)
  • JPAがORMであることくらいは知っている
  • IDE(IntelliJ IDEA / Eclipse / VS Code)で補完機能を使ったことがある

事象の整理:「setterがあるように見えるのにコンパイルできない」

検索すると以下のようなサンプルが大量にヒットします。

Java
@Entity public class Order { @ManyToOne private Customer customer; // これだけ書けばsetCustomer()が使えるはず!? }

ブログやQiitaのコード抜粋では、上記のクラスに対して

Java
Order order = new Order(); order.setCustomer(customer); // 補完が効いているように見える

と書かれています。
しかし、自分の手元でコンパイルすると

Unresolved reference: setCustomer

が出てしまいます。一体なぜでしょうか?

原因:Lombokの有無とIDEの補完が織りなす「幻想」

ステップ1:Lombokなしで@Entityを書いてみる

まず、Lombokを使っていないプロジェクトで以下のように書いてください。

Java
import jakarta.persistence.*; @Entity @Table(name = "orders") public class Order { @Id @GeneratedValue private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "customer_id") private Customer customer; // フィールドのみ定義 }

この時点でOrderクラスにsetCustomer()は存在しません。
Javaの仕様上、フィールドを書いただけではsetterは自動生成されないからです。

ステップ2:IDEが補完してくれる「幻影」を見極める

IntelliJ IDEAを使っていると、以下の2つの補完が混在します。

  1. 実際に存在するメソッド(自作したsetter)
  2. フィールド名を元にした「疑似補完」(存在しないメソッド)

補完候補に出てきても、実際に存在しないメソッドを呼ぶとコンパイルエラーになります。
「あれ?ブログでは動いていたのに…」と思うのはこのタイミングです。

ステップ3:Lombokを使った正しい記述パターン

Lombokを使う場合、クラスレベルでアノテーションを付けることでsetter/getterが生成されます。

Java
import jakarta.persistence.*; import lombok.*; @Entity @Table(name = "orders") @Getter // getter生成 @Setter // setter生成 @NoArgsConstructor // 引数なしコンストラクタ @AllArgsConstructor // 全フィールドコンストラクタ public class Order { @Id @GeneratedValue private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "customer_id") private Customer customer; // setter/getterが生成される! }

これでorder.setCustomer(customer)がコンパイル通るようになります。

ハマったポイント

  • Lombokを使っていないのに「setterがあるように見える」記事を鵜呑みにしてしまう
  • IDEの補完が「実在メソッド」と「疑似補完」を区別なく表示してしまう
  • JPAの仕様(フィールドだけでマッピングできる)とLombokの仕様(アノテーションでコード生成)を混同している

解決策

  1. プロジェクトにLombokが入っているかbuild.gradle/pom.xmlで確認する
  2. 入っていなければ@Getter/@Setterを付けてビルドし直す
  3. 入れたくない場合は、自分でsetter/getterを書く
Java
public void setCustomer(Customer customer) { this.customer = customer; } public Customer getCustomer() { return customer; }

まとめ

本記事では、JPAのManyToOneで「setterが自動で生えるように見えるのにコンパイルエラーになる」現象の真相を紐解きました。

  • Java言語仕様上、フィールドを書いただけではsetterは生えない
  • Lombokの@Setterがない限り、IDE補完は「幻想」
  • 正しくは@Entityクラスに@Getter/@Setterを付けるか、手動でsetterを書く

この記事を通して、JPAのエンティティ定義で「なぜ動かないのか」を自分で切り分けられるようになりました。
次回は、双方向参照(bidirectional)でmappedByを使ったときの無限ループ対策について掘り下げます。

参考資料