Fetch

fetchを使用すると、関連付けられたBeanでフェッチする必要があるプロパティを指定します。 *OneToOne、OneToMany、ManyToOne、ManyToMany* で関連付けられたBeanです。

Fetch は、そのパスをクエリが即時ロードし、それを SQL JOIN として実行することを 優先 することを意味します。

QCustomer cust = QCustomer.alias();
QContact cont = QContact.alias();

List<Customer> customers =
  new QCustomer()
  .select(cust.name, cust.version, cust.whenCreated)    // root level properties
  .contacts.fetch(cont.email)                           // contacts is a OneToMany path

  .name.istartsWith("Rob")
  .findList();

結果のSQLは

select t0.id, t0.name, t0.version, t0.when_created,    -- customer columns
       t1.id, t1.email                                 -- contact columns
from customer t0
left join contact t1 on t1.customer_id = t0.id
where lower(t0.name) like ? escape'|' order by t0.id

contacts パスは OneToMany であるため、連絡先テーブルへの left join が取得されます。連絡先のidとemailプロパティのみをフェッチします。

クエリ式APIを使用して記述された上記のクエリは

List<Customer> customers =
  database.find(Customer.class)
    .select("name, version, whenCreated")    // root level properties
    .fetch("contacts", "email")              // contacts is a OneToMany path

    .where()
    .istartsWith("name", "Rob")
    .findList();

Fetchのバリエーション - fetch、fetchQuery、fetchCache、fetchLazy

fetchには4つのバリエーションがあります。それらの概要は次のとおりです。

  • fetch - SQL JOINを優先して即時フェッチ
  • fetchQuery - 別のセカンダリSQLクエリを使用して即時フェッチ
  • fetchCache - L2キャッシュをヒットして即時フェッチ。キャッシュミスは別のセカンダリSQLクエリを使用してフェッチ
  • fetchLazy - 遅延フェッチ

Ebeanは、ORMクエリをSQLクエリに変換するときにルールを適用します。

  • EbeanはSQLデカルト積を生成しません
  • EbeanはSQLでmaxRowsを尊重します

Ebeanは、これらのルールを遵守するために、*fetch*パスを自動的に*fetchQuery*パスに変換します。事実上、SQLクエリに含めることができるOneToManyおよびManyToManyパスの数には制限があります(それ以外の場合は、望ましくないSQLデカルト積が発生するため)。

FetchQuery

fetchQuery を指定すると、このパスを即時フェッチしたいが、別のSQLクエリ(「セカンダリクエリ」または「クエリ結合」と呼ばれる)を使用して実行したいことをEbeanに伝えています。

カーディナリティや結果の相対的な幅などに基づいてSQL結合とクエリ結合の相対的なコストを把握しているため、特定のパスでこれを選択します。

List<Order> orders =
  new QOrder()
    .customer.fetchQuery()                // fetch customer (and children) using a "secondary query"
    .customer.billingAddress.fetch()
    .customer.shippingAddress.fetch()
    .findList();

上記のクエリでは、2番目の個別のSQLクエリを使用して、fetchQueryを使用して顧客を請求先と配送先の住所とともに明示的にフェッチします。

プライマリクエリ
-- "primary" query fetches the orders only
select t0.id, ...
from orders t0
セカンダリクエリ(別名「クエリ結合」)
-- "secondary" query fetches customers with
-- their billing and shipping addresses

select t0.id, t0.inactive, t0.name, ...,                         -- customer
       t1.id, t1.line1, t1.line2, t1.city, ...,                  -- customers billing address
       t2.id, t2.line1, t2.line2, t2.city, ...                   -- customers shipping address
from customer t0
join address t1 on t1.id = t0.billing_address_id
join address t2 on t2.id = t0.shipping_address_id
where t0.id in (?, ?, ... )                                      -- customer ids

FetchCache

fetchCache はfetchQueryに似ていますが、最初にL2キャッシュをヒットします。

List<Order> orders =
  new QOrder()
    .customer.fetchCache()         // Hit L2 cache for customer, for cache miss load from database
    .findList();

上記のクエリでは、データベースから注文をフェッチします。Customerの場合は、L2キャッシュをヒットして顧客を検索し、キャッシュミスが発生した場合は、データベースから顧客をロードします。これは、一度に100人の顧客をまとめて実行されます。

FetchLazy

fetchLazy を使用すると、このパスを即時フェッチする必要はありません。ただし、遅延ロードする場合は、遅延ロードクエリがフェッチする内容を最適化します。

例:顧客を遅延ロードする場合は、名前のみをフェッチします。
List<Order> orders =
  new QOrder()
    .fetchLazy("customer", "name")
    .findList();

...

// invoke lazy loading will only fetch customer name
customer.getName();
例:顧客を遅延ロードする場合は、さらに請求先住所と配送先住所をフェッチします。
QCustomer cust = QCustomer.alias();

List<Order> orders =
  new QOrder()
    .fetchLazy("customer")
    .fetch("customer.billingAddress")  // fetched with lazy load of customer
    .fetch("customer.shippingAddress") // fetched with lazy load of customer

    .findList();


...

// invoke lazy loading will fetch customer name + billingAddress + shippingAddress
customer.getName();

エイリアスクエリビーンズ

クエリビーンズを使用する場合、selectfetch の両方のプロパティを提供するためにaliasビーンズを使用します。

// "alias" beans for customer and contact which
// we use to specify the properties to select and fetch
QCustomer cust = QCustomer.alias();
QContact con = QContact.alias();

// using query beans
List<Customer> customers =
  new QCustomer()
  .select(cust.name, cust.version, cust.whenCreated)    // customer properties only
  .contacts.fetch(con.email)                            // contact properties only

  .name.istartsWith("Rob")
  .findList();

 

Kotlinエイリアスクエリビーンズ

Kotlinの場合、alias ビーンズはクエリビーンのコンパニオンオブジェクトであり、_aliasを介してアクセスします。それ以外の場合、クエリはKotlinで同じです。

val cust = QCustomer._alias
val con = QContact._alias

Select + Fetch = プロジェクションクエリのチューニング

selectfetch を使用して、オブジェクトグラフのどの部分がロードされるかを制御することにより、クエリを効果的に調整しています。

一般的に、クエリはデータベースから必要なものだけをロードすることを推奨します。これにより、クエリのコスト、フェッチおよびネットワーク経由でプルされるデータ量を削減できるだけでなく、カバリングインデックスなどを使用する機会も得られます。

select -> "グラフのルートレベルのプロパティ"

select は、オブジェクトグラフのルートレベルでフェッチするものを定義します。

fetch -> "グラフのリーフのプロパティ"

fetch は、オブジェクトグラフのリーフでフェッチするものを定義します。これは、オブジェクトグラフのOneToOne、OneToMany、ManyToOne、ManyToManyパスでフェッチするものに変換されます。

FetchGroupselect + fetchの代替手段を提供することに注意してください。

ORMクエリからSQLクエリへのルール

Ebeanは、ORMクエリをSQLクエリに変換するときに2つのルールを適用します。これらのルールは、ORMクエリをSQLクエリに変換するときに許可するSQL結合の数を制限する効果があります。これら2つのルールは次のとおりです。

  • EbeanはSQLデカルト積を生成しません
  • EbeanはSQLでmaxRowsを尊重します

ルール1:SQLデカルト積なし

このルールは、EbeanがOneToManyパスまたはManyToManyパスへの結合を最大1つ許可することを意味します。ORMクエリにそのようなパスが複数ある場合、ORMクエリは複数のSQLクエリとして変換および実行されます。

SQLデカルト積は、非常に悪いSQLクエリを生成するリスクが高いため、避けます。

ルール2:常にSQLでmaxRowsを尊重する

このルールは、maxRowsがある場合、EbeanがOneToManyパスまたはManyToManyパスへの結合を含めることができないことを意味します。これは、SQLのmaxRowsが行に対して機能するため、カーディナリティが1を超えるパスへのSQL結合を含めることができないためです。

maxRowsをSQLで尊重することは、データベースが通常、はるかに優れたクエリプランとより効率的なSQLクエリを提供できるため、重要です。

Ebeanは、どのパスが「ToMany」パスであるかを検出し、それらのパスをfetchパスからfetchQueryパスに変換する必要があるかどうかを判断します。Ebeanは最初に@FetchPreferenceマッピングを考慮し、次にクエリでパスが指定されている順序を考慮します。つまり、一般的に、最初のToManyパスはfetchのままになり、他のToManyパスはfetchQueryに変換されます。