web-dev-qa-db-ja.com

Hibernate Annotations-フィールドまたはプロパティアクセスのどちらが良いですか?

この質問は Hibernate Annotation Placement Question に多少関連しています。

しかし、私はどちらがbetterか知りたいですか?プロパティ経由でアクセスするか、フィールド経由でアクセスしますか?それぞれの長所と短所は何ですか?

128
Martin OConnor

必要なときにいつでもアクセサにビジネスロジックを追加できるので、アクセサが好きです。以下に例を示します。

@Entity
public class Person {

  @Column("nickName")
  public String getNickName(){
     if(this.name != null) return generateFunnyNick(this.name);
     else return "John Doe";
  }
}

また、別のライブラリをミックスに追加すると(JSON変換ライブラリ、BeanMapper、Dozer、またはgetter/setterプロパティに基づいた他のBeanマッピング/クローンライブラリ)、ライブラリが永続性と同期していることが保証されます。マネージャー(両方ともゲッター/セッターを使用)。

31
Miguel Ping

両方に引数がありますが、それらのほとんどは、「ロジックを追加する必要がある場合」または「xxxx breaks encapsulation」という特定のユーザー要件に由来します。しかし、誰も理論について本当にコメントしておらず、適切に論理的な議論をしている。

Hibernate/JPAがオブジェクトを永続化するときに実際に行っていること-まあ、オブジェクトのSTATEを永続化しています。つまり、簡単に再現できる方法で保存することを意味します。

カプセル化とは何ですか?カプセル化とは、アプリケーション/クライアントがデータに安全にアクセスするために使用できるインターフェースでデータ(または状態)をカプセル化することを意味し、一貫性と有効性を保ちます。

これはMS Wordのように考えてください。 MS Wordは、メモリ内のドキュメントのモデル(ドキュメントSTATE)を保持します。ユーザーがドキュメントを変更するために使用できるインターフェイスを提供します-ボタン、ツール、キーボードコマンドなどのセット。ただし、そのドキュメントを永続化(保存)することを選択すると、キープレスのセットではなく内部状態を保存します。生成に使用されるマウスクリック。

オブジェクトの内部状態を保存しても、カプセル化は破壊されません。そうしないと、カプセル化の意味とカプセル化の理由がわかりません。まさにオブジェクトのシリアル化のようなものです。

このため、ほとんどの場合、ACCESSORSではなくFIELDSを永続化することが適切です。これは、オブジェクトが保存されたとおりにデータベースからオブジェクトを正確に再作成できることを意味します。検証は必要ありません。これは、作成時に元のデータベースで行われたため、データベースに格納される前に行われたためです(ただし、神に禁じられていない限り、無効なデータをDBに格納しています!!!!)。同様に、値はオブジェクトが保存される前にすでに計算されているため、値を計算する必要はありません。オブジェクトは、保存される前の状態に見えるはずです。実際、ゲッター/セッターに追加のものを追加することにより、実際には増加オリジナルの正確なコピーではないものを再作成するリスクがあります。

もちろん、この機能は理由のために追加されました。アクセサーを永続化するための有効なユースケースが存在する場合がありますが、通常はまれです。例として、計算された値の永続化を避けたい場合がありますが、値のゲッターでオンデマンドで計算しない理由を質問したり、ゲッターで遅延的に初期化したりすることができます。個人的には、良いユースケースを考えることはできません。また、ここでの答えはどれも「ソフトウェアエンジニアリング」の答えを本当に与えません。

235
Martin

私はフィールドアクセスを好みます。その方法で、各プロパティにゲッター/セッターを提供することを強制されないからです。

Googleによる簡単な調査では、フィールドアクセスが大半であることが示唆されています(例: http://Java.dzone.com/tips/12-feb-jpa-20-why-accesstype )。

フィールドアクセスは、Springが推奨するイディオムだと思いますが、それをバックアップするためのリファレンスが見つかりません。

関連SO質問 があり、パフォーマンスを測定しようとし、「違いはない」という結論に達しました。

77
duffymo

プロパティアクセサを使用する必要がある状況を次に示します。 8つの具象サブクラスに継承するための実装の良さを備えたGENERIC抽象クラスがあるとします。

public abstract class Foo<T extends Bar> {

    T oneThing;
    T anotherThing;

    // getters and setters ommited for brevity

    // Lots and lots of implementation regarding oneThing and anotherThing here
 }

さて、このクラスに正確にどのように注釈を付けるべきでしょうか?答えは、この時点ではターゲットエンティティを指定できないため、フィールドアクセスまたはプロパティアクセスで注釈を付けることはできません。具体的な実装に注釈を付ける必要があります。ただし、永続プロパティはこのスーパークラスで宣言されているため、サブクラスではプロパティアクセスを使用する必要があります。

フィールドアクセスは、抽象ジェネリックスーパークラスを持つアプリケーションのオプションではありません。

38
HDave

私は、プロパティアクセッサを好み、使用する傾向があります。

  • 必要に応じてロジックを追加できます(受け入れられた回答に記載されています)。
  • プロキシを初期化せずにfoo.getId()を呼び出すことができます(Hibernateを使用する場合、 HHH-3718 get解決済み)。

欠点:

  • コードが読みにくくなります。たとえば、クラス全体をブラウズして、@Transientがあるかどうかを確認する必要があります。
33
Pascal Thivent

それは本当に特定のケースに依存します-両方のオプションが理由で利用可能です。 IMOは3つのケースに要約されます。

  1. setterには、データベースからインスタンスをロードするときに実行すべきでないロジックがいくつかあります。たとえば、セッターで値の検証が行われますが、dbからのデータは有効である必要があります(そうでない場合、そこに到達しません(:)。この場合、フィールドアクセスが最も適切です。
  2. setterには、dbからインスタンスをロードしているときでも、常に呼び出されるロジックがいくつかあります。たとえば、初期化されているプロパティは、いくつかの計算フィールドの計算に使用されます(たとえば、プロパティ-金額、計算プロパティ-同じインスタンスの複数の通貨プロパティの合計);この場合、プロパティへのアクセスが必要です。
  3. 上記のいずれの場合も-両方のオプションが適用可能で、一貫性を保ちます(つまり、この状況でフィールドアクセスが選択されている場合は、同様の状況で常に使用します)。
13
01es

セッターで値を設定するだけでなく、暗号化や計算などを行う場合は、ゲッターでのアクセスではなくフィールドアクセス(プロパティアクセス)を強くお勧めします。

プロパティへのアクセスの問題は、オブジェクトがロードされるときにセッターも呼び出されることです。暗号化を導入するまで、これは私にとって長年うまくいきました。ユースケースでは、セッター内のフィールドを暗号化し、ゲッター内で復号化することを望みました。現在、プロパティアクセスの問題は、Hibernateがオブジェクトをロードしたときに、セッターを呼び出してフィールドにデータを取り込むため、暗号化された値を再度暗号化することでした。この投稿では、これにも言及しています。 Java Hibernate:呼び出し元に応じて異なるプロパティセット関数の動作

これは、フィールドアクセスとプロパティアクセスの違いを思い出すまで頭痛の種でした。これで、すべての注釈をプロパティアクセスからフィールドアクセスに移動し、正常に機能するようになりました。

12
Christoph

次の理由から、フィールドアクセスを使用することを好みます。

  1. プロパティアクセスは、equals/hashCodeおよび フィールドを直接参照する (非常に厄介なバグを引き起こす可能性がありますそのゲッターを介して反対)。これは、ゲッターにアクセスしたときにのみプロキシが初期化され、直接フィールドアクセスでは単にnullが返されるためです。

  2. プロパティアクセスでは、すべての ユーティリティメソッド (addChild/removeChildなど)に@Transient

  3. フィールドアクセスでは、ゲッターをまったく公開しないことで@Versionフィールドを非表示にできます。ゲッターはセッターの追加にもつながる可能性があり、versionフィールドを手動で設定しないでください(非常に厄介な問題につながる可能性があります)。すべてのバージョンの増分は、 OPTIMISTIC_FORCE_INCREMENT または PESSIMISTIC_FORCE_INCREMENT 明示的なロックを介してトリガーする必要があります。

9
Vlad Mihalcea

フィールドを更新するとカプセル化が直接壊れるので、ORMがそれを行ったとしても、プロパティに注釈を付ける方が良いと思います。

これがどこにあなたを燃やすのかの素晴らしい例です:あなたはおそらく同じ場所(フィールドまたはプロパティのいずれか)でハイバネートバリデータと永続性のための注釈が欲しいでしょう。フィールドに注釈が付けられたHibernateバリデーターによる検証をテストする場合、エンティティのモックを使用して、単体テストをバリデーターだけに分離することはできません。痛い。

7
Justin Standard

プロパティアクセスとフィールドアクセスは、遅延初期化に関して微妙に異なると思います。

2つの基本Beanについて次のマッピングを検討してください。

<hibernate-mapping package="org.nkl.model" default-access="field">
  <class name="FieldBean" table="FIELD_BEAN">
    <id name="id">
      <generator class="sequence" />
    </id>
    <property name="message" />
  </class>
</hibernate-mapping>

<hibernate-mapping package="org.nkl.model" default-access="property">
  <class name="PropBean" table="PROP_BEAN">
    <id name="id">
      <generator class="sequence" />
    </id>
    <property name="message" />
  </class>
</hibernate-mapping>

そして、次の単体テスト:

@Test
public void testFieldBean() {
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    FieldBean fb = new FieldBean("field");
    Long id = (Long) session.save(fb);
    tx.commit();
    session.close();

    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    fb = (FieldBean) session.load(FieldBean.class, id);
    System.out.println(fb.getId());
    tx.commit();
    session.close();
}

@Test
public void testPropBean() {
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    PropBean pb = new PropBean("prop");
    Long id = (Long) session.save(pb);
    tx.commit();
    session.close();

    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    pb = (PropBean) session.load(PropBean.class, id);
    System.out.println(pb.getId());
    tx.commit();
    session.close();
}

必要な選択の微妙な違いが表示されます。

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        FIELD_BEAN
        (message, id) 
    values
        (?, ?)
Hibernate: 
    select
        fieldbean0_.id as id1_0_,
        fieldbean0_.message as message1_0_ 
    from
        FIELD_BEAN fieldbean0_ 
    where
        fieldbean0_.id=?
0
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        PROP_BEAN
        (message, id) 
    values
        (?, ?)
1

つまり、fb.getId()を呼び出すにはselectが必要ですが、pb.getId()は必要ありません。

6
toolkit

私はフィールドアクセッサを好みます。コードはずっときれいです。すべての注釈はクラスの1つのセクションに配置でき、コードははるかに読みやすくなります。

プロパティアクセサーに別の問題が見つかりました:クラスにgetXYZメソッドがあり、永続プロパティに関連付けられていると注釈されていない場合、hibernateはこれらのプロパティを取得しようとするsqlを生成し、非常に紛らわしいエラーメッセージを生成します。 2時間無駄になりました。私はこのコードを書きませんでした。私はこれまでフィールドアクセッサを使用してきましたが、この問題に遭遇したことはありません。

このアプリで使用されるHibernateバージョン:

<!-- hibernate -->
<hibernate-core.version>3.3.2.GA</hibernate-core.version>
<hibernate-annotations.version>3.4.0.GA</hibernate-annotations.version>
<hibernate-commons-annotations.version>3.1.0.GA</hibernate-commons-annotations.version>
<hibernate-entitymanager.version>3.4.0.GA</hibernate-entitymanager.version>
2
John Goyer

私たちはまだそこにいますか

それは古いプレゼンテーションですが、ロッドは、プロパティアクセスに関する注釈が貧弱なドメインモデルを奨励し、注釈を付けるための「デフォルト」の方法であってはならないと示唆しています。

2
Manuel Palacio

フィールドベースのアクセスを選択する最も重要な理由を要約してみましょう。さらに深く掘り下げたい場合は、私のブログでこの記事をお読みください: JPAとHibernateのアクセス戦略–フィールドまたはプロパティアクセスのどちらが良いですか?

フィールドベースのアクセスは、はるかに優れたオプションです。理由は次の5つです。

理由1:コードの読みやすさの向上

フィールドベースのアクセスを使用する場合、エンティティの属性にマッピングアノテーションを付けます。すべてのエンティティ属性の定義をクラスの最上部に配置すると、すべての属性とそれらのマッピングの比較的コンパクトなビューが得られます。

理由2:アプリケーションで呼び出されるべきではないgetterまたはsetterメソッドを省略します

フィールドベースのアクセスのもう1つの利点は、永続化プロバイダー(HibernateやEclipseLinkなど)がエンティティ属性の取得メソッドと設定メソッドを使用しないことです。つまり、ビジネスコードで使用してはならない方法を提供する必要はありません。これは、ほとんどの場合、 生成された主キー属性 またはバージョン列のsetterメソッドの場合です。永続性プロバイダーはこれらの属性の値を管理するため、プログラムで設定しないでください。

理由3:ゲッターおよびセッターメソッドの柔軟な実装

永続化プロバイダーはgetterおよびsetterメソッドを呼び出さないため、外部の要件を満たす必要はありません。これらのメソッドは任意の方法で実装できます。これにより、ビジネス固有の検証ルールを実装したり、追加のビジネスロジックをトリガーしたり、エンティティ属性を別のデータ型に変換したりできます。

たとえば、これを使用して、オプションの関連付けまたは属性をJava Optionalラップできます

理由4:ユーティリティメソッドを@Transientとしてマークする必要がない

フィールドベースのアクセス戦略のもう1つの利点は、ユーティリティメソッドに@Transientの注釈を付ける必要がないことです。この注釈は、メソッドまたは属性がエンティティの永続状態の一部ではないことを永続プロバイダーに伝えます。また、フィールドタイプアクセスでは、永続状態がエンティティの属性によって定義されるため、JPA実装はエンティティのすべてのメソッドを無視します。

理由5:プロキシを使用する場合のバグを回避する

Hibernateは lazyly fetched to-one associations のプロキシを使用して、これらの関連付けの初期化を制御できるようにします。このアプローチは、ほとんどすべての状況でうまく機能します。ただし、プロパティベースのアクセスを使用すると、危険な落とし穴が生じます。

プロパティベースのアクセスを使用する場合、Hibernateはgetterメソッドを呼び出すときにプロキシオブジェクトの属性を初期化します。ビジネスコードでプロキシオブジェクトを使用する場合は常にそうです。しかし、かなり多くの equalsとhashCode実装 が属性に直接アクセスします。プロキシ属性に初めてアクセスする場合、これらの属性はまだ初期化されていません。

2
Thorben Janssen

私はフィールドを好みますが、ゲッターに注釈を付けることを余儀なくされるような状況に遭遇しました。

Hibernate JPA実装では、@Embeddedはフィールドで機能しないようです。それで、それはゲッターに行かなければなりません。そして、それをゲッターに配置すると、さまざまな@Columnアノテーションもゲッターに配置する必要があります。 (ここでは、Hibernateがフィールドとゲッターを混在させたくないと考えています。)そして、@Columnを1つのクラスのゲッターに配置したら、おそらくそれを全体で行うのが理にかなっています。

2
Willie Wheeler

デフォルトでは、JPAプロバイダーはエンティティフィールドの値にアクセスし、エンティティのJavaBeanプロパティアクセサー(ゲッター)およびミューテーター(セッター)メソッドを使用して、それらのフィールドをデータベース列にマップします。そのため、エンティティのプライベートフィールドの名前とタイプはJPAにとって重要ではありません。代わりに、JPAはJavaBeanプロパティアクセサーの名前と戻り値の型のみを調べます。 @javax.persistence.Accessアノテーションを使用してこれを変更できます。これにより、JPAプロバイダーが採用するアクセス方法を明示的に指定できます。

@Entity
@Access(AccessType.FIELD)
public class SomeEntity implements Serializable
{
...
}

AccessType列挙に使用できるオプションは、PROPERTY(デフォルト)およびFIELDです。 PROPERTYを使用すると、プロバイダーはJavaBeanプロパティメソッドを使用してフィールド値を取得および設定します。 FIELDは、プロバイダーがインスタンスフィールドを使用してフィールド値を取得および設定できるようにします。ベストプラクティスとして、特に強いる理由がない限り、デフォルトのままにしてJavaBeanプロパティを使用する必要があります。

これらのプロパティアノテーションは、プライベートフィールドまたはパブリックアクセサメソッドのいずれかに配置できます。 AccessType.PROPERTY(デフォルト)を使用し、JavaBeanアクセサーの代わりにプライベートフィールドに注釈を付ける場合、フィールド名はJavaBeanプロパティ名と一致する必要があります。ただし、JavaBeanアクセサーに注釈を付ける場合、名前は一致する必要はありません。同様に、AccessType.FIELDを使用し、フィールドの代わりにJavaBeanアクセサーに注釈を付ける場合、フィールド名もJavaBeanプロパティ名と一致する必要があります。この場合、フィールドに注釈を付ける場合、一致する必要はありません。一貫性を保ち、AccessType.PROPERTYのJavaBeanアクセサーとAccessType.FIELDのフィールドに注釈を付けることをお勧めします。

JPAプロパティアノテーションとJPAフィールドアノテーションを同じエンティティに混在させないでください。これを行うと、不特定の動作が発生し、エラーが発生する可能性が非常に高くなります。

2
Faraz Durrani

フィールドアクセスを支持するもう1つのポイントは、そうしないとコレクションのセッターを公開することを余儀なくされることです。永続コレクションインスタンスをHibernateによって管理されていないオブジェクトに変更すると、データの一貫性が確実に失われるため、私にとっては悪い考えです。

そのため、デフォルトのコンストラクターで空の実装に初期化され、それらのゲッターのみを公開する保護フィールドとしてコレクションを持つことを好みます。その場合、clear()remove()removeAll()などのような、Hibernateが変更を認識しないことのない管理された操作のみが可能です。

2

プロパティ経由のアクセスではなく、フィールド経由のアクセスを選択する必要があります。フィールドを使用すると、送受信されるデータを制限できます。 viaプロパティを使用すると、より多くのデータをホストとして送信し、G単位を設定することができます(ファクトリは、全体でほとんどのプロパティを設定します)。

2
user10801787

ここで遅延初期化とフィールドアクセスを解決しました 1対1の休止状態:getId()オブジェクト全体を取得せずに

1
xmedeko

Hibernateのaccesstypeについても同じ質問があり、 いくつかの回答 が見つかりました。

1
sundary

エンティティBeanを作成し、ゲッターアノテーションを使用しました。私たちが遭遇した問題はこれです:いくつかのエンティティは、いつ更新できるかに関するいくつかのプロパティに対して複雑なルールを持っています。解決策は、各セッターに実際の値が変更されたかどうか、変更された場合は変更を許可するかどうかを決定するビジネスロジックを用意することでした。もちろん、Hibernateはいつでもプロパティを設定できるため、最終的に2つのセッターグループになりました。かなりい。

以前の投稿を読んで、エンティティ内からプロパティを参照すると、コレクションが読み込まれないという問題が発生する可能性があることもわかりました。

一番下の行、私は将来的にフィールドに注釈を付けることに傾くでしょう。

1
Steve11235

これについて考えて、メソッドアクセサを選択します

どうして?

フィールドとmethosアクセサーは同じであるが、後でロードフィールドに何らかのロジックが必要になった場合、フィールドに配置されたすべての注釈を移動して保存するため

よろしく

グラブハート

0
user261548

通常、BeanはPOJOであるため、とにかくアクセサーがあります。

したがって、質問は「どちらが良いですか?」ではなく、「いつフィールドアクセスを使用するか?」です。そして、答えは「フィールドにセッター/ゲッターが必要ないとき!」です。

0

クラスをきれいにするには、フィールドに注釈を入れてから@Access(AccessType.PROPERTY)を使用します

0
ely

両方 :

EJB3仕様では、アクセスする要素タイプ、つまりプロパティアクセスを使用する場合はgetterメソッド、フィールドアクセスを使用する場合はフィールドにアノテーションを宣言する必要があります。

https://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#entity-mapping

0
moueza

AccessType.PROPERTY:EJB永続化実装は、JavaBean「setter」メソッドを介してクラスに状態をロードし、JavaBean「getter」メソッドを使用してクラスから状態を取得します。これがデフォルトです。

AccessType.FIELD:状態はロードされ、クラスのフィールドから直接取得されます。 JavaBeanの「getter」および「setter」を記述する必要はありません。

0
Vicky