关于 serverpod-database
serverpod-database 是介绍 Serverpod ORM 和数据库操作的开发技能,面向构建 Serverpod 应用的开发者。内容涉及模型 CRUD、查询、关系、事务及客户端 SQLite 数据库。
主要功能
- 01基于模型生成 ORM
- 02CRUD 与筛选查询
- 03关系加载及关联操作
- 04事务与行锁
- 05原始 SQL 查询
- 06客户端 SQLite 数据库
适用场景
- 01定义模型并执行数据库增删改查
- 02按关系和条件检索及排序数据
- 03在事务中执行多项数据库操作
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ serverpod/ serverpod --skill serverpod-database安装后在 Claude Code 中输入/serverpod-database确认已加载。
详细介绍
Serverpod 数据库
对于 .spy.yaml 中带有 table 的模型,Serverpod 会生成 ORM 代码。PostgreSQL 是默认的生产数据库;SQLite 也受支持,适用于在 config/<runMode>.yaml 中通过 database.filePath: <path> 配置数据库的项目/测试。
CRUD
class: Company
table: company
fields:
name: String
foundedDate: DateTime
如需进一步了解模型和关系的定义,请参阅 Serverpod Models。
var company = Company(name: 'Serverpod Inc.', foundedDate: DateTime.now());
company = await Company.db.insertRow(session, company);
var stored = await Company.db.findById(session, company.id);
company = company.copyWith(name: 'New Name');
await Company.db.updateRow(session, company);
await Company.db.deleteRow(session, company);
筛选
通过 where 回调和表描述符 t 使用流畅的筛选 API:
var activeCompanies = await Company.db.find(session,
where: (t) => t.name.ilike('a%') & (t.foundedDate > DateTime(2020)));
- 相等:
t.column.equals(value)、t.column.notEquals(value) - 比较(int/double/Duration/DateTime):
>、>=、<、<= - 范围:
t.column.between(a, b)、notBetween - 集合:
t.column.inSet(set)、notInSet - 字符串:
t.column.like('A%')(区分大小写)、ilike(不区分大小写);%= 任意字符,_= 一个字符 - 组合:
&(与)、|(或)、~(非);使用括号控制优先级 - 关联(一对一):
t.address.street.like('%road%') - 关联(一对多):
t.orders.count() > 3、t.orders.count((o) => o.itemType.equals('book')) > 3、t.orders.none()、t.orders.any()、t.orders.any((o) => ...)、t.orders.every((o) => ...) - 向量:相似性搜索的距离运算符(
distanceCosine、distanceL2)(仅限 PostgreSQL)。
排序和分页
- 排序:
orderBy: (t) => t.column(默认升序);降序使用orderBy: (t) => t.column.desc() - 多字段排序:
orderByList: (t) => [t.name.desc(), t.id.asc()] - 按关联字段排序:
orderBy: (t) => t.ceo.name;按数量排序:orderBy: (t) => t.employees.count() - 分页:偏移量分页使用
limit+offset;游标分页使用where: (t) => t.id > lastId,并搭配orderBy: (t) => t.id和limit
关联(include、attach、detach)
使用 include 获取关联对象:
var employee = await Employee.db.findById(session, id,
include: Employee.include(address: Address.include()));
var company = await Company.db.findById(session, id,
include: Company.include(
employees: Employee.includeList(
where: (t) => t.name.ilike('a%'),
orderBy: (t) => t.name,
limit: 10,
includes: Employee.include(address: Address.include()),
),
));
带有对象关联的模型也有专用的 attach/detach 方法:
await Company.db.attachRow.employees(session, company, employee);
await Company.db.attach.employees(session, company, [e1, e2]);
await Company.db.detachRow.employees(session, employee);
要附加/分离的对象必须已设置 id(通常是之前从数据库中获取的)。
事务
await session.db.transaction((tx) async {
await Company.db.insertRow(session, company, transaction: tx);
await OtherModel.db.updateRow(session, other, transaction: tx);
});
在事务内部进行所有数据库调用时都使用 tx。
行锁定
需要事务。将 lockMode 和 transaction 传给 find/findFirstRow/findById:
LockMode.forUpdate(排他锁)、forNoKeyUpdate、forShare、forKeyShareLockBehavior.wait(默认)、noWait(抛出异常)、skipLocked(跳过,适用于作业队列)- 不读取数据直接锁定:
Company.db.lockRows(session, where: ..., lockMode: ..., transaction: tx)
在 SQLite 上,尝试锁定行不会产生效果,因为它一次只支持一个写事务。
运行时参数
在 Serverpod 初始化时,通过 runtimeParametersBuilder: (params) => [params.searchPaths(['my_schema', 'public'])] 全局设置 Postgres 参数。按事务设置:await tx.setRuntimeParameters(...)。可用于设置搜索路径、向量索引选项或自定义 MapRuntimeParameters。由于连接池的限制,无法在会话级别设置。
原始 SQL
如需运行原始 SQL 查询,请使用以下方法之一:
late DatabaseResult result;
result = await session.db.unsafeQuery(query, parameters: parameters);
result = await session.db.unsafeSimpleQuery(query);
late int rowsAffected;
rowsAffected = await session.db.unsafeExecute(query, parameters: parameters);
rowsAffected = await session.db.unsafeSimpleExecute(query);
标准 CRUD 操作优先使用 ORM,并对用户输入进行参数化,而不要通过字符串拼接构造 SQL。
客户端数据库
当至少一个模型设置了 database: client 或 database: all 时,生成的 Client 类将包含一个 createSession 方法,该方法会为 SQLite 数据库文件返回一个 ClientDatabaseSession。在 Flutter 上,按以下方式打开数据库:
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:my_project_client/my_project_client.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Set the client URL to the server URL
final client = Client(clientUrl);
// Resolve the database path
final path = await resolveDatabasePath('app.db');
// Store the session in your state manager to later use on database operations.
final session = await client.createSession(path, isDebugMode: kDebugMode);
}
Future<String> resolveDatabasePath(String fileName) async {
if (kIsWeb) return fileName;
final dir = await getApplicationSupportDirectory();
return p.join(dir.path, fileName);
}
请注意,客户端包必须将 serverpod_database 包添加为依赖项。