Избегать присоединения DataNucleus? - PullRequest
2 голосов
/ 12 июля 2010

Я экспериментирую с перемещением веб-приложения JDBC в JDO DataNucleus 2.1.1.

Предположим, у меня есть несколько классов, которые выглядят примерно так:

public class Position {private Integer id;частное название строки;}

открытый класс Employee {private Integer id;личное строковое имя;личная позиция;}

Содержимое таблицы Position SQL действительно не очень часто меняется.Используя JDBC, я считываю всю таблицу в память (с возможностью периодического обновления или по желанию).Затем, когда я читаю Employee в память, я просто извлекаю идентификатор позиции из таблицы Employee и использую его для получения экземпляра позиции в памяти.

Однако, используя 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());
}

Затем DataNucleus создает SQL, соединяющий две таблицы, когда я получаюПозиция сотрудника:

ВЫБЕРИТЕ A0.POSITION_ID, B0.ID, B0.TITLE ИЗ MYSCHEMA.EMPLOYEE A0 ВНЕШНИЙ ВСТУПИТЬ В МИШЕМУ.>

Насколько я понимаю, 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, что все позиции будут кэшироваться, поверьте мне в этом.И если по какой-то причине вы найдете такой, которого нет, обвините меня в промахе.Я понимаю, что вам придется (прозрачно) выполнить отдельный выбор в таблице позиций.(Еще лучше, закрепите все позиции, которые вам нужно получить из-за пропуска кэша. Таким образом, вы не сможете снова пропустить кэш-память по объекту.)

Это то, что я сейчас делаю, используяJDBC, посредством DAO.Одной из причин исследования персистентного слоя было прекращение этих DAO.Трудно представить переход на слой постоянства, который не может выйти за пределы наивных извлечений, что приводит к дорогостоящим объединениям.

Как только у сотрудника есть не только должность, но и отдел, и другие поля, выборка сотрудника.вызывает доступ к полдюжине таблиц, даже если все эти объекты уже закреплены в кэше и являются адресуемыми с учетом их класса и первичного ключа.Фактически я могу реализовать это самостоятельно, изменив Employee.position на Integer, создав IntIdentity и передав его в PersistenceManager.getObjectByID ().

Я думаю, что слышу, что DataNucleus не способен на эту оптимизацию.Это правильно?Это нормально, только не то, что я ожидал.

Ответы [ 2 ]

2 голосов
/ 12 июля 2010

По умолчанию объединение не выполняется при извлечении сущности Employee из хранилища данных, оно выполняется только при фактическом чтении Employee.position (это называется отложенной загрузкой).

Кроме того, этой второй выборки можно избежать, используя кэш 2-го уровня . Сначала проверьте, что кэш уровня 2 действительно включен (в DataNucleus 1.1 он отключен по умолчанию, в 2.0 он включен по умолчанию). Вы, вероятно, должны затем «закрепить» класс так, чтобы сущности Position его кэшировали бесконечно:

Однако кэш уровня 2 может вызвать проблемы, если другие приложения используют ту же базу данных, поэтому я рекомендую включить ее только для таких классов, как Position, которые редко изменяются. Для других классов установите для атрибута «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

Если я запускаю только вторую часть кода (экстент сотрудника), тогда я вижу только второй запрос без какого-либо доступа к таблице POSITION вообще. Зачем? Поскольку DataNucleus изначально предоставляет «полые» объекты Position, а реализация по умолчанию Position.toString (), унаследованная от Object, не имеет доступа к внутренним полям. Если я переопределю метод 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

(и так далее, один выбор для каждой позиции). Как вы видите, соединения не выполняются, и поэтому я удивлен, узнав, что у вас другой опыт.

Что касается вашего описания того, как, как вы надеетесь, должно работать кэширование, то - это , как должен работать кэш 2-го уровня, когда класс закреплен. На самом деле, я бы даже не стал пытаться предварительно загружать объекты Position в кеш при запуске приложения. Просто позвольте DN кешировать их кумулятивно.

Это правда, что вам, возможно, придется пойти на некоторые компромиссы, если вы примете JDO ... вам придется отказаться от абсолютного контроля, который вы получаете с DAO на основе JDBC. Но в этом случае, по крайней мере, вы сможете достичь того, чего хотите. Это действительно один из архетипических вариантов использования кеша уровня 2.

1 голос
/ 13 июля 2010

Добавление к ответу Тодда, чтобы уточнить несколько вещей.

  • Тег в отношении 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, поэтому ему нужно получить FK (поскольку он не знает, какой объект 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, поскольку он уже присутствует (в кэше), поэтому этот объект возвращается.

Все это ожидаемое поведение ИМХО. Вы можете поместить поле «Position» Employee в его группу выборок по умолчанию, и этот FK будет получен на шаге 4, что приведет к удалению одного вызова SQL.

...