今天做 CAP 项目时数据库配置经常被我们低估。很多人看到cds.requires.db第一反应是这里不过就是写一个kind再补一点credentials服务能跑起来就算完事。可一旦项目从本地原型走到 SAP BTP走到 SAP HANA Cloud走到混合调试、自动部署、CSV 初始数据、连接池、多租户、AI 向量检索这些场景数据库配置就不再是一个小角落里的 JSON 片段而是整个 CAP 应用生命周期的地基。CAP 的数据库设计有一个很有意思的地方它希望我们把业务模型写在 CDS 里把服务逻辑写在 Node.js 或 Java 运行时里而不是一上来就陷进某个数据库方言。我们的领域对象可以叫 Books、Authors、Orders、Incidents业务语义放在模型层持久化细节则由 CAP 运行时和构建工具去翻译。也正因为这样CAP 支持的数据库不是简单并列关系。SQLite 更适合开发和测试SAP HANA Cloud 是生产场景的主战场PostgreSQL 也可以进入生产部署H2 主要服务于 CAP Java 的本地开发。这样的分工看起来像技术选型实际更像工程节奏管理。在本地开发阶段SQLite 往往是最舒服的选择。它启动快依赖少很适合cds watch这种内循环开发模式。做一个 CAP Bookshop 原型时我们并不希望每改一行 CDS 都要连接远程数据库也不希望每个开发人员都先申请一套 HANA Cloud 实例才能开始写服务。此时安装 SQLite 支持只需要一条命令。npmaddcap-js/sqlite-D配置也很轻。内存数据库最适合快速验证应用启动后表结构和测试数据可以重新装载关闭后也不会留下脏数据。{cds:{requires:{db:{kind:sqlite,credentials:{url::memory:}}}}}这种模式很像我们在办公室搭白板。白板上的流程图画错了就擦掉字段设计想改就改没人会把白板当成正式档案。CAP 里的 SQLite 内存库也是这个角色它让我们专注于领域模型、服务行为、OData 输出和 Fiori Elements 页面是否符合预期而不是提前背上生产数据库的治理负担。不过有些时候内存数据库太轻了。我们可能想保留本地数据反复调试某个复杂场景或者排查一次特定的数据迁移问题。这个时候可以切到文件型 SQLite。{cds:{requires:{db:{kind:sqlite,credentials:{url:db.sqlite}}}}}把模型部署到这个文件里也很直接。cds deploy--tosqlite:db.sqlite这里有个很实用的经验SQLite 文件适合开发人员之间复现问题但不要让它变成团队事实标准。企业项目里的数据标准仍然应该回到 CDS 模型、CSV 初始数据、迁移脚本和正式数据库部署流程。SQLite 文件可以帮我们保留现场却不该替代项目的数据治理。回到生产环境SAP HANA Cloud 才是许多 SAP BTP 上 CAP 应用的核心数据库选择。尤其是企业管理软件里常见的主数据、单据、审批状态、库存数量、财务维度、组织结构这些数据不只是 CRUD 对象还会参与分析、权限、多租户、扩展和生命周期管理。SAP HANA Cloud 在这里不是一个普通数据库连接目标而是和 SAP 生态高度贴合的生产级持久化平台。Node.js 项目里可以安装 HANA 数据库服务包也可以使用cds add hana让工具顺手补齐部署相关配置。npmaddcap-js/hana cdsaddhana典型生产配置会写成按 profile 生效的形式。{cds:{requires:{db:{[production]:{kind:hana,deploy-format:hdbtable}}}}}这里的[production]很关键。CAP 项目不是只有一种运行状态。本地开发、混合调试、正式部署对数据库的要求完全不同。我们在开发时可能使用 SQLite在混合模式下服务跑在本机但连接云端 HANA在生产环境里应用和数据库都运行在 SAP BTP 上。把配置写进 profile就是把这些场景的边界提前讲清楚。混合开发在真实项目里特别有价值。一个团队正在开发采购审批应用前端是 Fiori Elements后端是 CAP数据库是 HANA Cloud。审批规则和供应商主数据已经在云端数据库里开发人员本地只想调试服务逻辑不想每次都完整部署应用。这时可以把 HANA 加到 hybrid profile 里。# Add HANA for hybrid modecdsaddhana--forhybrid# Deploy to HANA Cloudcds deploy--tohana# Run locally with remote HANAcdswatch--profilehybrid这个模式的好处是把本地代码热加载和云端真实数据库结合起来。我们改服务逻辑很快数据又不是凭空造的假数据。做权限、草稿、深层关联、性能排查时这比纯 SQLite 更接近生产现场。构建 HANA 部署产物时CAP 会把 CDS 模型转换成 HANA HDI 相关工件。cds build--forhana生成内容通常会落在gen/db/下其中包括.hdbtable、.hdbview、.hdbtabledata、.hdiconfig和.hdinamespace等文件。.hdbtable对应表.hdbview对应视图.hdbtabledata负责装载 CSV 数据。对于经历过传统 HANA 原生开发的人来说这些文件并不陌生。CAP 的价值在于我们不用手写每一个数据库工件而是让 CDS 成为模型源头再由构建过程生成目标数据库需要的形式。说到 HANA就绕不开它的一些原生能力。现在很多企业应用已经不只是保存结构化表单数据还要处理文档语义、相似度搜索、知识问答和 RAG 场景。CAP 里的 Vector 类型让这些 AI 相关场景可以进入 CDS 模型。entity Documents { key ID : UUID; content : String; embedding : Vector(1536); // For AI embeddings }一个更贴近业务的场景是售后服务知识库。我们的系统里有大量维修记录、客户投诉、工程师处理说明。过去只能靠关键字搜索用户输入逆变器过热系统只能匹配文本中出现这些字的记录。引入 embedding 后每条记录都可以生成向量用户输入的自然语言问题也可以生成向量数据库按向量相似度找出语义相近的事故单。这样用户不一定非要输入同样的词也能找到相关解决方案。CAP 数据模型里的Vector(1536)连接的是 AI 语义能力和企业业务数据之间的桥。HANA 还有地理空间能力。零售、物流、资产管理、能源巡检这些行业里位置不是一个备注字段而是业务判断的关键条件。门店服务半径、仓库配送范围、设备坐标、管线覆盖区域都可以进入模型。entity Locations { key ID : UUID; point : hana.ST_POINT; area : hana.ST_GEOMETRY; }例如我们做一个备件调拨应用系统需要根据故障设备坐标找到最近的仓库并判断配送范围是否覆盖客户现场。用普通字符串保存经纬度当然也能显示在页面上但很难做空间计算。使用hana.ST_POINT和hana.ST_GEOMETRY位置就不只是文本而变成数据库可以理解、可以计算、可以索引的空间对象。生产系统最怕的不是一开始建表而是上线后的模型演进。加字段通常还好删除字段、改类型、拆表、改主键就麻烦得多。CAP 对兼容性变化可以自动处理但遇到不兼容变化时最好显式启用迁移控制。cds.persistence.journal entity LargeTable { key ID : UUID; data : String; }这会生成.hdbmigrationtable让我们对迁移有更精细的控制。现实里一个已经跑了两年的订单表可能有几千万行数据不能因为模型改动就粗暴重建。迁移必须考虑历史数据、停机窗口、索引重建、回滚方案和兼容发布。cds.persistence.journal的价值就在这里它提醒我们数据库不是代码仓库里随便删改的文件而是承载企业运行状态的资产。有时 CAP 的数据库无关抽象不够用我们确实需要写一点原生 SQL。比如大表分区、列式表声明、特殊索引。CAP 提供sql.append和sql.prepend这样的出口。sql.append: PARTITION BY HASH (ID) PARTITIONS 4 entity PartitionedData { ... } sql.prepend: COLUMN TABLE entity ColumnTable { ... }这个能力很好用但也要克制。CAP 的优势是数据库无关原生 SQL 一多项目就会越来越绑定某个数据库。我们的经验是只有当性能、容量、合规或数据库原生能力确实要求时才把这些注解放进模型里并且在代码评审里明确记录原因。一个分区注解背后最好有真实的数据规模和访问模式支撑而不是工程师单纯觉得高级。PostgreSQL 在 CAP 里也有自己的位置。它可以作为生产数据库尤其在一些非典型 SAP HANA 场景、边缘部署或已有 PostgreSQL 运维体系的团队里会成为实际选择。安装方式很简单。npmaddcap-js/postgres cdsaddpostgres连接配置可以直接写主机、端口、数据库名、用户名和密码。{cds:{requires:{db:{kind:postgres,credentials:{host:localhost,port:5432,database:mydb,user:postgres,password:password}}}}}本地起一个 PostgreSQL 容器也不复杂。dockerrun-d--namepostgres\-ePOSTGRES_PASSWORDpostgres\-p5432:5432\postgres:15部署模型时使用下面的命令。cds deploy--topostgresPostgreSQL 的意义不只是多了一个数据库选项。它说明 CAP 的领域建模和 CQL 查询并不天然锁死在 HANA 上。只要我们把主要业务逻辑留在 CDS、CQL、服务层和标准注解里数据库就有一定替换空间。当然进入生产之后仍要诚实面对各数据库之间的差异函数支持、索引策略、事务隔离、部署机制、扩展能力都要验证不能只看配置能不能启动。CAP Java 项目里H2 常被用于开发阶段。它的角色和 SQLite 有点像适合轻量测试和本地运行。Maven 依赖可以这样写。dependencygroupIdcom.h2database/groupIdartifactIdh2/artifactIdscoperuntime/scope/dependencySpring 配置里指定内存库。# application.yamlspring:datasource:url:jdbc:h2:mem:testdbdriver-class-name:org.h2.DriverJava 团队常常已经有成熟的 Spring Boot 测试习惯H2 可以自然融入单元测试和集成测试。业务上我们可以把它理解成一个轻量沙盘。正式战场在 HANA 或 PostgreSQL本地沙盘则帮助我们快速验证 CQL、事件处理器、服务行为和数据约束。数据库无关查询是 CAP 的核心舒适区。只要我们使用标准 CQL 和 cds.ql API很多代码可以跨数据库运行。// Works on all databasesawaitSELECT.from(Books).where({stock:{:0}});awaitINSERT.into(Books).entries({title:New Book});awaitUPDATE(Books,id).set({stock:50});awaitDELETE.from(Books,id);这段代码朴素得像普通 CRUD但背后有很强的工程意义。我们写的是业务意图查询库存大于零的书插入一本新书更新库存删除指定记录。至于底层是 SQLite、HANA 还是 PostgreSQL交给 CAP 数据库服务适配。这样的抽象让团队少写很多方言代码也让测试环境和生产环境之间更容易保持一致。标准函数也很重要。字符串拼接、包含判断、前缀后缀检查、大小写转换、裁剪空白、长度计算、截取子串这些操作在各数据库里都有各自写法。CAP 通过 CQL 标准函数把它们统一起来。像concat(x, y)、contains(x, y)、startswith(x, y)、endswith(x, y)、tolower(x)、toupper(x)、trim(x)、length(x)、substring(x, i, n)这些函数看似普通却能减少大量跨数据库兼容问题。会话变量则把请求上下文带进数据访问逻辑。// Available across all databasescds.context.user;// Current usercds.context.tenant;// Current tenantcds.context.locale;// User localecds.context.timestamp;// Request timestamp在企业应用里当前用户、租户、语言和请求时间并不是边角料。多租户 SaaS 需要tenant来隔离数据审计日志需要user和timestamp多语言应用需要locale决定文本读取。一个采购订单服务如果没有这些上下文就很难处理权限、国际化和合规审计。CAP 把这些上下文放在统一位置服务逻辑写起来会干净很多。初始数据是另一个容易被忽略的主题。很多项目一开始只关注表结构到了联调才发现国家代码、币种、审批状态、产品分类、角色映射这些基础数据没人管。CAP 推荐把 CSV 数据放在清晰的位置。db/ ├── schema.cds └── data/ ├── my.bookshop-Books.csv ├── my.bookshop-Authors.csv └── sap.common-Countries.csv命名可以使用namespace-EntityName.csv也可以使用namespace.EntityName.csv。CSV 内容像下面这样。ID;title;author_ID;stock;price 1;Wuthering Heights;101;100;12.99 2;Jane Eyre;102;50;10.99生产初始数据和测试数据最好分开。测试数据可以放到test/data/。test/ └── data/ ├── my.bookshop-Books.csv └── my.bookshop-Reviews.csv这个区分非常实际。配置类数据可以进入db/data随部署进入生产。测试评论、测试订单、测试库存则放在test/data只服务开发和测试。我们做企业系统时最怕测试数据混进生产或者生产配置被开发样例覆盖。CAP 的目录约定其实是在帮助团队形成边界感。连接池配置关系到运行稳定性。开发机上一个连接也许就够生产环境里却要考虑并发请求、数据库最大连接数、服务实例数量和超时策略。Node.js 项目可以在cds.requires.db.pool里配置。{cds:{requires:{db:{pool:{min:0,max:10,acquireTimeoutMillis:10000,idleTimeoutMillis:30000}}}}}Java 项目常见的是 Hikari 配置。spring:datasource:hikari:minimum-idle:5maximum-pool-size:20idle-timeout:30000连接池不是越大越好。一个 CAP 服务如果部署了多个实例每个实例最大连接数都设得很激进总连接数很容易把数据库打满。比较稳妥的做法是根据数据库限制、应用实例数量、峰值并发和平均查询耗时一起算。比如客户服务系统每天白天会有明显高峰查询多、写入少可以适当放宽读请求容量。财务关账场景则可能短时间内有大量批处理写入连接池和事务时长都要更谨慎。数据约束应该尽量靠近数据本身。not null可以阻止缺失关键字段。entity Books { title : String(100) not null; }唯一性约束可以通过注解表达。assert.unique: { isbn: [isbn] } entity Books { isbn : String(13); }外键完整性可以配置为数据库级检查。{cds:{features:{assert_integrity:db}}}业务系统里约束不是为了让开发人员难受而是为了防止数据变成烂账。ISBN 不应重复订单行不应指向不存在的订单头审批记录不应丢失标题。服务层当然可以校验但数据库级约束是最后一道闸门。尤其当数据可能来自多个入口比如 OData 服务、批量导入、后台作业、迁移脚本底层约束就更重要。尽量使用 CQL 并不等于完全禁止原生查询。遇到复杂优化、数据库特定函数或历史表结构时Node.js 里可以直接跑 SQL。constresultawaitcds.db.run(SELECT * FROM my_bookshop_Books WHERE stock ?,[10]);Java 里也可以使用 JdbcTemplate。AutowiredJdbcTemplatejdbc;ListMapString,Objectresultjdbc.queryForList(SELECT * FROM my_bookshop_Books WHERE stock ?,10);但原生 SQL 要像外科手术一样用。它能救场也能留下长期维护成本。我们一旦写了原生 SQL就要考虑数据库方言、表名映射、字段命名、安全参数绑定、事务上下文和迁移后的兼容性。上面示例使用参数占位符而不是拼接字符串这是最低限度的安全习惯。企业应用里任何拼接用户输入的 SQL 都可能变成审计事故。profile-based configuration 是 CAP 数据库配置里最应该认真设计的一块。一个项目从开发到上线数据库配置通常不是一份。{cds:{requires:{db:{[development]:{kind:sqlite,credentials:{url::memory:}},[hybrid]:{kind:hana},[production]:{kind:hana}}}}}开发模式默认可以直接cds watch。# Development (default)cdswatch# Hybridcdswatch--profilehybrid# ProductionNODE_ENVproduction cds serve这套配置背后的思想是把环境差异显式写出来而不是靠口口相传。开发人员看到[development]就知道本地内存库会被使用看到[hybrid]就知道服务本地跑、数据库远程连看到[production]就知道生产数据库配置开始接管。对于团队协作来说这比在 README 里写一长段注意事项更可靠。把这些内容放在一起看CAP 数据库配置其实分三层。最上层是业务建模我们用 CDS 定义实体、关联、约束和类型。中间层是数据库服务CAP 把 CQL、CSV、schema evolution、profile 和连接池组织起来。底层才是具体数据库SQLite、SAP HANA Cloud、PostgreSQL、H2 各自承担不同角色。成熟的 CAP 项目不会在这三层之间乱跳。能用 CDS 表达的就不急着写原生 SQL。能用 profile 管理的就不硬编码环境判断。能用 CSV 管理的基础数据就不手工点数据库控制台。我更愿意把 CAP 数据库配置看成一套项目纪律。SQLite 让我们跑得快HANA 让我们站得稳PostgreSQL 提供更多部署可能H2 服务 Java 本地测试。CQL 让业务查询保持干净CSV 让初始数据进入版本管理连接池让运行时资源可控约束让数据质量不完全依赖代码自觉profile 则让每个环境各归其位。一个企业系统能不能长期维护很多时候不取决于第一次演示有多漂亮而取决于半年后还能不能安全加字段一年后还能不能平滑迁移两年后还能不能解释每一份配置为什么存在。CAP 给了我们相当完整的数据库配置框架真正的工程能力是把这些机制放在合适的位置不滥用也不偷懒。数据库配置写得清楚模型演进才有余地服务部署才有把握后面的 AI 能力、分析能力和扩展能力也才有地方落脚。
SAP CAP 数据库配置这件事,别只把它当成连接字符串
发布时间:2026/5/30 15:44:19
今天做 CAP 项目时数据库配置经常被我们低估。很多人看到cds.requires.db第一反应是这里不过就是写一个kind再补一点credentials服务能跑起来就算完事。可一旦项目从本地原型走到 SAP BTP走到 SAP HANA Cloud走到混合调试、自动部署、CSV 初始数据、连接池、多租户、AI 向量检索这些场景数据库配置就不再是一个小角落里的 JSON 片段而是整个 CAP 应用生命周期的地基。CAP 的数据库设计有一个很有意思的地方它希望我们把业务模型写在 CDS 里把服务逻辑写在 Node.js 或 Java 运行时里而不是一上来就陷进某个数据库方言。我们的领域对象可以叫 Books、Authors、Orders、Incidents业务语义放在模型层持久化细节则由 CAP 运行时和构建工具去翻译。也正因为这样CAP 支持的数据库不是简单并列关系。SQLite 更适合开发和测试SAP HANA Cloud 是生产场景的主战场PostgreSQL 也可以进入生产部署H2 主要服务于 CAP Java 的本地开发。这样的分工看起来像技术选型实际更像工程节奏管理。在本地开发阶段SQLite 往往是最舒服的选择。它启动快依赖少很适合cds watch这种内循环开发模式。做一个 CAP Bookshop 原型时我们并不希望每改一行 CDS 都要连接远程数据库也不希望每个开发人员都先申请一套 HANA Cloud 实例才能开始写服务。此时安装 SQLite 支持只需要一条命令。npmaddcap-js/sqlite-D配置也很轻。内存数据库最适合快速验证应用启动后表结构和测试数据可以重新装载关闭后也不会留下脏数据。{cds:{requires:{db:{kind:sqlite,credentials:{url::memory:}}}}}这种模式很像我们在办公室搭白板。白板上的流程图画错了就擦掉字段设计想改就改没人会把白板当成正式档案。CAP 里的 SQLite 内存库也是这个角色它让我们专注于领域模型、服务行为、OData 输出和 Fiori Elements 页面是否符合预期而不是提前背上生产数据库的治理负担。不过有些时候内存数据库太轻了。我们可能想保留本地数据反复调试某个复杂场景或者排查一次特定的数据迁移问题。这个时候可以切到文件型 SQLite。{cds:{requires:{db:{kind:sqlite,credentials:{url:db.sqlite}}}}}把模型部署到这个文件里也很直接。cds deploy--tosqlite:db.sqlite这里有个很实用的经验SQLite 文件适合开发人员之间复现问题但不要让它变成团队事实标准。企业项目里的数据标准仍然应该回到 CDS 模型、CSV 初始数据、迁移脚本和正式数据库部署流程。SQLite 文件可以帮我们保留现场却不该替代项目的数据治理。回到生产环境SAP HANA Cloud 才是许多 SAP BTP 上 CAP 应用的核心数据库选择。尤其是企业管理软件里常见的主数据、单据、审批状态、库存数量、财务维度、组织结构这些数据不只是 CRUD 对象还会参与分析、权限、多租户、扩展和生命周期管理。SAP HANA Cloud 在这里不是一个普通数据库连接目标而是和 SAP 生态高度贴合的生产级持久化平台。Node.js 项目里可以安装 HANA 数据库服务包也可以使用cds add hana让工具顺手补齐部署相关配置。npmaddcap-js/hana cdsaddhana典型生产配置会写成按 profile 生效的形式。{cds:{requires:{db:{[production]:{kind:hana,deploy-format:hdbtable}}}}}这里的[production]很关键。CAP 项目不是只有一种运行状态。本地开发、混合调试、正式部署对数据库的要求完全不同。我们在开发时可能使用 SQLite在混合模式下服务跑在本机但连接云端 HANA在生产环境里应用和数据库都运行在 SAP BTP 上。把配置写进 profile就是把这些场景的边界提前讲清楚。混合开发在真实项目里特别有价值。一个团队正在开发采购审批应用前端是 Fiori Elements后端是 CAP数据库是 HANA Cloud。审批规则和供应商主数据已经在云端数据库里开发人员本地只想调试服务逻辑不想每次都完整部署应用。这时可以把 HANA 加到 hybrid profile 里。# Add HANA for hybrid modecdsaddhana--forhybrid# Deploy to HANA Cloudcds deploy--tohana# Run locally with remote HANAcdswatch--profilehybrid这个模式的好处是把本地代码热加载和云端真实数据库结合起来。我们改服务逻辑很快数据又不是凭空造的假数据。做权限、草稿、深层关联、性能排查时这比纯 SQLite 更接近生产现场。构建 HANA 部署产物时CAP 会把 CDS 模型转换成 HANA HDI 相关工件。cds build--forhana生成内容通常会落在gen/db/下其中包括.hdbtable、.hdbview、.hdbtabledata、.hdiconfig和.hdinamespace等文件。.hdbtable对应表.hdbview对应视图.hdbtabledata负责装载 CSV 数据。对于经历过传统 HANA 原生开发的人来说这些文件并不陌生。CAP 的价值在于我们不用手写每一个数据库工件而是让 CDS 成为模型源头再由构建过程生成目标数据库需要的形式。说到 HANA就绕不开它的一些原生能力。现在很多企业应用已经不只是保存结构化表单数据还要处理文档语义、相似度搜索、知识问答和 RAG 场景。CAP 里的 Vector 类型让这些 AI 相关场景可以进入 CDS 模型。entity Documents { key ID : UUID; content : String; embedding : Vector(1536); // For AI embeddings }一个更贴近业务的场景是售后服务知识库。我们的系统里有大量维修记录、客户投诉、工程师处理说明。过去只能靠关键字搜索用户输入逆变器过热系统只能匹配文本中出现这些字的记录。引入 embedding 后每条记录都可以生成向量用户输入的自然语言问题也可以生成向量数据库按向量相似度找出语义相近的事故单。这样用户不一定非要输入同样的词也能找到相关解决方案。CAP 数据模型里的Vector(1536)连接的是 AI 语义能力和企业业务数据之间的桥。HANA 还有地理空间能力。零售、物流、资产管理、能源巡检这些行业里位置不是一个备注字段而是业务判断的关键条件。门店服务半径、仓库配送范围、设备坐标、管线覆盖区域都可以进入模型。entity Locations { key ID : UUID; point : hana.ST_POINT; area : hana.ST_GEOMETRY; }例如我们做一个备件调拨应用系统需要根据故障设备坐标找到最近的仓库并判断配送范围是否覆盖客户现场。用普通字符串保存经纬度当然也能显示在页面上但很难做空间计算。使用hana.ST_POINT和hana.ST_GEOMETRY位置就不只是文本而变成数据库可以理解、可以计算、可以索引的空间对象。生产系统最怕的不是一开始建表而是上线后的模型演进。加字段通常还好删除字段、改类型、拆表、改主键就麻烦得多。CAP 对兼容性变化可以自动处理但遇到不兼容变化时最好显式启用迁移控制。cds.persistence.journal entity LargeTable { key ID : UUID; data : String; }这会生成.hdbmigrationtable让我们对迁移有更精细的控制。现实里一个已经跑了两年的订单表可能有几千万行数据不能因为模型改动就粗暴重建。迁移必须考虑历史数据、停机窗口、索引重建、回滚方案和兼容发布。cds.persistence.journal的价值就在这里它提醒我们数据库不是代码仓库里随便删改的文件而是承载企业运行状态的资产。有时 CAP 的数据库无关抽象不够用我们确实需要写一点原生 SQL。比如大表分区、列式表声明、特殊索引。CAP 提供sql.append和sql.prepend这样的出口。sql.append: PARTITION BY HASH (ID) PARTITIONS 4 entity PartitionedData { ... } sql.prepend: COLUMN TABLE entity ColumnTable { ... }这个能力很好用但也要克制。CAP 的优势是数据库无关原生 SQL 一多项目就会越来越绑定某个数据库。我们的经验是只有当性能、容量、合规或数据库原生能力确实要求时才把这些注解放进模型里并且在代码评审里明确记录原因。一个分区注解背后最好有真实的数据规模和访问模式支撑而不是工程师单纯觉得高级。PostgreSQL 在 CAP 里也有自己的位置。它可以作为生产数据库尤其在一些非典型 SAP HANA 场景、边缘部署或已有 PostgreSQL 运维体系的团队里会成为实际选择。安装方式很简单。npmaddcap-js/postgres cdsaddpostgres连接配置可以直接写主机、端口、数据库名、用户名和密码。{cds:{requires:{db:{kind:postgres,credentials:{host:localhost,port:5432,database:mydb,user:postgres,password:password}}}}}本地起一个 PostgreSQL 容器也不复杂。dockerrun-d--namepostgres\-ePOSTGRES_PASSWORDpostgres\-p5432:5432\postgres:15部署模型时使用下面的命令。cds deploy--topostgresPostgreSQL 的意义不只是多了一个数据库选项。它说明 CAP 的领域建模和 CQL 查询并不天然锁死在 HANA 上。只要我们把主要业务逻辑留在 CDS、CQL、服务层和标准注解里数据库就有一定替换空间。当然进入生产之后仍要诚实面对各数据库之间的差异函数支持、索引策略、事务隔离、部署机制、扩展能力都要验证不能只看配置能不能启动。CAP Java 项目里H2 常被用于开发阶段。它的角色和 SQLite 有点像适合轻量测试和本地运行。Maven 依赖可以这样写。dependencygroupIdcom.h2database/groupIdartifactIdh2/artifactIdscoperuntime/scope/dependencySpring 配置里指定内存库。# application.yamlspring:datasource:url:jdbc:h2:mem:testdbdriver-class-name:org.h2.DriverJava 团队常常已经有成熟的 Spring Boot 测试习惯H2 可以自然融入单元测试和集成测试。业务上我们可以把它理解成一个轻量沙盘。正式战场在 HANA 或 PostgreSQL本地沙盘则帮助我们快速验证 CQL、事件处理器、服务行为和数据约束。数据库无关查询是 CAP 的核心舒适区。只要我们使用标准 CQL 和 cds.ql API很多代码可以跨数据库运行。// Works on all databasesawaitSELECT.from(Books).where({stock:{:0}});awaitINSERT.into(Books).entries({title:New Book});awaitUPDATE(Books,id).set({stock:50});awaitDELETE.from(Books,id);这段代码朴素得像普通 CRUD但背后有很强的工程意义。我们写的是业务意图查询库存大于零的书插入一本新书更新库存删除指定记录。至于底层是 SQLite、HANA 还是 PostgreSQL交给 CAP 数据库服务适配。这样的抽象让团队少写很多方言代码也让测试环境和生产环境之间更容易保持一致。标准函数也很重要。字符串拼接、包含判断、前缀后缀检查、大小写转换、裁剪空白、长度计算、截取子串这些操作在各数据库里都有各自写法。CAP 通过 CQL 标准函数把它们统一起来。像concat(x, y)、contains(x, y)、startswith(x, y)、endswith(x, y)、tolower(x)、toupper(x)、trim(x)、length(x)、substring(x, i, n)这些函数看似普通却能减少大量跨数据库兼容问题。会话变量则把请求上下文带进数据访问逻辑。// Available across all databasescds.context.user;// Current usercds.context.tenant;// Current tenantcds.context.locale;// User localecds.context.timestamp;// Request timestamp在企业应用里当前用户、租户、语言和请求时间并不是边角料。多租户 SaaS 需要tenant来隔离数据审计日志需要user和timestamp多语言应用需要locale决定文本读取。一个采购订单服务如果没有这些上下文就很难处理权限、国际化和合规审计。CAP 把这些上下文放在统一位置服务逻辑写起来会干净很多。初始数据是另一个容易被忽略的主题。很多项目一开始只关注表结构到了联调才发现国家代码、币种、审批状态、产品分类、角色映射这些基础数据没人管。CAP 推荐把 CSV 数据放在清晰的位置。db/ ├── schema.cds └── data/ ├── my.bookshop-Books.csv ├── my.bookshop-Authors.csv └── sap.common-Countries.csv命名可以使用namespace-EntityName.csv也可以使用namespace.EntityName.csv。CSV 内容像下面这样。ID;title;author_ID;stock;price 1;Wuthering Heights;101;100;12.99 2;Jane Eyre;102;50;10.99生产初始数据和测试数据最好分开。测试数据可以放到test/data/。test/ └── data/ ├── my.bookshop-Books.csv └── my.bookshop-Reviews.csv这个区分非常实际。配置类数据可以进入db/data随部署进入生产。测试评论、测试订单、测试库存则放在test/data只服务开发和测试。我们做企业系统时最怕测试数据混进生产或者生产配置被开发样例覆盖。CAP 的目录约定其实是在帮助团队形成边界感。连接池配置关系到运行稳定性。开发机上一个连接也许就够生产环境里却要考虑并发请求、数据库最大连接数、服务实例数量和超时策略。Node.js 项目可以在cds.requires.db.pool里配置。{cds:{requires:{db:{pool:{min:0,max:10,acquireTimeoutMillis:10000,idleTimeoutMillis:30000}}}}}Java 项目常见的是 Hikari 配置。spring:datasource:hikari:minimum-idle:5maximum-pool-size:20idle-timeout:30000连接池不是越大越好。一个 CAP 服务如果部署了多个实例每个实例最大连接数都设得很激进总连接数很容易把数据库打满。比较稳妥的做法是根据数据库限制、应用实例数量、峰值并发和平均查询耗时一起算。比如客户服务系统每天白天会有明显高峰查询多、写入少可以适当放宽读请求容量。财务关账场景则可能短时间内有大量批处理写入连接池和事务时长都要更谨慎。数据约束应该尽量靠近数据本身。not null可以阻止缺失关键字段。entity Books { title : String(100) not null; }唯一性约束可以通过注解表达。assert.unique: { isbn: [isbn] } entity Books { isbn : String(13); }外键完整性可以配置为数据库级检查。{cds:{features:{assert_integrity:db}}}业务系统里约束不是为了让开发人员难受而是为了防止数据变成烂账。ISBN 不应重复订单行不应指向不存在的订单头审批记录不应丢失标题。服务层当然可以校验但数据库级约束是最后一道闸门。尤其当数据可能来自多个入口比如 OData 服务、批量导入、后台作业、迁移脚本底层约束就更重要。尽量使用 CQL 并不等于完全禁止原生查询。遇到复杂优化、数据库特定函数或历史表结构时Node.js 里可以直接跑 SQL。constresultawaitcds.db.run(SELECT * FROM my_bookshop_Books WHERE stock ?,[10]);Java 里也可以使用 JdbcTemplate。AutowiredJdbcTemplatejdbc;ListMapString,Objectresultjdbc.queryForList(SELECT * FROM my_bookshop_Books WHERE stock ?,10);但原生 SQL 要像外科手术一样用。它能救场也能留下长期维护成本。我们一旦写了原生 SQL就要考虑数据库方言、表名映射、字段命名、安全参数绑定、事务上下文和迁移后的兼容性。上面示例使用参数占位符而不是拼接字符串这是最低限度的安全习惯。企业应用里任何拼接用户输入的 SQL 都可能变成审计事故。profile-based configuration 是 CAP 数据库配置里最应该认真设计的一块。一个项目从开发到上线数据库配置通常不是一份。{cds:{requires:{db:{[development]:{kind:sqlite,credentials:{url::memory:}},[hybrid]:{kind:hana},[production]:{kind:hana}}}}}开发模式默认可以直接cds watch。# Development (default)cdswatch# Hybridcdswatch--profilehybrid# ProductionNODE_ENVproduction cds serve这套配置背后的思想是把环境差异显式写出来而不是靠口口相传。开发人员看到[development]就知道本地内存库会被使用看到[hybrid]就知道服务本地跑、数据库远程连看到[production]就知道生产数据库配置开始接管。对于团队协作来说这比在 README 里写一长段注意事项更可靠。把这些内容放在一起看CAP 数据库配置其实分三层。最上层是业务建模我们用 CDS 定义实体、关联、约束和类型。中间层是数据库服务CAP 把 CQL、CSV、schema evolution、profile 和连接池组织起来。底层才是具体数据库SQLite、SAP HANA Cloud、PostgreSQL、H2 各自承担不同角色。成熟的 CAP 项目不会在这三层之间乱跳。能用 CDS 表达的就不急着写原生 SQL。能用 profile 管理的就不硬编码环境判断。能用 CSV 管理的基础数据就不手工点数据库控制台。我更愿意把 CAP 数据库配置看成一套项目纪律。SQLite 让我们跑得快HANA 让我们站得稳PostgreSQL 提供更多部署可能H2 服务 Java 本地测试。CQL 让业务查询保持干净CSV 让初始数据进入版本管理连接池让运行时资源可控约束让数据质量不完全依赖代码自觉profile 则让每个环境各归其位。一个企业系统能不能长期维护很多时候不取决于第一次演示有多漂亮而取决于半年后还能不能安全加字段一年后还能不能平滑迁移两年后还能不能解释每一份配置为什么存在。CAP 给了我们相当完整的数据库配置框架真正的工程能力是把这些机制放在合适的位置不滥用也不偷懒。数据库配置写得清楚模型演进才有余地服务部署才有把握后面的 AI 能力、分析能力和扩展能力也才有地方落脚。