Как управлять генерацией ПК с Cayenne 4.0 + PostgreSQL 9.4

Я имею:

  • PostgreSQL 9.4
  • Apache Cayenne 4.0.M3
  • Схема, которая состоит из одной простейшей таблицы "proba":

    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)?

1 ответ

Решение

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

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

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

Последняя стратегия требует от вас подготовки объектов БД. 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), но это должно работать. Просто убедитесь, что это действительно то, что вам нужно.

Другие вопросы по тегам