强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Deno 入门教程 / 第 11 章:数据库操作

第 11 章:数据库操作

11.1 Deno 数据库生态

Deno 支持多种数据库访问方式:

ORM/驱动类型特点
Drizzle ORMORMTypeScript 优先,轻量,类型安全
PrismaORM功能完整,迁移系统,Schema 定义
Knex查询构建器灵活,SQL 风格
原生驱动驱动性能最高,直接控制

11.2 SQLite

使用 Deno 内置 SQLite(Deno 2.1+)

// Deno 2.1+ 内置 SQLite 支持
const db = new Deno.Sqlite(":memory:");

// 创建表
db.execute(`
  CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE,
    created_at TEXT DEFAULT (datetime('now'))
  )
`);

// 插入数据
db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ["Alice", "alice@example.com"]);
db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ["Bob", "bob@example.com"]);

// 查询数据
const rows = db.query("SELECT * FROM users WHERE id > ?", [0]);
for (const [id, name, email, createdAt] of rows) {
  console.log(`ID: ${id}, Name: ${name}, Email: ${email}, Created: ${createdAt}`);
}

db.close();

使用 Drizzle + SQLite

import { drizzle } from "npm:drizzle-orm/better-sqlite3";
import { sqliteTable, text, integer } from "npm:drizzle-orm/sqlite-core";

// 定义 Schema
const users = sqliteTable("users", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  name: text("name").notNull(),
  email: text("email").unique(),
  age: integer("age"),
});

// 连接数据库(需要 Node 兼容的 SQLite 驱动)
import Database from "npm:better-sqlite3";
const sqlite = new Database("./app.db");
const db = drizzle(sqlite);

// 插入
const newUser = await db.insert(users).values({
  name: "Alice",
  email: "alice@example.com",
  age: 30,
}).returning();
console.log(newUser);

// 查询
const allUsers = await db.select().from(users).where(eq(users.age, 30));
console.log(allUsers);

11.3 PostgreSQL

使用 Deno 原生 PostgreSQL 驱动

import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";

const client = new Client({
  user: "postgres",
  password: "password",
  database: "testdb",
  hostname: "localhost",
  port: 5432,
});

await client.connect();

// 创建表
await client.execute(`
  CREATE TABLE IF NOT EXISTS products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    category VARCHAR(50),
    created_at TIMESTAMP DEFAULT NOW()
  )
`);

// 插入数据
await client.execute(
  "INSERT INTO products (name, price, category) VALUES ($1, $2, $3)",
  ["笔记本电脑", 5999.00, "电子产品"]
);

// 查询数据
const result = await client.queryObject("SELECT * FROM products WHERE category = $1", ["电子产品"]);
console.log(result.rows);

await client.end();

使用 Drizzle + PostgreSQL

import { drizzle } from "npm:drizzle-orm/node-postgres";
import { pgTable, serial, text, decimal, timestamp } from "npm:drizzle-orm/pg-core";
import { eq, gt, like } from "npm:drizzle-orm";
import pg from "npm:pg";

// 定义 Schema
const products = pgTable("products", {
  id: serial("id").primaryKey(),
  name: text("name").notNull(),
  price: decimal("price", { precision: 10, scale: 2 }).notNull(),
  category: text("category"),
  createdAt: timestamp("created_at").defaultNow(),
});

// 连接数据库
const pool = new pg.Pool({
  connectionString: "postgresql://user:password@localhost:5432/mydb",
});
const db = drizzle(pool);

// CRUD 操作
// 创建
const newProduct = await db.insert(products).values({
  name: "无线鼠标",
  price: "199.00",
  category: "电子产品",
}).returning();

// 查询
const electronics = await db.select().from(products).where(
  eq(products.category, "电子产品")
);

// 更新
await db.update(products).set({ price: "179.00" }).where(
  eq(products.name, "无线鼠标")
);

// 删除
await db.delete(products).where(gt(products.id, 100));

11.4 Drizzle ORM 详解

Schema 定义最佳实践

// src/db/schema.ts
import { pgTable, serial, text, integer, boolean, timestamp, jsonb } from "npm:drizzle-orm/pg-core";

// 用户表
export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  username: text("username").notNull().unique(),
  email: text("email").notNull().unique(),
  passwordHash: text("password_hash").notNull(),
  avatar: text("avatar"),
  role: text("role").default("user").notNull(),
  metadata: jsonb("metadata").default({}),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});

// 帖子表
export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  title: text("title").notNull(),
  content: text("content").notNull(),
  published: boolean("published").default(false),
  authorId: integer("author_id").references(() => users.id).notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

// 标签表
export const tags = pgTable("tags", {
  id: serial("id").primaryKey(),
  name: text("name").notNull().unique(),
});

关联查询

import { relations } from "npm:drizzle-orm";

// 定义关系
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));

// 查询时包含关联
const usersWithPosts = await db.query.users.findMany({
  with: {
    posts: true,
  },
});

console.log(usersWithPosts[0].posts);

事务

// 使用事务确保数据一致性
await db.transaction(async (tx) => {
  // 扣减库存
  await tx.update(products).set({
    stock: sql`stock - 1`,
  }).where(eq(products.id, productId));
  
  // 创建订单
  await tx.insert(orders).values({
    userId,
    productId,
    quantity: 1,
    total: product.price,
  });
});

11.5 Prisma

初始化 Prisma

# 初始化 Prisma
npx prisma init --datasource-provider postgresql

# 这会生成 prisma/schema.prisma

Schema 定义

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
}

在 Deno 中使用 Prisma

// 需要先生成客户端
// deno run -A npm:prisma generate

import { PrismaClient } from "npm:@prisma/client";

const prisma = new PrismaClient();

// 创建用户
const user = await prisma.user.create({
  data: {
    email: "alice@example.com",
    name: "Alice",
    posts: {
      create: { title: "第一篇文章", content: "Hello World" },
    },
  },
  include: { posts: true },
});
console.log(user);

// 查询用户
const users = await prisma.user.findMany({
  where: { email: { contains: "example.com" } },
  include: { posts: { where: { published: true } } },
});

await prisma.$disconnect();

11.6 Knex 查询构建器

import knex from "npm:knex";

const db = knex({
  client: "pg",
  connection: {
    host: "localhost",
    port: 5432,
    user: "postgres",
    password: "password",
    database: "mydb",
  },
});

// 创建表
await db.schema.createTableIfNotExists("users", (table) => {
  table.increments("id").primary();
  table.string("name", 100).notNullable();
  table.string("email", 255).unique().notNullable();
  table.timestamps(true, true);
});

// 插入
await db("users").insert({ name: "Alice", email: "alice@example.com" });

// 查询
const users = await db("users").where("name", "like", "%Ali%").select("*");

// 更新
await db("users").where({ id: 1 }).update({ name: "Alice Updated" });

// 删除
await db("users").where({ id: 1 }).del();

// 关闭连接
await db.destroy();

11.7 Deno KV(内置键值存储)

Deno 内置了一个分布式键值存储系统——Deno KV

// 打开 KV 数据库
const kv = await Deno.openKv();

// 写入数据
await kv.set(["users", "alice"], { name: "Alice", age: 30 });
await kv.set(["users", "bob"], { name: "Bob", age: 25 });

// 读取数据
const entry = await kv.get(["users", "alice"]);
console.log(entry.value);  // { name: "Alice", age: 30 }

// 删除数据
await kv.delete(["users", "bob"]);

// 列表查询
const entries = kv.list({ prefix: ["users"] });
for await (const entry of entries) {
  console.log(entry.key, entry.value);
}

// 原子操作
const result = await kv.atomic()
  .check({ key: ["counter"], versionstamp: null })
  .set(["counter"], 1)
  .commit();

// 设置过期时间
await kv.set(["session", "abc123"], { userId: "alice" }, { expireIn: 60 * 60 * 1000 }); // 1小时过期

kv.close();

11.8 连接池与性能

PostgreSQL 连接池

import pg from "npm:pg";

const pool = new pg.Pool({
  connectionString: "postgresql://user:password@localhost:5432/mydb",
  max: 20,           // 最大连接数
  idleTimeoutMillis: 30000,  // 空闲连接超时
  connectionTimeoutMillis: 2000, // 连接超时
});

// 自动管理连接
const result = await pool.query("SELECT NOW()");
console.log(result.rows[0]);

// 关闭连接池
await pool.end();

11.9 数据库迁移

Drizzle 迁移

# 生成迁移文件
npx drizzle-kit generate:pg

# 执行迁移
npx drizzle-kit push:pg

# 或在代码中执行迁移
import { migrate } from "npm:drizzle-orm/node-postgres/migrator";
import { drizzle } from "npm:drizzle-orm/node-postgres";
import pg from "npm:pg";

const pool = new pg.Pool({ connectionString: "..." });
const db = drizzle(pool);

await migrate(db, { migrationsFolder: "./drizzle" });
console.log("迁移完成");
await pool.end();

11.10 本章小结

数据库方案适用场景复杂度
Deno SQLite(内置)本地存储、原型
Drizzle ORMTypeScript 优先项目
Prisma企业级应用
Knex灵活查询需求
Deno KV缓存、Session、简单数据
原生驱动高性能需求

📖 扩展阅读


下一章第 12 章:测试 → 学习 Deno 的内置测试框架。