私はしばらくの間JPA(実装Hibernate)を使用しており、エンティティを作成する必要があるたびに、AccessType、不変のプロパティ、equals / hashCodeなどの問題に悩まされています。
そこで、私は各問題の一般的なベストプラクティスを見つけ出して、個人用に書き留めることにしました。
しかし、誰かがそれについてコメントしたり、どこが間違っているかを教えたりしても構わないと思います。
エンティティークラス
Serializableを実装する
理由:仕様では、そうする必要があると記載されていますが、一部のJPAプロバイダーはこれを強制していません。JPAプロバイダーとしてのHibernateはこれを強制しませんが、Serializableが実装されていない場合は、ClassCastExceptionで胃のどこかで失敗する可能性があります。
コンストラクタ
エンティティのすべての必須フィールドを持つコンストラクタを作成します
理由:コンストラクターは、作成されたインスタンスを常に正常な状態にしておく必要があります。
このコンストラクターのほかに、パッケージのプライベートデフォルトコンストラクターがある
理由:Hibernateでエンティティを初期化するには、デフォルトのコンストラクターが必要です。プライベートは許可されますが、ランタイムプロキシの生成とバイトコードインスツルメンテーションなしの効率的なデータ取得には、パッケージプライベート(またはパブリック)の可視性が必要です。
フィールド/プロパティ
一般的にフィールドアクセスを使用し、必要に応じてプロパティアクセスを使用する
理由:どちらか一方について明確で説得力のある引数がないため、これはおそらく最も議論の余地のある問題です(プロパティアクセスとフィールドアクセス)。ただし、フィールドアクセスは、より明確なコード、より良いカプセル化、および不変フィールドのセッターを作成する必要がないため、一般的に好まれているようです
不変フィールドのセッターを省略(アクセスタイプフィールドには不要)
- プロパティはプライベートである可能性があります
理由:以前は(Hibernate)パフォーマンスの方がprotectedの方が優れていると聞きましたが、Webで見つけることができるのは:Hibernateがpublic、private、protectedのアクセサーメソッド、およびpublic、private、protectedフィールドに直接アクセスできることです。選択はあなた次第であり、アプリケーション設計に合うようにそれを一致させることができます。
Equals / hashCode
- エンティティを永続化するときにのみこの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を使用します
final
(セッターの省略率から判断すると、あなたもそうだと思います)。
notNull
から来たの?