java - アノテーション - jpa 継承マッピング
完全なJPAエンティティを作成する (3)
私はJPA(Hibernateの実装)を使っていましたが、エンティティを作成する必要があるたびに、AccessType、不変プロパティ、equals / hashCodeなどの問題に苦しんでいます。
そこで私は、各問題の一般的なベストプラクティスを見つけ出し、個人的な使用のためにこれを書き留めることに決めました。
私はそれについて誰かがコメントしたり、どこに間違っているか教えてもらえません。
エンティティクラス
Serializableを実装する
理由: 仕様には必要があると言われていますが、一部のJPAプロバイダはこれを強制しません。 JPAプロバイダとしてのHibernateはこれを強制しませんが、Serializableが実装されていない場合、ClassCastExceptionを使用して胃のどこかで失敗する可能性があります。
コンストラクタ
エンティティのすべての必須フィールドを含むコンストラクタを作成する
理由:コンストラクタは、作成されたインスタンスを常に元の状態のままにしておく必要があります。
このコンストラクタの他に、パッケージをプライベートなデフォルトのコンストラクタにする
理由:デフォルトのコンストラクタは、Hibernateにエンティティを初期化させる必要があります。 非公開は許可されていますが、実行時プロキシの生成とバイトコード計測なしの効率的なデータ取得には、パッケージのプライベート(またはパブリック)可視性が必要です。
フィールド/プロパティ
必要に応じて、一般的なフィールドアクセスとプロパティアクセスを使用する
理由:これはおそらく最も議論の余地がある問題です。なぜなら、どちらか一方のための明確で説得力のある議論がないからです(プロパティアクセスとフィールドアクセス)。 しかし、コードが明確になり、カプセル化が改善され、不変フィールド用のセッターを作成する必要がないため、フィールドアクセスは一般的に好まれるようです
不変フィールドのセッターを省略する(アクセスタイプフィールドには不要)
- プロパティは非公開にすることができる
理由:私は一度、protected(Hibernate)のパフォーマンスが良いと聞きましたが、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を使用する
JPA 2.0仕様では次のように述べています。
- エンティティクラスには、引数なしのコンストラクタが必要です。 それには他のコンストラクタもあるかもしれません。 引数なしのコンストラクタはpublicまたはprotectedでなければなりません。
- エンティティクラスはトップレベルクラスでなければなりません。 列挙型またはインタフェースをエンティティとして指定することはできません。
- エンティティクラスはfinalであってはなりません。 エンティティクラスのメソッドまたは永続的なインスタンス変数は、最終的なものではありません。
- エンティティインスタンスが分離されたオブジェクト (例えば、リモートインタフェースを介して) として値によって渡される場合 、エンティティクラスはSerializableインタフェースを実装する必要があります。
- 抽象クラスと具象クラスの両方をエンティティにすることができます。 エンティティは、非エンティティクラスおよびエンティティクラスを拡張することができ、非エンティティクラスはエンティティクラスを拡張することができる。
この仕様には、エンティティのequalsおよびhashCodeメソッドの実装に関する要件はありません。主キークラスとマップキーについては、私が知る限り、仕様には含まれていません。
いくつかの重要な点に答えようとします:これは、いくつかの主要なアプリケーションを含む長いHibernate / persistenceエクスペリエンスからのものです。
エンティティクラス:Serializableを実装しますか?
キーはSerializableを実装する必要があります。 HttpSessionに入ったり、RPC / Java EEによってワイヤを介して送信されるものは、Serializableを実装する必要があります。 他のもの:そうではありません。 重要なことに時間を費やしてください。
コンストラクタ:エンティティのすべての必須フィールドを含むコンストラクタを作成しますか?
アプリケーションロジックのコンストラクターには、エンティティの作成時に常にわかっている重要な「外部キー」または「タイプ/種類」のフィールドがいくつかあります。 残りの部分はsetterメソッドを呼び出すことで設定する必要があります。
あまりにも多くのフィールドをコンストラクタに配置しないでください。 コンストラクターは便利で、オブジェクトに基本的な健全性を与えるべきです。 名前、タイプ、および/または親はすべて通常は有用です。
OTOHの場合、アプリケーションルール(現時点)で顧客に住所が必要な場合は、それを設定者に任せます。 それは「弱いルール」の一例です。 来週は、「詳細を入力」画面に行く前にCustomerオブジェクトを作成したいのですか? 不自然な、または不完全な、または「部分的に入力された」データの可能性を残してください。
コンストラクタ:また、プライベートのデフォルトのコンストラクタをパッケージ化しますか?
はい、パッケージプライベートではなく、 '保護された'を使用してください。 必要な内部構造が表示されていないときに、物をサブクラス化することは本当の苦痛です。
フィールド/プロパティ
Hibernateの場合、およびインスタンスの外部から 'property'フィールドへのアクセスを使用します。 インスタンス内では、フィールドを直接使用します。 理由:Hibernateの最も簡単で最も基本的な方法である標準的なリフレクションを動作させることができます。
アプリケーションへの '不変'のフィールドに関しては、Hibernateはまだそれらをロードできる必要があります。 これらのメソッドを「プライベート」にしたり、アノテーションを付けたりして、アプリケーションコードが不要なアクセスをしないようにすることができます。
注意:equals()関数を書くときは、 'other'インスタンスの値にgetterを使用してください! それ以外の場合は、プロキシインスタンスの未初期化/空のフィールドにヒットします。
保護された(休止状態)パフォーマンスが良いですか?
ありそうもない。
イコール/ハッシュコード?
エンティティを保存する前にエンティティを操作することは関連しています。これは難題です。 不変の値に対するハッシュ/比較? ほとんどのビジネスアプリケーションには、何もありません。
顧客はアドレスを変更したり、ビジネス名などを変更したりすることはできませんが、一般的ではありませんが、起こります。 データが正しく入力されなかった場合は、訂正も可能である必要があります。
通常は不変に保たれているいくつかの事柄はParentingであり、おそらくType / Kindです - 通常、ユーザーはこれらを変更するのではなく、レコードを再作成します。 しかし、これらはエンティティを一意に識別しません!
だから、長いと短い、主張された "不変の"データは実際にはありません。 一次キー/ IDフィールドは、そのような保証された安定性と不変性を提供する正確な目的のために生成される。
A)「頻繁に変更されたフィールド」を比較/ハッシュした場合、UIから「変更された/バインドされたデータ」を扱う場合、またはB)「頻繁に変更されたフィールド」で比較/あなたがIDで/ハッシュを比較した場合、未保存のデータです。
Equals / HashCode - 一意のビジネスキーが利用できない場合は、エンティティが初期化されたときに作成される非一時的なUUIDを使用します
はい、これは必要なときには良い戦略です。 UUIDは無料ではありませんが、パフォーマンスには問題はありません。
等しい/ HashCode - 関連エンティティを参照しない
"関連エンティティ(親エンティティのような)がビジネスキーの一部である必要がある場合、親のID(ManytoOne JoinColumnと同じ名前)を格納するために、挿入不可能で更新不可能なフィールドを追加し、このIDを等価チェックで使用します"
良いアドバイスのように聞こえる。
お役に立てれば!
私の2セントはここに答えを加えています:
フィールドまたはプロパティへのアクセス(パフォーマンスの考慮から離れたもの)に関しては、どちらもgetterおよびsetterを使用して正当にアクセスされるため、私のモデルロジックは同じ方法でそれらを設定/取得できます。 この違いは、永続性ランタイムプロバイダ(Hibernate、EclipseLinkなど)が表Aの一部のレコードを永続/設定する必要がある場合に発生します。このレコードには、表Bの一部の列を参照する外部キーがあります。プロパティアクセス型の場合、ランタイムシステムは、コード化されたセッターメソッドを使用して、テーブルB列のセルに新しい値を割り当てます。 フィールド・アクセス・タイプの場合、永続性ランタイム・システムは、表Bの列にセルを直接設定します。 この違いは一方向の関係では重要ではありませんが、setterメソッドが一貫性を考慮して設計されていれば、私自身のコードセッターメソッド(プロパティアクセスタイプ)を双方向関係に使用する必要があります。 双方向関係の一貫性は重要な問題です。このlinkを参照すると、うまく設計されたセッターの簡単な例が得られます。
Equals / hashCodeを参照して:双方向関係に参加するエンティティに対してEclipse自動生成Equals / hashCodeメソッドを使用することは不可能です。そうでない場合、循環参照が発生し、結果として例外が発生します。 双方向の関係(OneToOneと言う)を試してからEquals()やhashCode()やtoString()を自動生成すると、この例外に捕らえられます。