Как управлять генерацией PK с помощью Cayenne 4.0 + PostgreSQL 9.4

У меня есть:

  • Постгрес SQL 9.4
  • Апач Кайен 4.0.М3
  • Схема, состоящая из одной простейшей таблицы «проба»:

    CREATE TABLE proba (id bigint NOT NULL, переменный символ значения (255), CONSTRAINT proba_pkey PRIMARY KEY (id))

  • Простой основной метод:

    public static void main(String[] args) {
        ServerRuntime runtime = ServerRuntimeBuilder.builder()
                .addConfig("cayenne-project.xml")
                .build();
    
        ObjectContext ctx = runtime.newContext();
    
        CayenneDataObject newObject = new CayenneDataObject();
        newObject.writeProperty("value", "proba1");
        ctx.registerNewObject(newObject);
        ctx.commitChanges();
    }
    
  • Простой cayenne-project.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <domain project-version="7">
        <map name="datamap"/>
        <node name="datanode" 
              factory="org.apache.cayenne.configuration.server.XMLPoolingDataSourceFactory" 
              schema-update-strategy="org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy">
            <map-ref name="datamap"/>
            <data-source>
                  ....
            </data-source>
        </node>
    </domain>
    
  • Простой datamap.map.xml (сделанный вручную):

    <?xml version="1.0" encoding="utf-8"?>
    <data-map xmlns="http://cayenne.apache.org/schema/7/modelMap"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://cayenne.apache.org/schema/7/modelMap http://cayenne.apache.org/schema/7/modelMap.xsd"
              project-version="7">
        <property name="defaultPackage" value="ru.xxx"/>
        <property name="defaultSchema" value="public"/>
        <db-entity name="proba" schema="public">
            <db-attribute name="id" type="BIGINT" isPrimaryKey="true" isGenerated="false" length="19"/> 
            <db-attribute name="value" type="VARCHAR" length="255"/>
        </db-entity>
        <obj-entity name="Proba" dbEntityName="proba">
            <obj-attribute name="value" type="java.lang.String" db-attribute-path="value"/>
        </obj-entity>
    </data-map>
    

Попробовав, я получил следующий вывод:

    INFO: --- transaction started.
    Nov 15, 2016 5:06:26 PM org.apache.cayenne.log.CommonsJdbcEventLogger logQuery
    INFO: SELECT nextval('public.pk_proba')
    Exception in thread "main" org.apache.cayenne.CayenneRuntimeException: [v.4.0.M3 Feb 08 2016 16:38:05] Commit Exception
        at org.apache.cayenne.access.DataContext.flushToParent(DataContext.java:776)
        at org.apache.cayenne.access.DataContext.commitChanges(DataContext.java:693)
        at com.echelon.proba.cayenne.Main.main(Main.java:27)
    Caused by: org.postgresql.util.PSQLException: ERROR: relation "public.pk_proba" does not exist
      Position: 16
        at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2458)
        at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2158)

Итак, cayenne ожидает последовательность с именем pk_proba. Почему? Я не имел в виду, что это должно быть сгенерировано. Я не упомянул никаких последовательностей postgresql ни в своей схеме, ни в сопоставлениях Cayenne.

Итак, у меня есть два вопроса:

  • Как я могу подавить попытки Cayenne сгенерировать идентификаторы и сделать так, чтобы Cayenne быстро терпел неудачу, если во время фиксации не было предоставлено удостоверение для определенного объекта?
  • Могу ли я настроить способ, которым Cayenne управляет автоматической генерацией PK в моем проекте (предпочтительнее было бы решение без участия Cayenne Modeller)?

person skapral    schedule 15.11.2016    source источник


Ответы (1)


TL;DR: «pk_proba» — это имя по умолчанию для последовательности, используемой для генерации PK. Если вы хотите, чтобы механизм PK по умолчанию в Cayenne функционировал, вам необходимо предоставить специальные последовательности в PostgreSQL.

Более длинная версия. Вам нужно так или иначе предоставить PK для каждого вставленного объекта. Алгоритм генерации Cayenne PK примерно работает так:

  • Если ПК предоставлен пользователем как свойство объекта, используйте его.
  • Если PK распространяется от главного объекта через отношение, используйте его.
  • Если PK является столбцом auto_increment в БД, используйте его (поддерживается в PG с версии 4.0.M4)
  • Если ничего не помогло, используйте генератор Cayenne PK.

Последняя стратегия требует от вас подготовки объектов БД. Cayenne использует разные стратегии в зависимости от целевой БД. Для PostgreSQL это будут последовательности. В Modeler перейдите в «Инструменты»> «Создать схему базы данных» и снимите все флажки, кроме «Создать поддержку первичного ключа». Затем используйте сгенерированный SQL для обновления вашей БД.

Теперь, если вы действительно хотите, чтобы Cayenne потерпел неудачу на шаге 4 (хотя почему? вы хотите, чтобы ваша вставка в конце концов прошла успешно), вы можете использовать собственный PkGenerator. Вот как вы можете загрузить это через внедрение зависимостей с помощью пользовательского модуля DI:

class CustomAdapterFactory extends DefaultDbAdapterFactory {
    public CustomAdapterFactory(
       @Inject("cayenne.server.adapter_detectors") 
       List<DbAdapterDetector> detectors) {
        super(detectors);
    }

    @Override
    public DbAdapter createAdapter(
        DataNodeDescriptor nodeDescriptor, 
        DataSource dataSource) throws Exception {

        AutoAdapter adapter = 
           (AutoAdapter) super.createAdapter(nodeDescriptor, dataSource);

        // your PkGenerator goes here
        adapter.setPkGenerator(...);
        return adapter;
    }
}

// add this when creating ServerRuntime
Module module = new Module() {
        @Override
        public void configure(Binder binder) {

            binder.bind(DbAdapterFactory.class).to(CustomAdapterFactory.class);
        }
    };

По общему признанию, это можно сделать проще (и мы планируем представить PkGenerator как службу DI), но это должно работать. Просто убедитесь, что это действительно то, что вам нужно.

person andrus_a    schedule 15.11.2016
comment
Спасибо, Андрус, предоставленное решение работает для меня, по крайней мере, для случая 2 (настройка управления генерацией идентификаторов). - person skapral; 15.11.2016
comment
Для случая 1 (быстрая ошибка при отсутствии идентификатора) я лично ожидал какой-то флаг в db-атрибуте или obj-атрибуте (что для меня более удивительно, так это то, что есть один с именем isGenerated, но это не имеет значения). Отвечая на ваш вопрос (почему? Вы все-таки хотите, чтобы ваша вставка прошла успешно) - что, если моими ключами управляет какая-то другая сторона: где-то за пределами базы данных? В любом случае - в JPA этого можно добиться, просто не помещая аннотацию @GeneratedValue - почему это должно быть так нетривиально в Cayenne? - person skapral; 15.11.2016
comment
Сгенерировано == автоинкремент. Это имеет значение, но работает только на M4 для PostgreSQL. - person andrus_a; 15.11.2016
comment
что касается простоты пользовательской установки PkGenerator, она находится в списке TODO версии 4.0. - person andrus_a; 15.11.2016