私はこれまでしばらく JPA (Hibernate 実装) を使ってきましたが、エンティティを作成するたびに、AccessType、不変プロパティ、equals/hashCode などの問題に悩まされています。
そこで、各問題に対する一般的なベスト プラクティスを見つけて、個人使用のために書き留めることにしました。
ただし、どなたかコメントしていただいたり、間違っている点を指摘していただいたりしても構いません。
エンティティクラス
シリアル化を実装する
理由:仕様では必須とされていますが、一部の JPA プロバイダーはこれを強制しません。JPA プロバイダーとしての Hibernate はこれを強制しませんが、Serializable が実装されていない場合は、内部のどこかで ClassCastException が発生して失敗する可能性があります。
コンストラクター
エンティティのすべての必須フィールドを持つコンストラクタを作成する
理由: コンストラクターは、作成されたインスタンスを常に正常な状態のままにしておく必要があります。
このコンストラクタの他に、パッケージプライベートデフォルトコンストラクタがある
理由: Hibernate でエンティティを初期化するには、デフォルトのコンストラクターが必要です。プライベートは許可されますが、ランタイム プロキシの生成と、バイトコード インストルメンテーションなしでの効率的なデータ取得には、パッケージのプライベート (またはパブリック) 可視性が必要です。
フィールド/プロパティ
通常はフィールドアクセスを使用し、必要に応じてプロパティアクセスを使用する
理由: これはおそらく最も議論の余地のある問題です。どちらか一方 (プロパティ アクセス vs フィールド アクセス) について明確で説得力のある議論がないためです。ただし、フィールド アクセスは、コードが明確で、カプセル化が優れており、不変フィールドのセッターを作成する必要がないため、一般的に好まれているようです。
不変フィールドのセッターを省略します(アクセスタイプフィールドには必要ありません)
- プロパティはプライベートにすることができます。
理由: 以前、(Hibernate の) パフォーマンスには protected のほうが優れていると聞いたことがありますが、Web で見つけた情報は次のとおりです: Hibernate は、public、private、protected のアクセサ メソッドにアクセスできるほか、public、private、protected のフィールドに直接アクセスできます。選択はユーザー次第で、アプリケーション設計に合わせて調整できます。
等しい/ハッシュコード
- このIDがエンティティを永続化するときにのみ設定されている場合、生成されたIDは使用しないでください。
- 優先: 不変の値を使用して一意のビジネスキーを形成し、これを使用して等価性をテストします。
- 一意のビジネスキーが利用できない場合は、エンティティが初期化されるときに作成される非一時的なUUIDを使用します。この素晴らしい記事詳細については。
- 関連するエンティティ(ManyToOne)を参照しないでください。このエンティティ(親エンティティなど)がビジネスキーの一部である必要がある場合は、IDのみを比較します。プロキシでgetId()を呼び出しても、エンティティの読み込みはトリガーされません。プロパティアクセスタイプ。
エンティティの例
@Entity
@Table(name = "ROOM")
public class Room implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = "room_id")
private Integer id;
@Column(name = "number")
private String number; //immutable
@Column(name = "capacity")
private Integer capacity;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "building_id")
private Building building; //immutable
Room() {
// default constructor
}
public Room(Building building, String number) {
// constructor with required field
notNull(building, "Method called with null parameter (application)");
notNull(number, "Method called with null parameter (name)");
this.building = building;
this.number = number;
}
@Override
public boolean equals(final Object otherObj) {
if ((otherObj == null) || !(otherObj instanceof Room)) {
return false;
}
// a room can be uniquely identified by it's number and the building it belongs to; normally I would use a UUID in any case but this is just to illustrate the usage of getId()
final Room other = (Room) otherObj;
return new EqualsBuilder().append(getNumber(), other.getNumber())
.append(getBuilding().getId(), other.getBuilding().getId())
.isEquals();
//this assumes that Building.id is annotated with @Access(value = AccessType.PROPERTY)
}
public Building getBuilding() {
return building;
}
public Integer getId() {
return id;
}
public String getNumber() {
return number;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getNumber()).append(getBuilding().getId()).toHashCode();
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}
このリストに追加するその他の提案も大歓迎です...
アップデート
読んで以来この記事私は eq/hC の実装方法を次のように変更しました。
- 不変のシンプルなビジネスキーが利用可能な場合:それを使用する
- それ以外の場合: uuidを使用する
ベストアンサー1
のJPA 2.0 仕様次のように述べています。
- エンティティ クラスには、引数なしのコンストラクターが必要です。他のコンストラクターも存在する場合があります。引数なしのコンストラクターは、public または protected である必要があります。
- エンティティ クラスは最上位クラスである必要があります。列挙型またはインターフェイスをエンティティとして指定することはできません。
- エンティティ クラスは final であってはなりません。エンティティ クラスのメソッドまたは永続インスタンス変数は final であってはなりません。
- エンティティ インスタンスが分離されたオブジェクトとして値によって渡される場合(たとえば、リモート インターフェイス経由)、エンティティ クラスは Serializable インターフェイスを実装する必要があります。
- 抽象クラスと具象クラスの両方がエンティティになることができます。エンティティは、エンティティ クラスだけでなく非エンティティ クラスも拡張することができ、非エンティティ クラスはエンティティ クラスを拡張することができます。
仕様には、エンティティの equals メソッドと hashCode メソッドの実装に関する要件は含まれておらず、私の知る限り、主キー クラスとマップ キーに関する要件のみが含まれています。