Drizzle ORM:类型安全的数据库开发

Drizzle ORM 是一个 TypeScript 优先的轻量级 ORM(Object-Relational Mapping)框架,专注于提供类型安全的数据库操作体验。相比传统 ORM,它采用接近 SQL 的 API 设计,在保持类型推导能力的同时避免了运行时性能损耗。本文将介绍 Drizzle ORM 的核心特性和使用方式,帮助你快速学习 Drizzle ORM

什么是 Drizzle ORM

Drizzle ORM 是一个为 TypeScript 开发者设计的现代化 ORM 框架,核心目标是在提供完整类型安全的同时保持接近原生 SQL 的开发体验。官方文档 提供了完整的 API 参考和使用指南。

核心特性

Drizzle ORM 的设计围绕以下几个核心特性展开:

类型安全(Type Safety)

Drizzle 提供端到端的类型推导。从 Schema 定义到查询结果,TypeScript 编译器能够在编译时捕获类型错误。

typescript 复制代码
// Schema 定义自动推导出类型
const users = pgTable("users", {
  id: serial("id").primaryKey(),
  name: text("name").notNull(),
  email: text("email").notNull(),
});

// 查询结果自动推导类型
const result = await db.select().from(users).where(eq(users.id, 1));
// result 类型: { id: number; name: string; email: string }[]

SQL-Like API

Drizzle 的 API 设计贴近 SQL 语法,降低学习成本。熟悉 SQL 的开发者可以快速上手。

typescript 复制代码
// Drizzle API
await db.select().from(users).where(eq(users.email, "user@example.com"));

// 对应的 SQL
// SELECT * FROM users WHERE email = 'user@example.com'

零运行时开销(Zero Runtime Overhead)

Drizzle 不使用代理对象(Proxy)或运行时反射(Reflection),所有类型检查在编译时完成。查询构建器直接生成 SQL 字符串。参考 Drizzle 性能基准测试

支持多种数据库:Drizzle ORM 支持主流关系型数据库,包括 PostgreSQL、MySQL、SQLite 等

Drizzle Kit:数据库迁移工具

Drizzle Kit 是 Drizzle ORM 的配套 CLI 工具,负责数据库迁移管理、Schema 同步和自省。详细的命令和配置选项可参考 Drizzle Kit 官方文档

核心功能

  1. Schema 自省:从现有数据库反向生成 Drizzle Schema 代码
  2. 自动迁移生成:对比 Schema 定义和数据库状态,生成迁移 SQL
  3. 双向同步:支持从数据库拉取或推送到数据库
  4. 可视化工具:提供 Studio 在线管理数据库

常用命令

bash 复制代码
# 从数据库拉取并生成 Schema(新版本,推荐)
npx drizzle-kit pull

# 推送 Schema 到数据库(开发环境)
npx drizzle-kit push

# 生成迁移文件(对比 Schema 和数据库)
npx drizzle-kit generate

# 执行迁移文件(生产环境)
npx drizzle-kit migrate

# 查看迁移状态
npx drizzle-kit check

# 启动可视化管理工具
npx drizzle-kit studio

典型工作流

bash 复制代码
# 场景 1: 从零开始开发(Schema 优先)
npm run db:generate  # 生成初始迁移
npm run db:push      # 推送到数据库

# 场景 2: 接手现有项目(数据库优先)
npx drizzle-kit pull  # 从数据库拉取 Schema
npm run dev           # 开始开发

# 场景 3: Schema 变更后的开发流程
# 修改 schema.ts
npm run db:generate   # 生成迁移文件
npm run db:push       # 推送到开发数据库

# 场景 4: 生产环境部署
npm run db:generate   # 生成迁移文件
git add drizzle/      # 版本控制
npm run db:migrate    # 在生产环境执行迁移

push vs generate+migrate

方式 优点 缺点 适用场景
push 快速、简单 无迁移历史、难以回滚 开发环境
generate+migrate 可审查、可版本控制、可回滚 步骤多、需要管理迁移文件 生产环境

配置文件示例

typescript 复制代码
import type { Config } from "drizzle-kit";

export default {
  schema: "./src/db/schema.ts", // Schema 文件路径
  out: "./drizzle", // 迁移文件输出目录
  dialect: "postgresql", // 数据库类型:postgresql | mysql | sqlite
  dbCredentials: {
    url: process.env.DATABASE_URL!, // 数据库连接字符串
  },
} satisfies Config;

核心功能

本章深入介绍 Drizzle ORM 的核心功能,包括查询构建、关系处理、事务管理和类型系统。

查询构建器

Drizzle 提供完整的查询构建器 API,支持复杂的查询操作。参考 Queries 官方文档 了解更多查询选项。

基础查询

typescript 复制代码
import { db } from "./db";
import { users, posts } from "./db/schema";
import { eq, gt, and, or, like } from "drizzle-orm";

// 条件查询
const activeUsers = await db.select().from(users).where(eq(users.status, "active"));

// 多条件组合
const filteredUsers = await db
  .select()
  .from(users)
  .where(and(gt(users.age, 18), like(users.email, "%@example.com")));

// 排序和分页
const paginatedUsers = await db.select().from(users).orderBy(users.createdAt).limit(10).offset(20);

JOIN 查询

typescript 复制代码
// INNER JOIN
const usersWithPosts = await db
  .select({
    userId: users.id,
    userName: users.name,
    postTitle: posts.title,
  })
  .from(users)
  .innerJoin(posts, eq(users.id, posts.authorId));

// LEFT JOIN
const allUsersWithPosts = await db.select().from(users).leftJoin(posts, eq(users.id, posts.authorId));

聚合查询

typescript 复制代码
import { count, avg, sum } from "drizzle-orm";

// 统计数量
const userCount = await db.select({ count: count() }).from(users);

// 分组统计
const postsByAuthor = await db
  .select({
    authorId: posts.authorId,
    postCount: count(posts.id),
  })
  .from(posts)
  .groupBy(posts.authorId);

关系查询

Drizzle 支持声明式关系定义,简化关联数据查询。详细的关系查询 API 可参考 Relational Queries 文档

定义关系

typescript 复制代码
import { relations } from "drizzle-orm";

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

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

查询关联数据

typescript 复制代码
// 查询用户及其所有文章
const userWithPosts = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: true,
  },
});

// 嵌套查询
const postWithAuthor = await db.query.posts.findFirst({
  where: eq(posts.id, 1),
  with: {
    author: true,
  },
});

// 多级嵌套
const userWithPostsAndComments = await db.query.users.findFirst({
  with: {
    posts: {
      with: {
        comments: true,
      },
    },
  },
});

事务处理

Drizzle 提供事务支持,确保操作的原子性。更多事务用法参考 Transactions 文档

typescript 复制代码
// 基础事务
await db.transaction(async (tx) => {
  // 创建用户
  const [newUser] = await tx
    .insert(users)
    .values({
      name: "Bob",
      email: "bob@example.com",
    })
    .returning();

  // 为用户创建文章
  await tx.insert(posts).values({
    title: "First Post",
    content: "Hello World",
    authorId: newUser.id,
  });
});

// 事务回滚
try {
  await db.transaction(async (tx) => {
    await tx.insert(users).values({ name: "Alice", email: "alice@example.com" });

    // 如果这里抛出错误,整个事务会回滚
    throw new Error("Something went wrong");
  });
} catch (error) {
  console.log("Transaction rolled back");
}

迁移管理

Drizzle Kit 提供迁移管理功能,支持增量迁移和版本控制。详细的迁移命令和配置可参考 Migrations 文档

生成迁移

bash 复制代码
# 根据 schema 变化生成迁移文件
npx drizzle-kit generate:pg

# 查看迁移状态
npx drizzle-kit status

# 应用迁移
npx drizzle-kit push:pg

迁移文件示例

生成的迁移文件是标准 SQL:

sql 复制代码
-- drizzle/0000_initial.sql
CREATE TABLE IF NOT EXISTS "users" (
  "id" serial PRIMARY KEY NOT NULL,
  "name" text NOT NULL,
  "email" text NOT NULL,
  "created_at" timestamp DEFAULT now()
);

CREATE UNIQUE INDEX IF NOT EXISTS "users_email_idx" ON "users" ("email");

类型推导

Drizzle 的类型系统提供完整的端到端类型安全,充分利用了 TypeScript 的类型推导能力。更多类型推导的用法可参考 Goodies 文档

typescript 复制代码
// 从 Schema 推导类型
type User = typeof users.$inferSelect; // 查询结果类型
type NewUser = typeof users.$inferInsert; // 插入数据类型

// 查询结果自动推导
const result = await db
  .select({
    id: users.id,
    name: users.name,
  })
  .from(users);
// result 类型: { id: number; name: string }[]

// 部分选择也有类型推导
const partial = await db
  .select({
    userName: users.name,
    postTitle: posts.title,
  })
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId));
// partial 类型: { userName: string; postTitle: string | null }[]

类型推导覆盖所有操作,编辑器可以提供准确的自动补全和错误提示。

实战案例:构建任务管理系统

本章通过一个任务管理系统的完整实战案例,展示如何使用 Drizzle ORM 从零开始构建一个真实应用,包括项目初始化、数据库设计、Schema 定义、迁移管理以及常见的业务查询操作。

案例背景

我们要构建一个团队任务管理系统,需要实现以下核心功能:

  • 用户管理和认证
  • 项目创建和组织
  • 任务分配和状态跟踪
  • 任务标签分类
  • 任务评论和协作

这个案例将完整演示 Drizzle ORM 的实际应用场景,包括多对多关系、外键约束、复杂查询等。

项目初始化

第 1 步:创建项目并安装依赖

bash 复制代码
# 创建项目目录
mkdir task-manager && cd task-manager

初始化项目:npm init -y + 编辑 package.json

json 复制代码
{
  "name": "task-manager",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "db:generate": "drizzle-kit generate",
    "db:push": "drizzle-kit push",
    "db:seed": "tsx src/seed.ts",
    "db:query": "tsx src/query.ts"
  },
  "dependencies": {
    "dotenv": "^17.2.3",
    "drizzle-orm": "^0.45.1",
    "postgres": "^3.4.7"
  },
  "devDependencies": {
    "@types/node": "^25.0.0",
    "drizzle-kit": "^0.31.8",
    "tsx": "^4.21.0",
    "typescript": "^5.9.3"
  }
}

初始化 TypeScript 配置:npx tsc --init + 编辑 tsconfig.json

json 复制代码
{
  // Visit https://aka.ms/tsconfig to read more about this file
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "bundler",
    "types": ["node"],
    "target": "esnext"
  }
}

第 2 步:配置环境变量

创建 .env 文件:

env 复制代码
DATABASE_URL=postgresql://postgres:password@localhost:5432/task_manager_db

第 3 步:创建 PostgreSQL 数据库

使用 PostgreSQL 创建数据库:

bash 复制代码
# 连接到 PostgreSQL
psql -U postgres

# 创建数据库
CREATE DATABASE task_manager_db;

数据库设计

ER 关系说明

  • 一个用户可以创建多个项目(一对多)
  • 一个项目可以包含多个任务(一对多)
  • 一个任务可以分配给一个用户(多对一)
  • 一个任务可以有多个标签(多对多)
  • 一个任务可以有多条评论(一对多)

定义 Schema

创建 src/db/schema.ts 文件:

typescript 复制代码
import { pgTable, serial, text, varchar, integer, timestamp, boolean, pgEnum, primaryKey } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";

// 定义任务状态枚举
export const taskStatusEnum = pgEnum("task_status", ["todo", "in_progress", "review", "completed"]);

// 定义任务优先级枚举
export const taskPriorityEnum = pgEnum("task_priority", ["low", "medium", "high", "urgent"]);

// 1. 用户表
export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  username: varchar("username", { length: 50 }).notNull().unique(),
  email: varchar("email", { length: 100 }).notNull().unique(),
  passwordHash: varchar("password_hash", { length: 255 }).notNull(),
  avatar: varchar("avatar", { length: 255 }),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

// 2. 项目表
export const projects = pgTable("projects", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 100 }).notNull(),
  description: text("description"),
  ownerId: integer("owner_id")
    .notNull()
    .references(() => users.id, { onDelete: "cascade" }),
  isActive: boolean("is_active").default(true).notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at"),
});

// 3. 任务表
export const tasks = pgTable("tasks", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 200 }).notNull(),
  description: text("description"),
  projectId: integer("project_id")
    .notNull()
    .references(() => projects.id, { onDelete: "cascade" }),
  assigneeId: integer("assignee_id").references(() => users.id, {
    onDelete: "set null",
  }),
  status: taskStatusEnum("status").default("todo").notNull(),
  priority: taskPriorityEnum("priority").default("medium").notNull(),
  dueDate: timestamp("due_date"),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at"),
});

// 4. 标签表
export const tags = pgTable("tags", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 50 }).notNull().unique(),
  color: varchar("color", { length: 7 }).default("#3b82f6"),
});

// 5. 任务标签关联表(多对多)
export const taskTags = pgTable(
  "task_tags",
  {
    taskId: integer("task_id")
      .notNull()
      .references(() => tasks.id, { onDelete: "cascade" }),
    tagId: integer("tag_id")
      .notNull()
      .references(() => tags.id, { onDelete: "cascade" }),
  },
  (table) => ({
    pk: primaryKey({ columns: [table.taskId, table.tagId] }),
  })
);

// 6. 评论表
export const comments = pgTable("comments", {
  id: serial("id").primaryKey(),
  taskId: integer("task_id")
    .notNull()
    .references(() => tasks.id, { onDelete: "cascade" }),
  userId: integer("user_id")
    .notNull()
    .references(() => users.id, { onDelete: "cascade" }),
  content: text("content").notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

// 定义关系
export const usersRelations = relations(users, ({ many }) => ({
  ownedProjects: many(projects),
  assignedTasks: many(tasks),
  comments: many(comments),
}));

export const projectsRelations = relations(projects, ({ one, many }) => ({
  owner: one(users, {
    fields: [projects.ownerId],
    references: [users.id],
  }),
  tasks: many(tasks),
}));

export const tasksRelations = relations(tasks, ({ one, many }) => ({
  project: one(projects, {
    fields: [tasks.projectId],
    references: [projects.id],
  }),
  assignee: one(users, {
    fields: [tasks.assigneeId],
    references: [users.id],
  }),
  taskTags: many(taskTags),
  comments: many(comments),
}));

export const tagsRelations = relations(tags, ({ many }) => ({
  taskTags: many(taskTags),
}));

export const taskTagsRelations = relations(taskTags, ({ one }) => ({
  task: one(tasks, {
    fields: [taskTags.taskId],
    references: [tasks.id],
  }),
  tag: one(tags, {
    fields: [taskTags.tagId],
    references: [tags.id],
  }),
}));

export const commentsRelations = relations(comments, ({ one }) => ({
  task: one(tasks, {
    fields: [comments.taskId],
    references: [tasks.id],
  }),
  user: one(users, {
    fields: [comments.userId],
    references: [users.id],
  }),
}));

配置数据库连接

创建 src/db/index.ts 文件:

typescript 复制代码
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";
import "dotenv/config";

// 创建 PostgreSQL 连接
const client = postgres(process.env.DATABASE_URL!);

// 创建 Drizzle 实例
export const db = drizzle(client, { schema });

配置迁移管理

创建 drizzle.config.ts 文件:

typescript 复制代码
import type { Config } from "drizzle-kit";
import "dotenv/config";

export default {
  schema: "./src/db/schema.ts",
  out: "./drizzle",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
} satisfies Config;

执行数据库迁移

bash 复制代码
# 生成迁移文件
npx drizzle-kit generate

# 查看生成的 SQL 文件
cat drizzle/0000_*.sql

# 执行迁移(将 Schema 同步到数据库)
npx drizzle-kit push

# 或者使用 migrate 命令
npx drizzle-kit migrate

执行后会在 drizzle 目录生成迁移 SQL 文件,类似:

sql 复制代码
-- drizzle/0000_initial.sql
CREATE TYPE "task_status" AS ENUM('todo', 'in_progress', 'review', 'completed');
CREATE TYPE "task_priority" AS ENUM('low', 'medium', 'high', 'urgent');

CREATE TABLE IF NOT EXISTS "users" (
  "id" serial PRIMARY KEY NOT NULL,
  "username" varchar(50) NOT NULL UNIQUE,
  "email" varchar(100) NOT NULL UNIQUE,
  "password_hash" varchar(255) NOT NULL,
  "avatar" varchar(255),
  "created_at" timestamp DEFAULT now() NOT NULL
);

-- ... 其他表的创建语句

插入测试数据

创建 src/seed.ts 文件:

typescript 复制代码
import { db } from "./db";
import { users, projects, tasks, tags, taskTags, comments } from "./db/schema";

async function seed() {
  console.log("Seeding database...");

  // 插入用户
  const [user1, user2, user3] = await db
    .insert(users)
    .values([
      {
        username: "alice",
        email: "alice@taskmanager.com",
        passwordHash: "hash1",
        avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=alice",
      },
      {
        username: "bob",
        email: "bob@taskmanager.com",
        passwordHash: "hash2",
        avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=bob",
      },
      {
        username: "charlie",
        email: "charlie@taskmanager.com",
        passwordHash: "hash3",
        avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=charlie",
      },
    ])
    .returning();

  if (!user1 || !user2 || !user3) throw new Error("Failed to create users");
  console.log("✓ Users created");

  // 插入项目
  const [project1, project2] = await db
    .insert(projects)
    .values([
      {
        name: "Website Redesign",
        description: "Redesign company website with modern UI/UX",
        ownerId: user1.id,
      },
      {
        name: "Mobile App Development",
        description: "Build iOS and Android mobile application",
        ownerId: user2.id,
      },
    ])
    .returning();

  if (!project1 || !project2) throw new Error("Failed to create projects");
  console.log("✓ Projects created");

  // 插入任务
  const [task1, task2, task3, task4] = await db
    .insert(tasks)
    .values([
      {
        title: "Design homepage mockup",
        description: "Create high-fidelity mockup for homepage",
        projectId: project1.id,
        assigneeId: user1.id,
        status: "in_progress",
        priority: "high",
        dueDate: new Date("2025-12-20"),
      },
      {
        title: "Implement authentication",
        description: "Add JWT-based authentication system",
        projectId: project1.id,
        assigneeId: user2.id,
        status: "todo",
        priority: "urgent",
        dueDate: new Date("2025-12-15"),
      },
      {
        title: "Setup CI/CD pipeline",
        description: "Configure GitHub Actions for automated deployment",
        projectId: project1.id,
        assigneeId: user3.id,
        status: "completed",
        priority: "medium",
      },
      {
        title: "Design app architecture",
        description: "Define mobile app architecture and tech stack",
        projectId: project2.id,
        assigneeId: user2.id,
        status: "review",
        priority: "high",
        dueDate: new Date("2025-12-18"),
      },
    ])
    .returning();

  if (!task1 || !task2 || !task3 || !task4) throw new Error("Failed to create tasks");
  console.log("✓ Tasks created");

  // 插入标签
  const [tag1, tag2, tag3, tag4, tag5] = await db
    .insert(tags)
    .values([
      { name: "frontend", color: "#3b82f6" },
      { name: "backend", color: "#10b981" },
      { name: "design", color: "#f59e0b" },
      { name: "devops", color: "#8b5cf6" },
      { name: "mobile", color: "#ec4899" },
    ])
    .returning();

  if (!tag1 || !tag2 || !tag3 || !tag4 || !tag5) throw new Error("Failed to create tags");
  console.log("✓ Tags created");

  // 插入任务标签关联
  await db.insert(taskTags).values([
    { taskId: task1.id, tagId: tag1.id }, // Design homepage - frontend
    { taskId: task1.id, tagId: tag3.id }, // Design homepage - design
    { taskId: task2.id, tagId: tag2.id }, // Authentication - backend
    { taskId: task3.id, tagId: tag4.id }, // CI/CD - devops
    { taskId: task4.id, tagId: tag5.id }, // App architecture - mobile
  ]);

  console.log("✓ Task tags created");

  // 插入评论
  await db.insert(comments).values([
    {
      taskId: task1.id,
      userId: user2.id,
      content: "Looking great! Please add dark mode support.",
    },
    {
      taskId: task1.id,
      userId: user3.id,
      content: "Should we use Figma for the mockup?",
    },
    {
      taskId: task2.id,
      userId: user1.id,
      content: "Don't forget to implement refresh token rotation.",
    },
    {
      taskId: task4.id,
      userId: user1.id,
      content: "Consider using React Native for cross-platform development.",
    },
  ]);

  console.log("✓ Comments created");
  console.log("\n✅ Database seeded successfully!");
  process.exit(0);
}

seed().catch((error) => {
  console.error("❌ Seeding failed:", error);
  process.exit(1);
});

运行数据填充:

bash 复制代码
npm run db:seed

常用查询和操作示例

创建 src/query.ts 文件,演示核心功能:

typescript 复制代码
import { db } from "./db";
import { users, projects, tasks, tags, taskTags, comments } from "./db/schema";
import { eq, and, or, desc, count } from "drizzle-orm";

async function main() {
  console.log("=== Drizzle ORM 实战示例 ===\n");

  // 1. 查询所有用户
  console.log("1. 查询所有用户:");
  const allUsers = await db.select().from(users);
  console.log(allUsers);

  // 2. 查询用户创建的项目(关系查询)
  console.log("\n2. 查询 Alice 创建的项目:");
  const aliceProjects = await db.query.users.findFirst({
    where: eq(users.username, "alice"),
    with: {
      ownedProjects: {
        columns: {
          id: true,
          name: true,
          description: true,
        },
      },
    },
  });
  console.log(JSON.stringify(aliceProjects, null, 2));

  // 3. 查询项目的任务及负责人(JOIN 查询)
  console.log("\n3. 查询项目 1 的任务及负责人:");
  const projectTasks = await db
    .select({
      taskId: tasks.id,
      taskTitle: tasks.title,
      status: tasks.status,
      priority: tasks.priority,
      assignee: users.username,
    })
    .from(tasks)
    .leftJoin(users, eq(tasks.assigneeId, users.id))
    .where(eq(tasks.projectId, 1));
  console.log(projectTasks);

  // 4. 查询任务及其标签(多对多关系)
  console.log("\n4. 查询任务 1 的详细信息及标签:");
  const taskWithTags = await db.query.tasks.findFirst({
    where: eq(tasks.id, 1),
    with: {
      assignee: {
        columns: {
          username: true,
          email: true,
        },
      },
      taskTags: {
        with: {
          tag: true,
        },
      },
    },
  });
  console.log(JSON.stringify(taskWithTags, null, 2));

  // 5. 统计每个项目的任务数量(聚合查询)
  console.log("\n5. 统计各项目的任务数量:");
  const taskCounts = await db
    .select({
      projectId: projects.id,
      projectName: projects.name,
      taskCount: count(tasks.id),
    })
    .from(projects)
    .leftJoin(tasks, eq(projects.id, tasks.projectId))
    .groupBy(projects.id, projects.name);
  console.log(taskCounts);

  // 6. 更新操作:将任务 1 状态改为 completed
  console.log("\n6. 更新任务状态:");
  const [updatedTask] = await db
    .update(tasks)
    .set({
      status: "completed",
      updatedAt: new Date(),
    })
    .where(eq(tasks.id, 1))
    .returning();
  console.log(`✓ 任务 ${updatedTask.id} 状态已更新为: ${updatedTask.status}`);

  // 7. 插入操作:添加新评论
  console.log("\n7. 添加新评论:");
  const [newComment] = await db
    .insert(comments)
    .values({
      taskId: 1,
      userId: 2,
      content: "Great job on completing this task!",
    })
    .returning();
  console.log(`✓ 评论已创建,ID: ${newComment.id}`);

  // 8. 使用事务:创建项目并同时创建任务
  console.log("\n8. 使用事务创建项目和任务:");
  const result = await db.transaction(async (tx) => {
    // 创建项目
    const [newProject] = await tx
      .insert(projects)
      .values({
        name: "Testing Project",
        description: "A project for testing transactions",
        ownerId: 1,
      })
      .returning();

    // 为项目创建任务
    const [newTask] = await tx
      .insert(tasks)
      .values({
        title: "Setup testing environment",
        projectId: newProject.id,
        assigneeId: 2,
        status: "todo",
        priority: "high",
      })
      .returning();

    return { project: newProject, task: newTask };
  });
  console.log(`✓ 项目创建成功: ${result.project.name}`);
  console.log(`✓ 任务创建成功: ${result.task.title}`);

  console.log("\n=== 示例执行完成 ===");
  process.exit(0);
}

main().catch((error) => {
  console.error("❌ 执行出错:", error);
  process.exit(1);
});

运行查询示例:

bash 复制代码
npm run db:query

Schema 变更与数据迁移

在实际开发中,需求经常变化,表结构也需要随之调整。下面演示如何使用 Drizzle Kit 处理 Schema 变更。

场景:产品经理要求为任务添加「预估工时」功能

第 1 步:修改 Schema 定义

编辑 src/db/schema.ts,在 tasks 表中添加 estimatedHours 字段:

typescript 复制代码
// 3. 任务表
export const tasks = pgTable("tasks", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 200 }).notNull(),
  description: text("description"),
  projectId: integer("project_id")
    .notNull()
    .references(() => projects.id, { onDelete: "cascade" }),
  assigneeId: integer("assignee_id").references(() => users.id, {
    onDelete: "set null",
  }),
  status: taskStatusEnum("status").default("todo").notNull(),
  priority: taskPriorityEnum("priority").default("medium").notNull(),
  dueDate: timestamp("due_date"),
  estimatedHours: integer("estimated_hours"), // 新增:预估工时(小时)
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at"),
});

第 2 步:生成迁移文件

bash 复制代码
npm run db:generate

查看生成的 SQL:

bash 复制代码
cat drizzle/0001_*.sql

内容:

sql 复制代码
ALTER TABLE "tasks" ADD COLUMN "estimated_hours" integer;

第 3 步:执行迁移

bash 复制代码
npm run db:push

第 4 步:验证迁移结果

bash 复制代码
psql -U postgres -d task_manager_db -c "\d tasks"

你将看到 estimated_hours 列已添加到表中。

相关推荐
SEO-狼术2 小时前
ASP.NET Zero v15.0.0 adds full .NET
后端·asp.net·.net
艾斯Felix2 小时前
SearXNG使用之引擎连接超时,响应成功但是空数据
后端
木木一直在哭泣2 小时前
我是怎么用 Redis 把 ERP→CRM 同步提速到“几秒钟”的
后端
零日失眠者2 小时前
【Python好用到哭的库】pandas-数据分析神器
后端·python·ai编程
读书郎霍2 小时前
linux 从docker官网源码安装docker
后端
零日失眠者2 小时前
【Python好用到哭的库】numpy-数值计算基础
后端·python·ai编程
创新技术阁2 小时前
CryptoAiAdmin 项目后端启动过程详解
后端·python·fastapi
何中应2 小时前
【面试题-2】Java集合
java·开发语言·后端·面试题
a程序小傲2 小时前
scala中的Array
开发语言·后端·scala