mcpField
S
serverpod-database

serverpod-database

github.com/serverpod数据与数据库3.3k44 次安装1 天前更新
代码仓库

关于 serverpod-database

serverpod-database 是介绍 Serverpod ORM 和数据库操作的开发技能,面向构建 Serverpod 应用的开发者。内容涉及模型 CRUD、查询、关系、事务及客户端 SQLite 数据库。

主要功能

  1. 01基于模型生成 ORM
  2. 02CRUD 与筛选查询
  3. 03关系加载及关联操作
  4. 04事务与行锁
  5. 05原始 SQL 查询
  6. 06客户端 SQLite 数据库

适用场景

  1. 01定义模型并执行数据库增删改查
  2. 02按关系和条件检索及排序数据
  3. 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、forKeyShare
  • LockBehavior.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 包添加为依赖项。

评论

支持 Markdown