2

JDBC Web アプリケーションを JDO DataNucleus 2.1.1 に移行しようとしています。

次のようなクラスがいくつかあるとします。

public class Position { プライベート整数 ID; プライベート文字列のタイトル。}

public class Employee { プライベート Integer id; プライベート文字列名; プライベート ポジション ポジション; }

Position SQL テーブルの内容は、あまり頻繁に変更されることはありません。JDBC を使用して、テーブル全体をメモリに読み込みます (定期的に、または必要に応じて更新できます)。次に、Employee をメモリに読み込むときに、Employee テーブルから位置 ID を取得し、それを使用してメモリ内の Position インスタンスを取得します。

ただし、DataNucleus を使用して、すべての位置を反復すると、次のようになります。

Extent<Position> extent =pm.getExtent(Position.class, true);
Iterator<Position> iter =extent.iterator();
while(iter.hasNext()) {
   Position position =iterPosition.next();
   System.out.println(position.toString());
}

その後、別の PersistenceManager を使用して、すべての Employees を反復処理し、Position を取得します。

Extent<Employee> extent =pm.getExtent(Employee.class, true);
Iterator<Employee> iter =extent.iterator();
while(iter.hasNext()) {
   Employee employee =iter.next();
   System.out.println(employee.getPosition());
}

次に、従業員の職位を取得すると、DataNucleus は 2 つのテーブルを結合する SQL を生成するように見えます。

SELECT A0.POSITION_ID,B0.ID,B0.TITLE FROM MYSCHEMA.EMPLOYEE A0 LEFT OUTER JOIN MYSCHEMA."POSITION" B0 ON A0.POSITION_ID = B0.ID WHERE A0.ID = <1>

私の理解では、利用可能な場合、DataNucleus はキャッシュされた Position インスタンスを使用します。(そうですか?)しかし、結合によってパフォーマンスが低下することが懸念されます。ベンチマークを実行するにはまだ十分ではありません。私の恐れは見当違いですか?続行してベンチマークする必要がありますか? DataNucleus に結合を回避させる方法はありますか?

<jdo>
<package name="com.example.staff">
    <class name="Position" identity-type="application" schema="MYSCHEMA" table="Position">
        <inheritance strategy="new-table"/>
        <field name="id" primary-key="true">
            <column name="ID" jdbc-type="integer"/>
        </field>
        <field name="title">
            <column name="TITLE" jdbc-type="varchar"/>
        </field>
    </class>
</package>
</jdo>

<jdo>
<package name="com.example.staff">
    <class name="Employee" identity-type="application" schema="MYSCHEMA" table="EMPLOYEE">
        <inheritance strategy="new-table"/>
        <field name="id" primary-key="true">
            <column name="ID" jdbc-type="integer"/>
        </field>
        <field name="name">
            <column name="NAME" jdbc-type="varchar"/>
        </field>
        <field name="position" table="Position">
            <column name="POSITION_ID" jdbc-type="int" />
            <join column="ID" />
        </field>
    </class>
</package>
</jdo>

私ができることを望んでいるのは、DataNucleus に先に進み、デフォルトのフェッチ グループの一部として POSITION_ID int を読み取り、対応する位置が既にキャッシュされているかどうかを確認するように指示することだと思います。その場合は、そのフィールドを設定します。そうでない場合は、必要に応じて後で参加します。さらに良いことに、その int ID をどこかに隠しておき、後で getPosition() が呼び出されたときにそれを使用します。これにより、すべての場合で結合が回避されます。

クラスと主キーの値を知っていれば、素朴なケースを回避するのに十分だと思いますが、DataNucleus についてはまだ十分に知りません。


いただいた有益なフィードバックにより、私の .jdo はクリーンアップされました。ただし、POSITION_ID フィールドをデフォルトのフェッチ グループに追加した後も、まだ参加しています。

SELECT 'com.example.staff.Employee' AS NUCLEUS_TYPE,A0.ID,A0."NAME",A0.POSITION_ID,B0.ID,B0.TITLE FROM MYSCHEMA.EMPLOYEE A0 LEFT OUTER JOIN MYSCHEMA."POSITION" B0 ON A0.POSITION_ID = B0.ID

なぜそうしているのか理解しています。素朴な方法は常に機能します。私はそれがもっと能力があることを望んでいました。DataNucleus は結果セットからすべての列を読み取るのではなく、キャッシュされた位置を返す場合がありますが、データストアを呼び出して 2 番目のテーブルにアクセスし、それに伴うすべての処理 (ディスクのシークや読み取りを含む) を行います。それがその仕事を放棄するという事実は、ほとんど慰めにはなりません。

私がやりたかったことは、すべての位置がキャッシュされることを DataNucleus に伝えることでした。それを信じてください。何らかの理由でそうでないものを見つけた場合は、キャッシュミスのせいにしてください。Position テーブルで別の選択を (透過的に) 実行する必要があることを理解しています。(さらに良いのは、キャッシュ ミスのために取得する必要があるすべての位置を固定することです。そうすれば、オブジェクトでキャッシュ ミスが再び発生することはありません。)

それが、DAO を介して JDBC を使用して現在行っていることです。永続層を調査する理由の 1 つは、これらの DAO を捨てることでした。単純なフェッチを超えて移動できない永続レイヤーに移動することを想像するのは困難であり、結果として高価な結合が発生します。

Employee が Position だけでなく Department やその他のフィールドを持つとすぐに、Employee fetch によって半ダースのテーブルがアクセスされますが、これらのオブジェクトはすべてキャッシュに固定されており、クラスとクラスを指定してアドレス指定できます。主キー。実際、これを自分で実装して、Employee.position を Integer に変更し、IntIdentity を作成して、それを PersistenceManager.getObjectByID() に渡すことができます。

私が聞いていると思うのは、DataNucleus はこの最適化ができないということです。そうですか?大丈夫です、私が期待したものではありません。

4

2 に答える 2

2

デフォルトでは、Employee エンティティがデータストアからフェッチされるときに結合は実行されず、Employee.position が実際に読み取られるときにのみ実行されます (これは遅延読み込みと呼ばれます)。

さらに、この 2 回目のフェッチは、レベル 2 キャッシュを使用して回避できます。まず、レベル 2 キャッシュが実際に有効になっていることを確認します (DataNucleus 1.1 ではデフォルトで無効になっていますが、2.0 ではデフォルトで有効になっています)。その後、Position エンティティが無期限にキャッシュされるように、クラスを「固定」する必要があります。

ただし、レベル 2 キャッシュは、他のアプリケーションが同じデータベースを使用する場合に問題を引き起こす可能性があるため、めったに変更されない Position などのクラスに対してのみ有効にすることをお勧めします。他のクラスについては、「キャッシュ可能」属性を false に設定します (デフォルトは true)。

追加するために編集:

メタデータの <join> タグは、この状況には適していません。実際、リレーションシップを明示的に指定する必要はまったくありません。DataNucleus は型からそれを把握します。しかし、デフォルトのフェッチ グループで POSITION_ID を読み取る必要があると言うのは正しいです。これはすべて、メタデータを次のように変更することで実現できます。

<field name="position" default-fetch-group="true">
    <column name="POSITION_ID" jdbc-type="int" />
</field>

追加するために編集:

明確にするために、上記のメタデータの変更を行った後、提供された (MySQL データベースに基づく) テスト コードを実行したところ、次の 2 つのクエリのみが表示されました。

SELECT 'com.example.staff.Position' AS NUCLEUS_TYPE,`THIS`.`ID`,`THIS`.`TITLE` FROM `POSITION` `THIS` FOR UPDATE
SELECT 'com.example.staff.Employee' AS NUCLEUS_TYPE,`THIS`.`ID`,`THIS`.`NAME`,`THIS`.`POSITION_ID` FROM `EMPLOYEE` `THIS` FOR UPDATE

コードの 2 番目の部分 (Employee エクステント) のみを実行すると、2 番目のクエリのみが表示され、POSITION テーブルにはまったくアクセスできません。なんで?DataNucleus は最初に「中空」の Position オブジェクトを提供し、Object から継承された Position.toString() のデフォルトの実装は内部フィールドにアクセスしないためです。toString() メソッドをオーバーライドして役職のタイトルを返し、サンプル コードの 2 番目の部分を実行すると、データベースへの呼び出しは次のようになります。

SELECT 'com.example.staff.Employee' AS NUCLEUS_TYPE,`THIS`.`ID`,`THIS`.`NAME`,`THIS`.`POSITION_ID` FROM `EMPLOYEE` `THIS` FOR UPDATE
SELECT `A0`.`TITLE` FROM `POSITION` `A0` WHERE `A0`.`ID` = <2> FOR UPDATE
SELECT `A0`.`TITLE` FROM `POSITION` `A0` WHERE `A0`.`ID` = <1> FOR UPDATE

(など、Position エンティティごとに 1 つのフェッチ)。ご覧のとおり、結合は実行されていません。そのため、あなたの経験が異なると聞いて驚いています。

キャッシングがどのように機能するかについての説明についてはクラスが固定されているときにレベル 2 キャッシュが機能する方法です。実際、アプリケーションの起動時に、Position オブジェクトをキャッシュにプリロードしようとすることさえ気にしません。DNにそれらを累積的にキャッシュさせてください。

JDO を採用する場合、いくつかの妥協を受け入れる必要があるかもしれないことは事実です...手動でロールされた JDBC ベースの DAO で得られる絶対的な制御を放棄する必要があります。しかし、この場合、少なくともあなたが望むものを達成できるはずです。これは、レベル 2 キャッシュの典型的な使用例の 1 つです。

于 2010-07-12T06:51:03.650 に答える
1

いくつかのことを明確にするために、Todd の返信に追加します。

  • 1 対 1 の関係の <join> タグは意味がありません。「このリレーションシップを格納する結合テーブルを作成する」と解釈することもできますが、DataNucleus はそのような概念をサポートしていません。ベスト プラクティスは、所有者テーブルまたは関連テーブルのいずれかで FK を使用することだからです。したがって、 <join> を削除します

  • 1対1の関係の「テーブル」は、それがセカンダリテーブルに保存されていることを示唆していますが、それも必要ないため、削除してください。

  • Position オブジェクトを取得するため、次のようなものを発行します

「org.datanucleus.test.Position」を NUCLEUS_TYPE,A0.ID,A0.TITLE FROM "POSITION" A0 として選択
  • Employee オブジェクトを取得するため、次のようなものを発行します
SELECT 'org.datanucleus.test.Employee' AS NUCLEUS_TYPE,A0.ID,A0."NAME" FROM EMPLOYEE A0

このフィールドはデフォルトのフェッチ グループ (遅延ロード) にないため、ここでは位置の FK を取得しないことに注意してください。

  • Employee オブジェクトの position フィールドにアクセスするため、FK を取得する必要があります (この Employee に関連する Position オブジェクトがわからないため)。
SELECT A0.POSITION_ID,B0.ID,B0.TITLE FROM EMPLOYEE A0 LEFT OUTER JOIN "POSITION" B0 ON A0.POSITION_ID = B0.ID WHERE A0.ID = ?

この時点では、Position オブジェクトは (キャッシュ内に) 既に存在するため取得する必要はなく、そのオブジェクトが返されます。

これはすべて予想される動作です。Employee の「position」フィールドをデフォルトのフェッチ グループに入れることができ、その FK は手順 4 で取得されるため、1 つの SQL 呼び出しが削除されます。

于 2010-07-13T06:07:18.307 に答える