Hibernate 3.2のannotationコメントの位置がパフォーマンスに与える大きな影響


オリジナル:
http://www.iteye.com/topic/212236
最近、自分のAppFuseをJDK 1.5の形式に書き換え、主な変更はHibernate配置をannotation配置に変更し、順調だと思っていたが、意外にも多くの奇妙な問題に遭遇し、何度もHibernateソースコードを持って追跡せざるを得なかった.
問題の1つは次のとおりです.
もともと私はxDoclet 2の方式で注釈を書いて、antを実行してhbmの配置のスクリプトを生成して、xDoclet 1は必ずgetMethodの上で書いて、私をとても不快にさせて、xDoclet 2の注釈はfieldの上で書くことができて、コードの可読性は悪くなくて、fieldの意味の注釈は一般的にfieldの上で書いて、getMethodの上で書くことができなくて、しかしxDoclet 2はJDK 1.4の下で走って間違いを報告することができて、しかし、生成に影響を与えないのは幸いです.
こんなにくだらないことを言うと、実は元の方法がhbmプロファイルで実行されていることを示しており、annotationに書き換えた後に元のデータをキャプチャして表現行為が一致することを望んでいる.
まずJDK 1.4の場合についてお話しします.
実行環境Hibernate 3.2多対一で生成された構成例は、user.hbm.xml(test.User)であると仮定する.

この構成のデフォルトでは、次のコードを分析します.

User user = userManager.findByName("diablo3");
Group group = user.getGroup();
group.getGroupId();
group.getName();

まず2行目はselectを生成しません...from group WHERE group_id=? を選択します.
重要なのは、3行目も生成されず、4行目までこのsqlがトリガーされます.
Group.javaに次のコードがある場合、奇妙な現象があります.

public String getGroupIdTest01() {
    return groupId;
}
public String getGroupIdTest02() {
    return this.getGroupId();
}

そして同じ条件でuser.getGroupIdTest 01()とuser.getGroupIdTest 02()を実行するとsql文の生成がトリガーされ、気絶するでしょう.しばらく伏線をして、この奇妙な現象については議論しません.
まず、このsqlをトリガーしない用途と適用シーンを紹介します.
例えば、あるページにユーザリストを表示する(ページを書く方式でもJSONシーケンス化でも同じ)、最後の列にボタンがあり、ボタンの文字が「所属するグループの詳細を表示する」である場合、このボタンがトリガーするイベントはポップアップウィンドウまたはURLジャンプである可能性があり、一般的にはパラメータgroupId=xxxを持って表示するグループIDを特定し、このとき,データキャプチャの深さは明らかにgroupの他の情報を必要としないが,上記のsqlをトリガするとN+1の性能問題(これもタイトルの「巨大」の由来)が発生するに違いないが,実際にグループの他の情報を事前に知っていれば,このページの表示方法はまったく他の戦略である.すなわち、user.getGroup Id()を呼び出して新しいsqlが発生しないことを確保することが重要であり、多くのトラブルを減らすことができる.
それからJDK 1.5の下で、私が新しい例を書く時:

@Id
@Column(name = "group_id")
private String groupId;

従来の戦略に問題がないことを確保するために、わざわざ実験結果を出したところ、user.getGroup().getGroup Id()を呼び出したときにsqlがトリガーされ、気絶中だったのですが、どうしたらいいのでしょうか.ああ、気がふさいで数分後、hibernateソースコードを工事にコピーしてデバッグを開始します.
上がってすぐにinterface LazyInitializerに直行しました.多分ここでやっている鬼です.まずdebugモードでついてみると、CGLIB強化モデルの中にLazyInitializerの対象があることがわかります.だから自然なctrl+tです.LazyInitializerの実現類がいくつあるかを見て、CGLIBLazyInitializerを基本的にロックして、その中のinvoke方法を考察します.次の方法でorg.hibernate.proxy.pojo.BasicLazyInitializerに追跡します.
protected final Object invoke(Method method, Object[] args, Object proxy)
これがgroup.getGroupId()を呼び出して実行時に呼び出すエージェントメソッドであることは基本的に知っていますが、そのうちの1つを見つけます.

else if ( isUninitialized() && method.equals(getIdentifierMethod) ) {
    return getIdentifier();
}

ああ、
本来は、「現在のエージェントオブジェクトデータがロードされていない」「呼び出される方法がidのgetMethodである」と判断し、これを満たすと、直接idを取得できるMethodを返す.すなわち,これがsql生成をトリガしない実装コードの所在地であり,エージェントオブジェクトはこの時点でidを持っているに違いなく,データベースに読む必要はない.前に言った「怪奇現象」は、このコードを見ると、idのgetMethodだけがsqlをトリガーしないことがわかります.
印刷文を加えると,新しい実験でgetidentifierMethodが空であることが判明し,この条件が満たされていないと判断し,飛び越えた.
次に、getIdentifiierMethodがnullである理由を追跡し続け、CGLIBLazyInitializerのprivate構造方法についてから、CGLIBProxyFactoryのpostInstantiate方法を見つけます.
この場合はちょっと厄介ですが、このクラスには3つの引用があり、位置決めが面倒で、15分かけてデバッグを繰り返し、org.hibernate.tuple.entity.PojoEntityTuplizerに位置決めするbuildProxyFactoryメソッドがあり、このメソッドの入力パラメータGetter idGetterがgetidentifierMethodを空にした元凶であることを発見し、印刷しました.届いたGetterクラスがorg.hibernate.property.DirectPropertyAccessorであることを発見しました.この時私はもうついていませんでした.少し考えましたが、基本的に分かりました.すぐにannotationの位置を変えて、getGroup Idメソッドの前に置きました.

@Id
@Column(name = "group_id")
public String getGroupId() {
    return groupId;
}

問題は解決して、後で資料を調べて状況を確認して、もとはannotationはfieldの上で書いて、hibenateはデフォルトでアクセス方式が“field”だと思って、getMethodの上で書くならば、アクセス方式は“property”です.アクセス方式が「field」だと、前に言った「idGetter」が手に入らず、アクセス方式が「property」だと手に入るので、エージェントオブジェクトのgetGroupId()メソッドを呼び出すときにgetIdentifiierMethodが空でないと判断してhibernateが表示されます.ああ、あなたはidのgetMethodを調整しているので、IDを直接あげましょう.やはりhibernateはスマートではなく、methodの名前を直接通して、fieldの名前を逆さまに出すことをサポートすべきです.もちろんこのmethodはgetMethodの特性を満たさなければなりません(戻りタイプはidのfieldタイプと一致し、この方法にはパラメータがありません)、このfieldの名前とidの名前が等しいと判断し、idのgetMethodを呼び出していると判断すればいいのでしょうか.
新しい問題はannotationをgetMethodに書きたくないことです.どうすればいいですか.次のように書き直せばいいです.

@Id
@AccessType(value = "property")//    
@Column(name = "group_id")//           ,             group_id
private String groupId;

IDがこのように書けばいいので、他の属性は書かなくてもいいです.公式ドキュメントではfield読み取り方式とproperty方式を混用してはいけないと言っています.相手にしなくてもいいです.
これで、問題が解決し、皆さんに役に立つことを願っています.
ちょっとまとめてみます.
1.従来のhbmプロファイルはannotationのデフォルトアクセスとは異なり、コメントを書くときにIDをproperty方式で指定します.
2.IDがproperty方式である場合、hibernateはgetIdentifiierMethodを見つけることができ、idを直接返すことができ、sqlをトリガーしません.
3.このsqlを触発しない特性自体は、皆さんも多く利用すべきで、考え方は伝統的なsqlプログラミング方式と比較的に近い:この外部キーIDだけが必要で、他の表の詳細なデータは必要ありません.