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