この質問は、Hibernate Annotation Placement Questionに多少関連しています。
しかし、どちらが良いか知りたいですか?プロパティ経由でアクセスするか、フィールド経由でアクセスしますか?それぞれの長所と短所は何ですか?
この質問は、Hibernate Annotation Placement Questionに多少関連しています。
しかし、どちらが良いか知りたいですか?プロパティ経由でアクセスするか、フィールド経由でアクセスしますか?それぞれの長所と短所は何ですか?
回答:
必要なときにいつでもアクセサにビジネスロジックを追加できるので、アクセサを好みます。次に例を示します。
@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マッピング/クローニングライブラリなど)、ライブラリが永続性と同期していることが保証されますマネージャー(両方ともゲッター/セッターを使用します)。
両方の議論がありますが、それらのほとんどは、特定のユーザー要件「ロジックを追加する必要がある場合」または「xxxxがカプセル化を壊す」から生じます。しかし、誰も実際には理論についてコメントせず、適切に推論された議論を与えていません。
オブジェクトを永続化するときにHibernate / JPAが実際に行っていること-まあ、それはオブジェクトの状態を永続化しています。つまり、簡単に再生できる方法で保管するということです。
カプセル化とは何ですか?カプセル化とは、アプリケーション/クライアントがデータに安全にアクセスするために使用できるインターフェースでデータ(または状態)をカプセル化することを意味し、一貫性と有効性を維持します。
これをMS Wordのように考えてください。MS Wordは、ドキュメントのモデルをメモリに保持します-ドキュメントの状態。これは、ユーザーがドキュメントを変更するために使用できるインターフェイス(一連のボタン、ツール、キーボードコマンドなど)を提供します。ただし、そのドキュメントを永続化(保存)することを選択すると、キーを押したときのセットではなく、内部状態が保存されます。それを生成するために使用されるマウスクリック。
オブジェクトの内部状態を保存してもカプセル化は解除されません-そうでなければ、カプセル化の意味とそれが存在する理由がよくわかりません。まさにオブジェクトのシリアライゼーションのようなものです。
このため、ほとんどの場合、ACCESSORSではなくFIELDSを永続化することが適切です。これは、オブジェクトが格納されたとおりにデータベースからオブジェクトを正確に再作成できることを意味します。検証は必要ありません。これは、オリジナルの作成時、およびデータベースに格納される前に行われたためです(神が禁じている場合を除き、DBに無効なデータを格納しています!!!!)。同様に、値はオブジェクトが保存される前にすでに計算されているため、値を計算する必要はありません。オブジェクトは、保存される前と同じように表示されます。実際、ゲッター/セッターに追加のものを追加することで、オリジナルの正確なコピーではないものを再作成するリスクが実際に高まります。
もちろん、この機能は理由のために追加されました。アクセサーを永続化するためのいくつかの有効な使用例があるかもしれませんが、それらは通常まれです。例としては、計算された値を保持したくない場合がありますが、値のゲッターでオンデマンドで計算しない理由を尋ねるか、ゲッターで遅延初期化する必要がある場合があります。個人的には良いユースケースを考えることはできません、そしてここでの答えのどれも実際には「ソフトウェア工学」の答えを与えません。
私はフィールドへのアクセスを好みます。その方法では、各プロパティにゲッター/セッターを提供する必要がないからです。
Googleを介した簡単な調査では、フィールドアクセスが主であることが示されています(例:http://java.dzone.com/tips/12-feb-jpa-20-why-accesstype)。
フィールドアクセスはSpringが推奨するイディオムだと思いますが、それをバックアップするためのリファレンスが見つかりません。
パフォーマンスを測定しようとして、「違いはない」という結論に達した、関連するSOの質問があります。
プロパティアクセサを使用する必要がある状況を次に示します。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
}
では、このクラスにどのように注釈を付ければよいでしょうか?答えは、現時点ではターゲットエンティティを指定できないため、フィールドアクセスまたはプロパティアクセスのいずれでも注釈を付けることはできません。具体的な実装に注釈を付ける必要があります。ただし、永続化されたプロパティはこのスーパークラスで宣言されているため、サブクラスでプロパティアクセスを使用する必要があります。
フィールドアクセスは、抽象ジェネリックスーパークラスを持つアプリケーションではオプションではありません。
abstract T getOneThing()
、かつabstract void setOneThing(T thing)
、およびフィールドへのアクセスを使用しています。
私はプロパティアクセサーを好み、使用する傾向があります。
foo.getId()
プロキシを初期化せずに呼び出すことができます(Hibernateを使用する場合は、HHH-3718が解決されるまで重要です)。欠点:
@Transient
あります。それは本当に特定のケースに依存します-両方のオプションが理由のために利用可能です。IMOは、3つのケースに要約されます。
値を設定するだけではなく、セッターで何かを実行したい場合は(たとえば、暗号化や計算)、フィールドアクセスを使用し、ゲッターのアノテーション(プロパティアクセス)は使用しないことを強くお勧めします。
プロパティアクセスの問題は、オブジェクトが読み込まれるときにセッターも呼び出されることです。暗号化を導入するまで、これは何ヶ月にもわたってうまくいきました。私たちのユースケースでは、セッターのフィールドを暗号化し、ゲッターで復号化したいと考えました。プロパティアクセスの問題は、Hibernateがオブジェクトをロードしたときに、セッターを呼び出してフィールドにデータを入力し、暗号化された値を再度暗号化していたことです。この投稿では、これについても触れてい ます。JavaHibernate:呼び出し元に応じてプロパティセット関数の動作が異なります。
フィールドアクセスとプロパティアクセスの違いを思い出すまで、これは頭痛の種です。これで、すべての注釈をプロパティアクセスからフィールドアクセスに移動しましたが、現在は正常に機能しています。
以下の理由から、フィールドアクセスを使用することを好みます。
プロパティへのアクセスには、等号/ハッシュコードを実装したときに非常に厄介なバグにつながることができ、直接フィールドを参照する(そのゲッターを通じて反対に)。これは、ゲッターがアクセスされたときにのみプロキシが初期化され、直接フィールドアクセスは単にnullを返すためです。
プロパティへのアクセスは、すべての注釈を付けする必要がありますユーティリティメソッドとして(例はaddChild /のremoveChild) @Transient
。
フィールドアクセスを使用すると、ゲッターをまったく公開しないことで@Versionフィールドを非表示にできます。ゲッターはセッターの追加にもつながる可能性があり、version
フィールドを手動で設定しないでください(非常に厄介な問題につながる可能性があります)。すべてのバージョンの増分はを通じてトリガされなければならないOPTIMISTIC_FORCE_INCREMENTまたはPESSIMISTIC_FORCE_INCREMENT明示的なロック。
version
フィールドのアクセサを公開することは、分離されたエンティティの代わりにDTOが使用される状況でしばしば役立ちます。
ORMがフィールドを更新する場合でも、フィールドを更新するとカプセル化が直接解除されるため、プロパティに注釈を付ける方が良いと思います。
これがあなたを燃やす場所の素晴らしい例です:あなたはおそらく同じ場所(フィールドかプロパティのどちらか)で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()
ません。
フィールドベースのアクセスを選択する最も重要な理由を要約してみましょう。さらに詳しく知りたい場合は、私のブログのこの記事を読んでください:JPAとHibernateでのアクセス戦略–フィールドとプロパティのどちらが良いですか?
フィールドベースのアクセスは、はるかに優れたオプションです。これには5つの理由があります。
理由1:コードの読みやすさの向上
フィールドベースのアクセスを使用する場合は、エンティティ属性にマッピングアノテーションを付けます。すべてのエンティティ属性の定義をクラスの最上部に配置することで、すべての属性とそのマッピングの比較的コンパクトなビューを取得できます。
理由2:アプリケーションから呼び出されないgetterまたはsetterメソッドを省略します
フィールドベースのアクセスのもう1つの利点は、HibernateやEclipseLinkなどの永続化プロバイダーがエンティティ属性のゲッターメソッドとセッターメソッドを使用しないことです。つまり、ビジネスコードで使用してはならないメソッドを提供する必要はありません。これはほとんどの場合、生成された主キー属性またはバージョン列のセッターメソッドの場合です。永続化プロバイダーはこれらの属性の値を管理するため、プログラムで設定しないでください。
理由3:getterメソッドとsetterメソッドの柔軟な実装
永続化プロバイダーはゲッターメソッドとセッターメソッドを呼び出さないため、外部の要件を満たす必要はありません。これらのメソッドは任意の方法で実装できます。これにより、ビジネス固有の検証ルールを実装したり、追加のビジネスロジックをトリガーしたり、エンティティ属性を別のデータ型に変換したりできます。
たとえば、それを使用して、オプションの関連付けまたは属性をJavaにラップできますOptional
。
理由4:ユーティリティメソッドを次のようにマークする必要はありません @Transient
フィールドベースのアクセス戦略のもう1つの利点は、ユーティリティメソッドに注釈を付ける必要がないことです。 @Transient
。このアノテーションは、メソッドまたは属性がエンティティの永続状態の一部ではないことを永続化プロバイダーに通知します。また、フィールドタイプのアクセスでは、永続的な状態はエンティティの属性によって定義されるため、JPA実装はエンティティのすべてのメソッドを無視します。
理由5:プロキシを操作するときのバグを回避する
Hibernateは、遅延フェッチされた1対の関連付けにプロキシを使用して、これらの関連付けの初期化を制御できるようにします。このアプローチは、ほとんどすべての状況で正常に機能します。ただし、プロパティベースのアクセスを使用する場合、危険な落とし穴が生じます。
プロパティベースのアクセスを使用する場合、giberメソッドを呼び出すと、Hibernateはプロキシオブジェクトの属性を初期化します。ビジネスコードでプロキシオブジェクトを使用する場合は常にそうです。しかし、equalsとhashCodeの実装の多くは、属性に直接アクセスします。初めてプロキシ属性にアクセスする場合、これらの属性はまだ初期化されていません。
デフォルトでは、JPAプロバイダーはエンティティフィールドの値にアクセスし、エンティティのJavaBeanプロパティアクセサー(getter)メソッドとミューテーター(setter)メソッドを使用してそれらのフィールドをデータベース列にマップします。そのため、エンティティ内のプライベートフィールドの名前とタイプは、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プロパティー名と一致する必要があります。この場合、フィールドに注釈を付ける場合、それらは一致する必要はありません。一貫性を保ち、のJavaBeanアクセサAccessType.PROPERTY
とのフィールドに注釈を付けるのが最善です
AccessType.FIELD
。
同じエンティティ内でJPAプロパティアノテーションとJPAフィールドアノテーションを混在させないようにすることが重要です。これを行うと、不特定の動作が発生し、エラーが発生する可能性が高くなります。
それは古いプレゼンテーションですが、Rodは、プロパティアクセスの注釈が貧血ドメインモデルを助長するものであり、注釈を付ける「デフォルト」の方法であってはならないことを示唆しています。
フィールドアクセスを支持するもう1つのポイントは、それ以外の場合はコレクションのセッターを公開することを余儀なくされることです。永続的なコレクションインスタンスをHibernateで管理されていないオブジェクトに変更すると、データの一貫性が確実に失われるため、私にとっては悪い考えです。
したがって、デフォルトのコンストラクターで空の実装に初期化された保護フィールドとしてコレクションを用意し、それらのゲッターのみを公開することを好みます。その後、のような唯一の管理業務clear()
、remove()
、removeAll()
などが可能であり、変更のHibernateは気づかない作ることがないという。
私はフィールドを好みますが、注釈をゲッターに配置するように強制する1つの状況に遭遇しました。
Hibernate JPA実装で@Embedded
は、フィールドで動作しないようです。だから、それはゲッターに行く必要があります。そして、それをゲッターに配置したら、さまざまな@Column
注釈もゲッターに配置する必要があります。(ここでは、Hibernateはフィールドとゲッターを混在させたくないと思います。)そして@Column
、1つのクラスでゲッターを使用するようになったら、それを全体にわたって行うことはおそらく意味があります。
私はフィールドアクセサーを優先します。コードはずっときれいです。すべての注釈はクラスの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>
プロパティ経由のアクセスよりもフィールド経由のアクセスを選択する必要があります。フィールドを使用すると、送受信されるデータを制限できます。viaプロパティを使用すると、ホストとしてより多くのデータを送信し、G宗派を設定することができます(工場出荷時にほとんどのプロパティが設定されています)。
hibernateのアクセスタイプに関して同じ質問があり、ここでいくつかの回答を見つけました。
ここでは、オブジェクト全体をフェッチせずに、遅延初期化とフィールドアクセスを解決しましたHibernate 1対1:getId()
エンティティBeanを作成し、ゲッターアノテーションを使用しました。私たちが遭遇した問題はこれです:一部のエンティティは、いつ更新できるかに関していくつかのプロパティについて複雑なルールを持っています。解決策は、実際の値が変更されたかどうか、変更された場合に変更を許可するかどうかを決定するビジネスロジックを各セッターに含めることでした。もちろん、Hibernateは常にプロパティを設定できるため、最終的に2つのグループのセッターができました。かなり醜い。
以前の投稿を読んで、エンティティ内からプロパティを参照すると、コレクションが読み込まれないという問題が発生する可能性があることもわかりました。
結論として、私は将来、フィールドに注釈を付けることに傾倒します。
通常、BeanはPOJOなので、とにかくアクセサがあります。
したがって、問題は「どちらが優れているか」ではなく、単に「フィールドアクセスをいつ使用するか」です。そして答えは、「フィールド用のセッター/ゲッターが必要ないとき!」です。
私はこれについて考え、メソッドアクセサを選択します
どうして?
フィールドとメソッドのアクセサーは同じですが、後でロードフィールドにロジックが必要な場合は、フィールドに配置されたすべての注釈を保存して移動します
よろしく
グルバート
両方とも :
EJB3仕様では、アクセスされる要素タイプ、つまりプロパティアクセスを使用する場合はゲッターメソッド、フィールドアクセスを使用する場合はフィールドにアノテーションを宣言する必要があります。
https://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#entity-mapping