如何使用Cayenne 4.0 + PostgreSQL 9.4管理PK生成

时间:2016-11-15 14:24:58

标签: java orm apache-cayenne

我有:

  • 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(hand-maded):

    <?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的那个)?

1 个答案:

答案 0 :(得分:1)

TL; DR:&#34; pk_proba&#34;是用于生成PK的序列的默认名称。如果你想让Cayenne默认的PK机制起作用,你需要在PostgreSQL中提供特殊的序列。

更长的版本。您需要以这种或那种方式为每个插入的对象提供PK。 Cayenne PK生成算法大致如下:

  • 如果用户将PK作为对象属性提供,请使用它。
  • 如果PK是通过关系从主对象传播的,请使用它。
  • 如果PK是DB中的auto_increment列,请使用它(自4.0.M4起支持PG)
  • 如果其他一切都失败了,请使用Cayenne PK发生器。

最后一个策略要求您准备DB对象。 Cayenne根据目标DB使用不同的策略。对于PostgreSQL,它将是序列。在Modeler中转到&#34;工具&gt;生成数据库模式&#34;并取消选中除&#34;创建主键支持&#34;之外的所有复选框。然后使用生成的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服务公开),但它应该可行。只要确保这确实是你需要的。