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のコード抜粋では、上記のクラスに対して
JavaOrder order = new Order(); order.setCustomer(customer); // 補完が効いているように見える
と書かれています。
しかし、自分の手元でコンパイルすると
Unresolved reference: setCustomer
が出てしまいます。一体なぜでしょうか?
原因:Lombokの有無とIDEの補完が織りなす「幻想」
ステップ1:Lombokなしで@Entityを書いてみる
まず、Lombokを使っていないプロジェクトで以下のように書いてください。
Javaimport 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つの補完が混在します。
- 実際に存在するメソッド(自作したsetter)
- フィールド名を元にした「疑似補完」(存在しないメソッド)
補完候補に出てきても、実際に存在しないメソッドを呼ぶとコンパイルエラーになります。
「あれ?ブログでは動いていたのに…」と思うのはこのタイミングです。
ステップ3:Lombokを使った正しい記述パターン
Lombokを使う場合、クラスレベルでアノテーションを付けることでsetter/getterが生成されます。
Javaimport 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の仕様(アノテーションでコード生成)を混同している
解決策
- プロジェクトにLombokが入っているか
build.gradle/pom.xmlで確認する - 入っていなければ
@Getter/@Setterを付けてビルドし直す - 入れたくない場合は、自分でsetter/getterを書く
Javapublic 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を使ったときの無限ループ対策について掘り下げます。
参考資料
- JPA 2.2 Specification
- Project Lombok – Getter/Setter
- IntelliJ IDEA Help – Code Completion
