【问题标题】:Avoid DataNucleus joins?避免 DataNucleus 连接?
【发布时间】:2010-07-11 20:52:07
【问题描述】:

我正在尝试将 JDBC Web 应用程序迁移到 JDO DataNucleus 2.1.1。

假设我有一些看起来像这样的类:

公共类职位{ 私人整数 id; 私有字符串标题; }

公共类员工{ 私人整数 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,遍历所有员工,获得他们的职位:

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());
}

然后,当我获得 Employee's Position 时,DataNucleus 似乎会生成连接两个表的 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 =

我的理解是,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 可能不会从结果集中读取所有列,而是返回缓存的位置,但它仍然调用数据存储来访问第二个表,包括可能的磁盘查找和读取。它会抛弃这项工作这一事实并不能让人感到安慰。

我希望做的是告诉 DataNucleus 所有位置 都将被缓存,相信我。如果由于某种原因你找到了一个不是,请怪我缓存未命中。我了解您必须(透明地)在职位表上执行单独的选择。 (更好的是,固定由于缓存未命中而必须获取的任何位置。这样就不会再次在对象上发生缓存未命中。)

这就是我现在通过 DAO 使用 JDBC 所做的事情。研究持久层的原因之一是放弃这些 DAO。很难想象迁移到无法超越简单获取导致昂贵连接的持久层。

只要 Employee 不仅有一个职位,而且有一个部门和其他字段,一个 Employee 提取会导致访问六个表,即使所有这些对象都已经固定在缓存中,并且在给定的情况下是可寻址的他们的类和主键。事实上,我可以自己实现,将 Employee.position 更改为 Integer,创建 IntIdentity,并将其传递给 PersistenceManager.getObjectByID()。

我认为我听到的是 DataNucleus 无法进行这种优化。那正确吗?没关系,只是和我想的不一样。

【问题讨论】:

  • 顺便说一句,您可能不想真正抛弃 DAO。希望您可以使用 JDO 重写 DAO 层,而无需过多更改应用程序代码。

标签: jdo datanucleus


【解决方案1】:

默认情况下,从数据存储中获取 Employee 实体时不会进行连接,只有在实际读取 Employee.position 时才会进行连接(这称为延迟加载)。

此外,使用level 2 cache 可以避免第二次提取。首先检查二级缓存是否实际启用(在 DataNucleus 1.1 中默认禁用,在 2.0 中默认启用)。然后您可能应该“固定”该类,以便将其位置实体无限期地缓存:

但是,如果其他应用程序使用相同的数据库,二级缓存可能会导致问题,因此我建议仅对很少更改的类(例如位置)启用它。对于其他类,将“cacheable”属性设置为 false(默认为 true)。

编辑添加:

元数据中的 标签不适合这种情况。事实上,您根本不需要明确指定关系,DataNucleus 会从类型中找出它。但是,当您说需要在默认提取组中读取 POSITION_ID 时,您是对的。这一切都可以通过对元数据进行以下更改来实现:

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

编辑添加:

澄清一下,在进行上述元数据更改后,我运行了您提供的测试代码(由 MySQL 数据库支持),我只看到了这两个查询:

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

如果我只运行代码的第二部分(Employee 范围),那么我只看到第二个查询,根本无法访问 POSITION 表。为什么?因为 DataNucleus 最初提供“空心” Position 对象,而从 Object 继承的 Position.toString() 的默认实现不访问任何内部字段。如果我重写 toString() 方法以返回职位的标题,然后运行示例代码的第二部分,那么对数据库的调用是:

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 对象预加载到缓存中。让DN累积缓存就好了。

确实,如果您采用 JDO,您可能不得不接受一些妥协……您将不得不放弃使用手动滚动的基于 JDBC 的 DAO 获得的绝对控制权。但在这种情况下,至少你应该能够实现你想要的。它确实是 2 级缓存的典型用例之一。

【讨论】:

  • 感谢您提供有关 2 级缓存和固定的提示。我使用的是 2.1.1,所以 L2 缓存应该打开。我不确定默认类型是什么,所以我明确地将其设置为“软”。当我迭代它们时,我尝试固定每个位置。当我询问员工职位时,我仍然看到加入。
  • 谢谢。这有帮助,但看起来我无法做我想做的事。
【解决方案2】:

补充托德的回复,澄清一些事情。

  • 1-1 关系上的 标记没有任何意义。好吧,它可以解释为“创建一个连接表来存储这种关系”,但是 DataNucleus 不支持这样的概念,因为最佳实践是在所有者或相关表中使用 FK。所以删除

  • 1-1 关系上的“表”表明它存储在辅助表中,但您也不希望这样,因此请将其删除。

  • 您检索 Position 对象,因此它发出类似

SELECT 'org.datanucleus.test.Position' AS 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 检索(因为它不知道哪个 Position 对象与该 Employee 相关),所以它发出
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 的“位置”字段放入其默认提取组中,并且将在第 4 步中检索 FK,从而删除一个 SQL 调用。

【讨论】:

  • 感谢您的解释。我添加了 join 标签来修复由于 table 属性的存在而导致的错误。删除两者都很好。文档虽然非常详细和完整,但并不能胜任一本书的角色。我有两本可以找到的 JDO 书籍,但它们都过时了,以至于它们对其中的一些东西确实没有帮助。如今,所有与持久性相关的发布似乎都围绕着 Hibernate。我仍然看到一个加入(见我上面的编辑)。虽然这可能是您所期望的行为,但这不是我所期望的。
  • 想想 DataNucleus 有哪些选项。选项 1:如果 DN 刚刚检索到 FK 值,然后检查缓存并没有找到相关对象,则它必须发出另一个 SQL 来获取该对象。选项 2:如果 DN 检索到 FK 值和相关对象值(通过连接),那么如果它没有找到相关对象,则它具有实例化对象本身的所有值(因此不需要其他 SQL)。我们使用选项 2 实现了它;不能为每个人赢;-)
  • 正如我所说,我理解为什么 DataNucleus 提供了简单的提取。我正在寻找的是一种暗示开明获取的方法。我现在知道 JDO 不提供,DataNucleus 也不提供这种特定于供应商的元数据扩展(这将是选项 3)。
  • DataNucleus SVN 实际上允许元数据扩展“fetch-fk-only”(将其设置为 true),这仅获取相关对象的 FK,因此不进行连接。这显然只适用于 1-1 UNI 或 1-1 BI(非所有者)关系 - 其中 FK 在所选类的表中。可能在某个时候成为标准 JDO 的一部分,但暂时不会
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-23
  • 2014-11-18
  • 2013-06-30
  • 2013-12-18
  • 2019-09-13
  • 2018-10-24
  • 2019-08-04
相关资源
最近更新 更多