1. ホーム
  2. java

[解決済み] 完璧なJPAエンティティを作成する【クローズド

2022-03-22 06:21:47

質問

私はJPA(実装はHibernate)を使ってしばらく仕事をしていますが、エンティティを作成する必要があるたびに、AccessType、immutableプロパティ、equals/hashCode、...といった問題と格闘している自分に気がつきます。
そこで、それぞれの問題に対する一般的なベストプラクティスを探してみることにし、個人的な使用のためにこれを書き留めておくことにしました。
しかし、どなたかコメントや間違っているところを教えていただけると幸いです。

エンティティクラス

  • Serializable を実装する。

    理由 仕様ではそうなっていますが、JPAプロバイダによってはそうなっていないものもあります。JPAプロバイダとしてのHibernateはこれを強制しませんが、Serializableが実装されていない場合、ClassCastExceptionで腹の底で失敗する可能性があります。

コンストラクタ

  • エンティティのすべての必須フィールドを含むコンストラクタを作成します。

    理由 コンストラクタは、作成されたインスタンスを常に正常な状態に保つ必要があります。

  • このコンストラクタの他に、パッケージのプライベートなデフォルトコンストラクタを用意します。

    理由 デフォルトのコンストラクタは、Hibernate にエンティティを初期化させるために必要です。プライベートも可能ですが、バイトコードインスツルメンテーションなしでランタイムプロキシ生成およびデータ取得を効率的に行うには、パッケージプライベート(またはパブリック)可視性が必要です。

フィールド/プロパティ

  • 一般的にはフィールドアクセス、必要な場合はプロパティアクセスを使用する

    理由: プロパティアクセスとフィールドアクセスのどちらかに明確で説得力のある議論がないため、おそらく最も議論の多い問題です。

  • immutableフィールドのセッターを省略する(アクセス型フィールドでは必要ない)

  • プロパティはプライベートにすることができます。
    理由:以前、(Hibernateの)パフォーマンスのためにprotectedの方が良いと聞いたことがありますが、ウェブで見つかるのは、このようなものばかりです。 Hibernateはpublic、private、protectedのアクセッサメソッドだけでなく、public、private、protectedのフィールドに直接アクセスすることができます。選択はあなた次第で、アプリケーションの設計に合うように合わせることができます。

イコール/ハッシュコード

  • このIDがエンティティの永続化時にのみ設定される場合、生成されたIDは決して使用しないでください。
  • 優先順位:一意なビジネス・キーを形成するために不変の値を使用し、これを使用して等質性をテストする。
  • 一意のビジネスキーが利用できない場合は、非一時的なビジネスキーを使用します。 UUID これは、エンティティが初期化されたときに作成されます。 この素晴らしい記事 をご覧ください。
  • 決して このエンティティ(親エンティティなど)がビジネスキーの一部である必要がある場合は、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 を使用します。

解決方法は?

いくつかの主要なアプリケーションを含む、長いHibernate/永続化の経験から、いくつかの重要なポイントにお答えします。

エンティティクラス:Serializableを実装するか?

キー はSerializableを実装する必要があります。HttpSessionに入るもの、RPC/Java EEで送信されるものは、Serializableを実装する必要があります。それ以外のものは、それほどでもない。重要なことに時間を使ってください。

コンストラクタ:エンティティの必須フィールドをすべて含むコンストラクタを作成しますか?

アプリケーション・ロジック用のコンストラクタは、エンティティを作成するときに常に分かっている、いくつかの重要なフィールド("外部キー" または"タイプ/種類" )だけを持つべきです。残りは、セッターメソッドを呼び出して設定する必要があります。

コンストラクターに多くのフィールドを入れることは避けてください。コンストラクタは便利であるべきで、オブジェクトに基本的な健全性を与えるものです。Name、Type、Parentsの3つのフィールドは、一般的に有用です。

一方、アプリケーションのルールとして(現在では)顧客が住所を持つことが必要な場合、それはセッターに任せましょう。これは弱いルールの一例です。来週には、詳細入力画面に行く前にCustomerオブジェクトを作成したいと思うかもしれませんね? 未知のデータ、不完全なデータ、または部分的に入力されたデータに対して可能性を残しておきましょう。

コンストラクタ:また、パッケージのプライベート・デフォルト・コンストラクタ?

しかし、package private ではなく、'protected' を使ってください。必要な内部構造が見えないと、サブクラス化するのが本当に苦痛になります。

フィールド・プロパティ

Hibernateの、インスタンスの外側からの'property'フィールドアクセスを使用します。インスタンス内では、フィールドを直接使用します。理由: Hibernateの最もシンプルな方法である標準リフレクションが機能するようにします。

アプリケーションに対して「不変」なフィールドについては、Hibernateがこれらを読み込むことができる必要があります。これらのメソッドを「プライベート」にしたり、アノテーションをつけたりして、アプリケーションのコードが不要なアクセスをしないようにすることができます。

注意:equals()関数を書くときは、「他の」インスタンスの値に対するゲッターを使用してください。そうしないと、プロキシのインスタンスで初期化されていないフィールドや空のフィールドがヒットします。

保護された方が(Hibernateの)パフォーマンスにとって良いのか?

ありえない。

Equals/HashCode?

これは、保存される前のエンティティを操作するのに関連するもので、茨の道です。不変の値でハッシュ化/比較?ほとんどのビジネス・アプリケーションでは、そんなものはない。

顧客が住所を変えたり、会社の名前を変えたりすることはよくあることですが、よくあることです。また、データが正しく入力されていない場合、訂正が可能でなければなりません。

通常、不変に保たれるものは、ParentingとおそらくType/Kindです。通常、ユーザーはこれらを変更するのではなく、レコードを再作成します。しかし、これらは実体を一意に特定するものではありません。

要するに、「不変のデータ」というのは、実はそうではないということです。主キー/IDフィールドは、そのような保証された安定性& immutabilityを提供するために、正確な目的のために生成されます。

比較・ハッシュ・リクエスト処理の作業フェーズは、A) UIから変更・結合されたデータを扱う場合(変更頻度の低いフィールドを比較・ハッシュする場合)、B) 保存されていないデータを扱う場合(IDを比較・ハッシュする場合)に考慮・計画する必要があります。

Equals/HashCode -- ユニークなビジネス・キーが利用できない場合、エンティティの初期化時に作成される非一時的なUUIDを使用します。

はい、これは必要な場合には良い戦略です。しかし、UUIDはパフォーマンス的に無料ではないことに注意してください -- そして、クラスタリングは物事を複雑にしています。

Equals/HashCode -- 関連するエンティティを参照しない

"関連するエンティティ(親エンティティのような)がビジネスキーの一部である必要がある場合、挿入および更新不可のフィールドを追加して親ID(ManytoOne JoinColumnと同じ名前)を格納し、このIDを等値検査で使用します"。

良いアドバイスのようですね。

お役に立てれば幸いです。