protectedの空コンストラクタは、なぜJPAに必要なのか
JPAのエンティティには、引数のないコンストラクタが必要です。
@Entity
public class User {
@Id
private Long id;
private String name;
protected User() {}
public User(String name) {
this.name = name;
}
}
この空のコンストラクタを使うのは、アプリケーションではなくJPAプロバイダーです。データベースからエンティティを取得するとき、JPAプロバイダーは概念的に次の処理を行います。
- 空のコンストラクタでインスタンスを生成する
- データベースから取得した値をフィールドへ設定する
- 完成したエンティティをアプリケーションへ返す
つまり、引数のあるコンストラクタはアプリケーションが新しいエンティティを作るため、空のコンストラクタはJPAがエンティティを復元するために使います。
また、Javaは別のコンストラクタが定義されていると、空のコンストラクタを自動生成しません。そのため、JPA用の空のコンストラクタを明示的に定義する必要があります。
実際に削除してみる
空のコンストラクタを削除してみます。
@Entity
public class User {
@Id
private Long id;
private String name;
public User(String name) {
this.name = name;
}
}
このコードはコンパイルできます。しかし、JPAでデータベースからUserを取得しようとすると、引数のないコンストラクタが存在しないためエラーになります。
No default constructor for entity
Userには引数のあるコンストラクタが定義されているため、Javaは空のコンストラクタを自動生成しません。その結果、JPAプロバイダーがUserのインスタンスを生成できなくなります。
空のコンストラクタを戻すと、再びエンティティを取得できるようになります。
protected User() {}
なお、エラーの内容や発生するタイミングは、使用するJPAプロバイダーによって異なる場合があります。
なぜpublicなどではなくprotectedなのか
JPAの仕様では、エンティティの空のコンストラクタをpublicまたはprotectedにする必要があります。
publicでも動作しますが、アプリケーションのどこからでも呼び出せるため、値を持たない不完全なエンティティを作れてしまいます。
User user = new User();
一方、privateにすると、エンティティを継承したクラスから空のコンストラクタを呼び出せません。
@Entity
public class User {
private User() {}
}
@Entity
public class AdminUser extends User {
protected AdminUser() {} // 暗黙のsuper()を呼べずコンパイルエラー
}
これは、空のコンストラクタを書かなかった場合にJPAプロバイダーが出す実行時エラーとは別の問題です。privateによるエラーは、サブクラスからスーパークラスのコンストラクタへアクセスできないために発生します。
protectedなら、通常のアプリケーションからの利用を制限しつつ、サブクラスとJPAプロバイダーから利用できます。
protected User() {}
つまり、protectedは、JPA仕様に準拠しながら呼び出せる範囲を狭め、エンティティの継承にも対応できる指定です。
まとめ
JPAエンティティの空のコンストラクタをprotectedにすることで、JPA仕様を満たしながらアプリケーションからの利用を制限できます。