如何使用 Cayenne 4.0 + PostgreSQL 9.4 管理 PK 生成
How to manage PK generation with Cayenne 4.0 + PostgreSQL 9.4
我有:
- PostgreSQL 9.4
- Apache 卡宴 4.0.M3
由一个最简单的 table "proba":
组成的模式
创建 TABLE 概率 (
id bigint 不为空,
值字符变化(255),
约束 proba_pkey 主键 (id)
)
一个简单的Main方法:
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();
}
一个简单的卡宴-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 的序列。为什么?我不是说要生成它。我既没有在我的架构中也没有在 Cayenne 映射中提到任何 postgresql 序列。
所以,我有两个问题:
- 如果在提交时没有为特定实体提供身份,我如何禁止 Cayenne 尝试生成 ID 并使 Cayenne 快速失败?
- 我可以自定义一种 Cayenne 在我的项目中管理 PK 自动生成的方式吗(最好的解决方案是不涉及 Cayenne Modeller 的解决方案)?
TL;DR:"pk_proba" 是用于生成 PK 的序列的默认名称。如果想让Cayenne默认的PK机制发挥作用,需要在PostgreSQL.
中提供特殊序列
更长的版本。您需要以一种或另一种方式为每个插入的对象提供 PK。卡宴PK生成算法大致是这样的:
- 如果PK是用户提供的对象属性,使用它。
- 如果 PK 是通过关系从主对象传播的,请使用它。
- 如果 PK 是数据库中的 auto_increment 列,则使用它(自 4.0.M4 起在 PG 上支持)
- 如果一切都失败了,请使用 Cayenne PK 生成器。
最后一种策略要求您准备数据库对象。 Cayenne 根据目标数据库使用不同的策略。对于 PostgreSQL 它将是序列。在建模器中转到 "Tools > Generate Database Schema" 并取消选中除 "Create Primary Key Support" 之外的所有复选框。然后使用生成的 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 服务公开),但它应该可以工作。只要确保这确实是您所需要的。
我有:
- PostgreSQL 9.4
- Apache 卡宴 4.0.M3
由一个最简单的 table "proba":
组成的模式创建 TABLE 概率 ( id bigint 不为空, 值字符变化(255), 约束 proba_pkey 主键 (id) )
一个简单的Main方法:
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(); }
一个简单的卡宴-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 的序列。为什么?我不是说要生成它。我既没有在我的架构中也没有在 Cayenne 映射中提到任何 postgresql 序列。
所以,我有两个问题:
- 如果在提交时没有为特定实体提供身份,我如何禁止 Cayenne 尝试生成 ID 并使 Cayenne 快速失败?
- 我可以自定义一种 Cayenne 在我的项目中管理 PK 自动生成的方式吗(最好的解决方案是不涉及 Cayenne Modeller 的解决方案)?
TL;DR:"pk_proba" 是用于生成 PK 的序列的默认名称。如果想让Cayenne默认的PK机制发挥作用,需要在PostgreSQL.
中提供特殊序列更长的版本。您需要以一种或另一种方式为每个插入的对象提供 PK。卡宴PK生成算法大致是这样的:
- 如果PK是用户提供的对象属性,使用它。
- 如果 PK 是通过关系从主对象传播的,请使用它。
- 如果 PK 是数据库中的 auto_increment 列,则使用它(自 4.0.M4 起在 PG 上支持)
- 如果一切都失败了,请使用 Cayenne PK 生成器。
最后一种策略要求您准备数据库对象。 Cayenne 根据目标数据库使用不同的策略。对于 PostgreSQL 它将是序列。在建模器中转到 "Tools > Generate Database Schema" 并取消选中除 "Create Primary Key Support" 之外的所有复选框。然后使用生成的 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 服务公开),但它应该可以工作。只要确保这确实是您所需要的。