この質問は、 Hibernate Annotation Placement Questionに多少関連しています。
しかし、どちらが優れているか知りたいですか?プロパティ経由でアクセスするか、フィールド経由でアクセスしますか? それぞれの長所と短所は何ですか?
この質問は、 Hibernate Annotation Placement Questionに多少関連しています。
しかし、どちらが優れているか知りたいですか?プロパティ経由でアクセスするか、フィールド経由でアクセスしますか? それぞれの長所と短所は何ですか?
両方に議論がありますが、それらのほとんどは、「ロジックを追加する必要がある場合はどうなるか」、または「xxxxはカプセル化を破る」という特定のユーザー要件に由来します。しかし、誰も実際に理論についてコメントしておらず、適切に推論された議論をしていません。
Hibernate / JPAがオブジェクトを永続化するときに実際に何をしているのか-まあ、それはオブジェクトのSTATEを永続化しています。つまり、簡単に再現できるように保管するということです。
カプセル化とは何ですか?カプセル化とは、アプリケーション/クライアントがデータに安全にアクセスするために使用できるインターフェイスを使用してデータ(または状態)をカプセル化することを意味します。つまり、データの一貫性と有効性を維持します。
これをMSWordのように考えてください。MS Wordは、ドキュメントのモデルをメモリに保持します-ドキュメントはSTATEです。これは、ユーザーがドキュメントを変更するために使用できるインターフェイス(ボタン、ツール、キーボードコマンドのセットなど)を提供します。ただし、そのドキュメントを永続化(保存)することを選択すると、キー押下のセットではなく、内部状態が保存されます。それを生成するために使用されるマウスクリック。
オブジェクトの内部状態を保存しても、カプセル化は中断されません。そうしないと、カプセル化の意味と、カプセル化が存在する理由がよくわかりません。これは、実際にはオブジェクトのシリアル化と同じです。
このため、ほとんどの場合、ACCESSORではなくFIELDSを永続化することが適切です。これは、オブジェクトが保存されたとおりにデータベースから正確に再作成できることを意味します。これは、作成時にオリジナルで行われ、データベースに保存される前に行われたため、検証は必要ありません(ただし、神が禁じている場合を除き、無効なデータをDBに保存しています!!!!)。同様に、値はオブジェクトが保存される前にすでに計算されているため、値を計算する必要はありません。オブジェクトは、保存される前とまったく同じように見える必要があります。実際、ゲッター/セッターに追加のものを追加することにより、元のコピーではないものを再作成するリスクが実際に高まります。
もちろん、この機能は理由で追加されました。アクセサーを永続化するためのいくつかの有効なユースケースがあるかもしれませんが、それらは通常まれです。例としては、計算された値を永続化しないようにしたい場合がありますが、値のゲッターでオンデマンドで計算しない理由や、ゲッターで遅延的に初期化しない理由を質問したい場合があります。個人的には、良いユースケースは考えられません。ここでの答えはどれも、実際には「ソフトウェアエンジニアリング」の答えにはなりません。
プロパティ アクセサーを使用する必要がある状況を次に示します。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
}
では、このクラスにどのように注釈を付ける必要があるのでしょうか? 答えは、この時点でターゲット エンティティを指定できないため、フィールド アクセスまたはプロパティ アクセスで注釈を付けることはまったくできないということです。具体的な実装に注釈を付ける必要があります。ただし、永続化されたプロパティはこのスーパークラスで宣言されているため、サブクラスでプロパティ アクセスを使用する必要があります。
フィールド アクセスは、抽象ジェネリック スーパークラスを使用するアプリケーションではオプションではありません。
私はプロパティアクセサーを好み、使用する傾向があります。
foo.getId() プロキシを初期化せずに呼び出すことができます( HHH-3718が解決されるまで、Hibernate を使用する場合は重要です)。欠点:
@Transientあります。必要なときにいつでもビジネス ロジックをアクセサーに追加できるので、私はアクセサーを好みます。次に例を示します。
@Entity
public class Person {
@Column("nickName")
public String getNickName(){
if(this.name != null) return generateFunnyNick(this.name);
else return "John Doe";
}
}
さらに、別のライブラリ (JSON 変換ライブラリ、BeanMapper、Dozer、またはゲッター/セッター プロパティに基づくその他の Bean マッピング/クローン作成ライブラリなど) をミックスに投入すると、ライブラリが永続化と同期していることが保証されます。 manager (どちらも getter/setter を使用します)。
次の理由から、フィールド アクセスを使用することを好みます。
equals/hashCode を実装し、フィールドを直接参照する場合 (getter を使用するのではなく) 、プロパティ アクセスによって非常に厄介なバグが発生する可能性があります。これは、getter がアクセスされたときにのみプロキシが初期化され、フィールドへの直接アクセスでは単に null が返されるためです。
プロパティ アクセスでは、すべてのユーティリティ メソッド (addChild/removeChild など) に として注釈を付ける必要があります@Transient。
フィールド アクセスを使用@Versionすると、ゲッターをまったく公開しないことでフィールドを非表示にできます。ゲッターはセッターの追加にもつながる可能性があり、versionフィールドは手動で設定しないでください (非常に厄介な問題につながる可能性があります)。すべてのバージョンのインクリメントは、OPTIMISTIC_FORCE_INCREMENTまたはPESSIMISTIC_FORCE_INCREMENT明示的なロックによってトリガーする必要があります。
値を設定するだけでなく、セッターで何かをしたい場合 (暗号化や計算など) は、フィールド アクセスとゲッター (プロパティ アクセス) の注釈ではなく、強くお勧めします。
プロパティ アクセスの問題は、オブジェクトのロード時にセッターも呼び出されることです。これは、暗号化を導入するまで、何ヶ月もの間うまくいきました。私たちのユースケースでは、setter でフィールドを暗号化し、getter で復号化したいと考えていました。プロパティ アクセスに関する現在の問題は、Hibernate がオブジェクトをロードしたときに、セッターを呼び出してフィールドに入力し、暗号化された値を再度暗号化していたことです。この投稿では、次のことについても言及しています: Java Hibernate: 呼び出し元によって異なるプロパティ セット関数の動作
フィールドアクセスとプロパティアクセスの違いを思い出すまで、これは頭痛の種でした。これで、すべての注釈をプロパティ アクセスからフィールド アクセスに移動し、正常に動作するようになりました。
プロパティへのアクセスとフィールドへのアクセスは、遅延初期化に関して微妙に異なると思います。
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()には選択が必要ですが、そうではありpb.getId()ません。
ORM でカプセル化を行う場合でも、フィールドを更新するとカプセル化が直接壊れるため、プロパティに注釈を付ける方が良いと思います。
やけどを負ってしまう場所の良い例を次に示します。おそらく、Hibernate バリデーターと永続化のアノテーションを同じ場所 (フィールドまたはプロパティ) に配置する必要があります。フィールドに注釈が付けられた hibernate バリデーターを使用した検証をテストする場合、エンティティのモックを使用して単体テストをバリデーターのみに分離することはできません。ああ。
これは古いプレゼンテーションですが、Rod は、プロパティ アクセスの注釈は貧血ドメイン モデルを助長するものであり、注釈を付ける「既定の」方法であってはならないと示唆しています。
私はフィールド アクセサを好みます。コードはずっときれいです。すべての注釈をクラスの 1 つのセクションに配置できるため、コードが読みやすくなります。
プロパティ アクセサーに関する別の問題が見つかりました。クラスに getXYZ メソッドがあり、永続プロパティに関連付けられているという注釈が付けられていない場合、hibernate はそれらのプロパティを取得しようとする SQL を生成し、非常に紛らわしいエラー メッセージが表示されます。2時間無駄。このコードは私が書いたものではありません。私はこれまで常にフィールド アクセサーを使用してきましたが、この問題に遭遇したことはありません。
このアプリで使用される休止状態のバージョン:
<!-- 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>
私はフィールドの方が好きですが、getter にアノテーションを配置せざるを得ないように思われる状況に遭遇しました。
Hibernate JPA 実装で@Embeddedは、フィールドでは機能しないようです。そのため、ゲッターに行く必要があります。そして、それをゲッターに配置すると、さまざまな@Column注釈もゲッターに追加する必要があります。(Hibernate はここでフィールドとゲッターを混在させたくないと思います。) そして、一度@Columnゲッターを 1 つのクラスに配置したら、おそらくずっとそれを行うのが理にかなっています。
休止状態のアクセスタイプに関して同じ質問があり、ここでいくつかの回答を見つけました。
ここで遅延初期化とフィールドアクセスを解決しましたHibernate one-to-one: getId() without fetching objects
エンティティ Bean を作成し、ゲッター アノテーションを使用しました。私たちが遭遇した問題は次のとおりです。一部のエンティティには、一部のプロパティに対して、いつ更新できるかに関する複雑なルールがあります。解決策は、実際の値が変更されたかどうか、変更された場合は変更を許可するかどうかを決定するビジネス ロジックを各セッターに含めることでした。もちろん、Hibernate はいつでもプロパティを設定できるため、最終的に 2 つのグループのセッターができました。かなり醜い。
以前の投稿を読んで、エンティティ内からプロパティを参照すると、コレクションが読み込まれないという問題が発生する可能性があることもわかりました。
結論として、将来的にはフィールドに注釈を付けることに傾倒します。
クラスをよりクリーンにするには、フィールドにアノテーションを入力してから、@ Access(AccessType.PROPERTY)を使用します
私はこれについて考え、メソッドアクセサーを選択します
なぜ?
フィールドとメソッドアクセサーは同じですが、後でロードフィールドにロジックが必要な場合は、フィールドに配置されたすべての注釈を移動して保存します
よろしく
グルバート
AccessType.PROPERTY: EJB 永続化実装は、JavaBean の「setter」メソッドを介してクラスに状態をロードし、JavaBean の「getter」メソッドを使用してクラスから状態を取得します。これがデフォルトです。
AccessType.FIELD: 状態は、クラスのフィールドから直接読み込まれ、取得されます。JavaBean の「ゲッター」と「セッター」を記述する必要はありません。