JPA OneToOne関係を遅延させるにはどうすればよいですか


212

私たちが開発しているこのアプリケーションでは、ビューが特に遅いことに気づきました。ビューのプロファイルを作成したところ、データベースにフェッチするオブジェクトが2つしかない場合でも、hibernateによって1つのクエリが実行され、10秒かかったことがわかりました。すべてOneToManyManyToMany関係は怠惰だったので、それは問題ではありませんでした。実行中の実際のSQLを調べたところ、クエリに80を超える結合があることに気付きました。

さらに問題を調査したところ、問題はエンティティクラスの深い階層OneToOneManyToOneエンティティクラス間の関係が原因であることがわかりました。したがって、私はそれらを遅延フェッチするだけで問題を解決できると思いました。しかし、注釈を付けるか@OneToOne(fetch=FetchType.LAZY)@ManyToOne(fetch=FetchType.LAZY)機能しないようです。例外が発生するか、実際にはプロキシオブジェクトに置き換えられないため、遅延が発生します。

これを機能させる方法はありますか?persistence.xml関係や構成の詳細を定義するためにを使用していないことに注意してください。すべてはJavaコードで行われます。

回答:


218

まず、KLEの回答の説明をいくつか示します。

  1. 制約のない(null可能)1対1の関連付けは、バイトコードのインストルメンテーションなしではプロキシできない唯一の関連付けです。これは、所有者エンティティが関連プロパティにプロキシオブジェクトまたはNULLを含める必要があるかどうかを認識しなければならず、通常、共有PKを介して1対1でマッピングされているため、ベーステーブルの列を見ても判別できないためです。とにかく積極的にフェッチして、プロキシを無意味にする必要があります。これは、より詳細な説明です。

  2. 多対1の関連付け(および1対多)は、この問題の影響を受けません。所有者エンティティは自身のFKを簡単にチェックでき(1対多の場合、空のコレクションプロキシが最初に作成され、オンデマンドで入力されます)、関連付けは遅延する可能性があります。

  3. 1対1を1対多で置き換えることは、ほとんど決して良い考えではありません。あなたはそれをユニークな多対一で置き換えることができますが、他の(おそらくより良い)オプションがあります。

Rob H.は有効なポイントを持っていますが、モデルによっては(たとえば、1対1の関連付け null可能である場合など)実装できない場合があります。

さて、元の質問に関する限り:

A)@ManyToOne(fetch=FetchType.LAZY)正常に動作するはずです。クエリ自体で上書きされていませんか?join fetchHQLで指定したり、クラスアノテーションよりも優先されるCriteria APIを使用してフェッチモードを明示的に設定したりすることができます。そうでない場合でも問題が解決しない場合は、クラス、クエリ、結果のSQLを投稿して、より的確な会話を行ってください。

B)@OneToOneトリッキーです。それが明らかにnull可能ではない場合は、Rob H.の提案に従い、そのように指定します。

@OneToOne(optional = false, fetch = FetchType.LAZY)

それ以外の場合で、データベースを変更できる(外部キー列を所有者テーブルに追加する)ことができる場合は、変更して「結合」としてマップします。

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
public OtherEntity getOther()

そしてOtherEntityで:

@OneToOne(mappedBy = "other")
public OwnerEntity getOwner()

それができない場合(そして熱心なフェッチに対応できない場合)は、バイトコードインスツルメンテーションが唯一の選択肢です。私は同意する必要がCPerkinsただし、 -あなたが持っている場合は80を!!! 熱心なOneToOneアソシエーションのために参加すると、これより大きな問題が発生します:-)


多分別のオプションがあるかもしれませんが、私はそれを個人的にテストしていません。制約のない側では、のone-to-oneような式でa を使用しselect other_entity.id from other_entity where id = other_entity.idます。もちろん、これはクエリのパフォーマンスには理想的ではありません。
フレデリック

1
optional = false、私には機能しません。@OneToOne(fetch = FetchType.LAZY、mappedBy = "fundSeries"、optional = false)プライベートFundSeriesDetailEntity FundSeriesDetail;
Oleg Kuts

21

null可能な1対1のマッピングで遅延読み込みを機能させるには、Hibernateにコンパイル時のインストルメンテーションを実行させ@LazyToOne(value = LazyToOneOption.NO_PROXY)、1対1の関係にを追加する必要があります。

マッピングの例:

@OneToOne(fetch = FetchType.LAZY)  
@JoinColumn(name="other_entity_fk")
@LazyToOne(value = LazyToOneOption.NO_PROXY)
public OtherEntity getOther()

Antビルドファイル拡張子の例(Hibernateコンパイル時の計測を行うため):

<property name="src" value="/your/src/directory"/><!-- path of the source files --> 
<property name="libs" value="/your/libs/directory"/><!-- path of your libraries --> 
<property name="destination" value="/your/build/directory"/><!-- path of your build directory --> 

<fileset id="applibs" dir="${libs}"> 
  <include name="hibernate3.jar" /> 
  <!-- include any other libraries you'll need here --> 
</fileset> 

<target name="compile"> 
  <javac srcdir="${src}" destdir="${destination}" debug="yes"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </javac> 
</target> 

<target name="instrument" depends="compile"> 
  <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </taskdef> 

  <instrument verbose="true"> 
    <fileset dir="${destination}"> 
      <!-- substitute the package where you keep your domain objs --> 
      <include name="/com/mycompany/domainobjects/*.class"/> 
    </fileset> 
  </instrument> 
</target>

3
なぜLazyToOneOption.NO_PROXYありませんかLazyToOneOption.PROXY
Telmo Marques

これは、「なぜ」に答えていないが、この事実は(「一般的なマッピング」セクションの終わりに向かって)ここにもアサートされている:vladmihalcea.com/...
DanielM

12

HibernateでXToOneを動作させる基本的な考え方は、ほとんどの場合、それらが遅延ではないということです。

1つの理由は、Hibernateがプロキシ(ID付き)またはnullを配置
する必要がある場合に、結合するためにとにかく他のテーブルを調べる必要があることです。データベース内の他のテーブルにアクセスするコストは非常に大きいため、その時点でそのテーブルのデータをフェッチすることもできます(レイジーでない動作)。同じテーブル。

編集:詳細については、ChssPly76の回答を参照してください。これは精度と詳細度が低く、提供するものは何もありません。ChssPly76に感謝します。


ここにいくつかの問題があります-以下に別の回答を説明とともに提供しました(多すぎるため、コメントに収まりません)
ChssPly76

8

これは私のために働いているものです(計装なしで):

@OneToOne両側で使用する代わりに@OneToMany、関係の逆の部分(との関係mappedBy)で使用します。これにより、プロパティはコレクションになりますが(List以下の例)、ゲッター内のアイテムに変換して、クライアントに対して透過的にします。

このセットアップは遅延して機能します。つまり、selectは、getPrevious()またはgetNext()が呼び出されたときにのみ行われ、呼び出しごとに1つだけ選択されます。

テーブル構造:

CREATE TABLE `TB_ISSUE` (
    `ID`            INT(9) NOT NULL AUTO_INCREMENT,
    `NAME`          VARCHAR(255) NULL,
    `PREVIOUS`      DECIMAL(9,2) NULL
    CONSTRAINT `PK_ISSUE` PRIMARY KEY (`ID`)
);
ALTER TABLE `TB_ISSUE` ADD CONSTRAINT `FK_ISSUE_ISSUE_PREVIOUS`
                 FOREIGN KEY (`PREVIOUS`) REFERENCES `TB_ISSUE` (`ID`);

クラス:

@Entity
@Table(name = "TB_ISSUE") 
public class Issue {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @Column
    private String name;

    @OneToOne(fetch=FetchType.LAZY)  // one to one, as expected
    @JoinColumn(name="previous")
    private Issue previous;

    // use @OneToMany instead of @OneToOne to "fake" the lazy loading
    @OneToMany(mappedBy="previous", fetch=FetchType.LAZY)
    // notice the type isnt Issue, but a collection (that will have 0 or 1 items)
    private List<Issue> next;

    public Integer getId() { return id; }
    public String getName() { return name; }

    public Issue getPrevious() { return previous; }
    // in the getter, transform the collection into an Issue for the clients
    public Issue getNext() { return next.isEmpty() ? null : next.get(0); }

}

7

この記事で説明したようにバイトコード拡張を使用していない限り、親側の@OneToOne関連付けを遅延フェッチすることはできません。

ただし、ほとんどの場合、@MapsIdクライアント側で使用する場合は、親側の関連付けさえ必要ありません。

@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {

    @Id
    private Long id;

    @Column(name = "created_on")
    private Date createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private Post post;

    public PostDetails() {}

    public PostDetails(String createdBy) {
        createdOn = new Date();
        this.createdBy = createdBy;
    }

    //Getters and setters omitted for brevity
}

を使用する@MapsIdと、id子テーブルのプロパティは、親テーブルの主キーの主キーと外部キーの両方として機能します。

したがって、親Postエンティティへの参照がある場合、親エンティティ識別子を使用して子エンティティを簡単にフェッチできます。

PostDetails details = entityManager.find(
    PostDetails.class,
    post.getId()
);

これにより、親側の関連付けが原因で発生する可能性のあるN + 1クエリの問題が発生しなくなりますmappedBy @OneToOne


このようにして、親から子に操作をカスケードすることはできなくなります:/
Hamdi

永続化の場合、これは追加の永続化呼び出しであり、削除の場合は、DDLカスケードを使用できます。
Vlad Mihalcea

6

ネイティブのHibernate XMLマッピングでは、constrained属性をtrueに設定して1対1のマッピングを宣言することでこれを実現できます。これに相当するHibernate / JPAアノテーションが何であるかはわかりません。ドキュメントをすばやく検索しても回答は得られませんでしたが、うまくいけば、先に進むことができます。


5
+1をお勧めします。ドメインモデルは実際にはnull値を許可する必要があるため、残念ながら、必ずしも適用できるとは限りません。アノテーションを介してこれをマッピングする適切な方法は@OneToOne(optional=false,fetch=FetchMode.LAZY)
次のとおり

私はこれを試しましたが、パフォーマンスの向上は見られませんでした。デバッガを介してhibernateの出力に多くのクエリがまだ表示されました。
P.Brian.Mackey 2017

3

ChssPly76ですでに完全に説明されているように、Hibernateのプロキシは制約のない(null可能)1対1の関連付けには役立ちませんが、インストルメンテーションのセットアップを回避するためにここで説明するトリックがあります。このアイデアは、使用したいエンティティークラスがすでに装備されていることをHibernateにだますためのものです:ソースコードで手動で装備します。それは簡単です!私はCGLibをバイトコードプロバイダーとして実装しましたが、動作します(HBMで「join」ではなく、lazy = "no-proxy"およびfetch = "select"を構成していることを確認してください)。

遅延させたい1対1のnull可能な関係が1つしかない場合、これは実際の(つまり自動)インストルメンテーションの良い代替案だと思います。主な欠点は、ソリューションが使用しているバイトコードプロバイダーに依存することです。そのため、将来バイトコードプロバイダーを変更しなければならない可能性があるため、クラスを正確にコメントしてください。もちろん、技術的な理由でモデルBeanも変更するので、これは問題ありません。


1

この質問はかなり古いですが、Hibernate 5.1.10では、より快適な新しいソリューションがいくつかあります。

遅延読み込みは、@ OneToOneアソシエーションの親側を除いて機能します。これは、HibernateがnullまたはProxyをこの変数に割り当てるかどうかを知る他の方法がないためです。この記事で見つけることができる詳細

  • 遅延読み込みバイトコード拡張をアクティブ化できます
  • または、上記の記事で説明されているように、親サイドを削除して@MapsIdでクライアントサイドを使用することもできます。このようにすると、子は親と同じIDを共有するため、親側は本当に必要ないことがわかり、親IDを知ることで子を簡単にフェッチできます。

0

リレーションが双方向であってはならない場合、@ ElementCollectionは、レイジーなOne2Manyコレクションを使用するよりも簡単です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.