4

Grails でプロキシされたオブジェクトに問題があります。私は次のものを持っていると仮定します

class Order {
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name="xxx", joinColumns = {@JoinColumn(name = "xxx")}, inverseJoinColumns = {@JoinColumn(name = "yyy")})
    @OrderBy("id")
      @Fetch(FetchMode.SUBSELECT)
      private List<OrderItem> items;
    } 

class Customer {
    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
    @JoinColumn(name = "xxx",insertable = false, nullable = false)
    private OrderItem lastItem;

    private Long lastOrderId;
}

そして、いくつかのコントローラークラス内

//this all happens during one hibernate session.
    def currentCustomer = Customer.findById(id)
//at this point currentCustomer.lastItem is a javassist proxy

def lastOrder = Order.findById(current.lastOrderId)
//lastOrder.items is a proxy
//Some sample actions to initialise collections 
lastOrder.items.each { println "${it.id}"}

反復後lastOrder.itemsも のプロキシが含まれていますcurrentCustomer.lastItem。たとえば、lastOrder.items コレクションに 4 つのアイテムがある場合、次のようになります。

  • 物体
  • 物体
  • javassist プロキシ (id フィールドを含むすべてのフィールドが null です)。これは currentCustomer.lastItem と同じオブジェクトです。
  • 物体

さらに、このプロキシ オブジェクトはすべてのプロパティが null に設定されており、getter が呼び出されたときに初期化されません。内部にプロキシがないことを確認するために、GrailsHibernateUtils.unwrapIdProxy()内部のすべての要素を手動で呼び出す必要があります (これは基本的に EAGER フェッチにつながります)。lastOrder.items

この 1 つのプロキシ オブジェクトは、テスト フェーズで追跡するのが困難ないくつかの非常に奇妙な例外につながります。

興味深い事実: 操作の順序を変更すると (注文を最初にロードし、顧客を 2 番目にロードする)、内部のすべての要素lastOrder.itemsが初期化されます。

問題は、コレクションの要素がセッションですでにプロキシされているかどうかに関係なく、コレクションに触れたときにコレクションを初期化する必要があることを Hibernate に伝える方法はありますか?

4

2 に答える 2

4

ここで起こっているのは、(HibernateのSessionインスタンスに格納されている)第1レベルのキャッシュとFetchType、関連するオブジェクトで異なるものとの間の興味深い相互作用だと思います。

ロードすると、ロードされたオブジェクトとともにキャッシュにCustomer入れられます。を持っているので、Sessionこれにはオブジェクトのプロキシオブジェクトが含まれます。Hibernateでは1つのインスタンスのみを特定のIDに関連付けることができるため、そのIDで動作する以降の操作では、常にそのプロキシが使用されます。同じように別の方法でその特定を取得するように要求した場合、それを含むものをロードする場合と同様に、セッションレベルのIDルールにより、プロキシが使用されます。OrderItemFetchType.LAZYOrderItemSessionOrderItemOrderOrder

そのため、順序を逆にすると「機能」します。最初のロード、そのOrderコレクションはFetchType.EAGER、であるため、それ(および第1レベルのキャッシュ)はのインスタンスを完全に実現していますOrderItem。次に、すでにロードされているインスタンスとprestoの1つに設定されCustomerているをロードします。プロキシではなく、実際のインスタンスがあります。lastItemOrderItemOrderItem

Hibernateマニュアルに記載されているIDルールを確認できます。

特定のセッションにアタッチされたオブジェクトの場合...データベースIDのJVMIDは、Hibernateによって保証されます。

とはいえ、プロキシを取得した場合でも、関連付けがアクティブでOrderItemある限り、プロキシは正常に機能するはずです。Sessionプロキシが「特別な」方法で処理するため(つまり、POJOではないため)、プロキシIDフィールドがデバッガーなどに入力されたものとして表示されるとは限りません。ただし、基本クラスと同じようにメソッド呼び出しに応答する必要があります。したがって、OrderItem.getId()メソッドがある場合は、呼び出されたときにIDを返す必要があります。他のメソッドでも同様です。ただし、遅延初期化されるため、これらの呼び出しの一部にはデータベースクエリが必要になる場合があります。

OrderItemここでの唯一の本当の問題は、特定のものがプロキシであるかどうかに関係なく、それを持っていることが混乱しているということである可能性があります。関係を単純に変更して、両方が怠惰になるか、両方が熱心になるようにしたいのではないでしょうか。

価値があるのは、ManyToMany関係がEAGERとして、ManyToOneがとしてあるのは少し奇妙なことですLAZY。これは通常の設定とは正反対なので、少なくとも変更することを考えます(ただし、ユースケース全体は明らかにわかりません)。それについて考える1つの方法:OrderItem完全にフェッチするのにコストがかかり、クエリを実行するときに問題になる場合は、すべてを一度Customerにロードするのもコストがかかりすぎるのではないでしょうか。または逆に、それらすべてをロードするのに十分安い場合は、確かにそれを取得したときにそれをつかむのに十分安いですか?Customer

于 2013-02-28T14:00:38.463 に答える
0

この方法または使用して熱心な読み込みを強制できると思います

def lastOrder = Order.withCriteria(uniqueResult: true) {
       eq('id', current.lastOrderId)
       items{}
   }

または「すべてフェッチ」で HQL クエリを使用する

于 2013-03-01T16:52:09.213 に答える