Prisma 简介

Prisma 是一个现代的 Node.js 和 TypeScript ORM(对象关系映射)工具,它提供了一种类型安全、直观的方式来访问数据库。Prisma 的设计目标是使数据库访问变得更加简单、可靠和高效,同时提供了丰富的功能来满足各种应用场景的需求。

核心特点

  • 类型安全:自动生成 TypeScript 类型,提供编译时类型检查,减少运行时错误。
  • 直观的查询 API:提供简洁、直观的查询 API,使数据库操作更加易于编写和理解。
  • 数据库建模:使用 Prisma Schema 定义数据库模型,支持多种数据库。
  • 迁移工具:内置强大的数据库迁移工具,管理数据库模式变更。
  • 数据库代理:提供数据库代理服务,优化数据库连接和查询性能。
  • 支持多种数据库:支持 PostgreSQL、MySQL、SQLite、MongoDB 等多种数据库。
  • 生态系统丰富:提供了丰富的工具和集成,如 Prisma Client、Prisma Migrate、Prisma Studio 等。
  • 现代开发体验:与现代前端框架和工具良好集成,如 Next.js、NestJS 等。

安装与配置

安装 Prisma

使用 npm 或 yarn 安装 Prisma CLI 和 Prisma Client:

# 使用 npm 安装
npm install prisma @prisma/client

# 使用 yarn 安装
yarn add prisma @prisma/client

初始化 Prisma

初始化 Prisma 项目,创建基本的配置文件:

# 初始化 Prisma
npx prisma init

执行此命令后,会在项目根目录创建 .prisma 目录和 prisma/schema.prisma 文件,以及更新 .env 文件。

配置数据库连接

.env 文件中配置数据库连接字符串:

# PostgreSQL
DATABASE_URL="postgresql://username:password@localhost:5432/database?schema=public"

# MySQL
# DATABASE_URL="mysql://username:password@localhost:3306/database"

# SQLite
# DATABASE_URL="file:./dev.db"

定义数据模型

prisma/schema.prisma 文件中定义数据模型:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql" // 或 mysql、sqlite、mongodb
  url      = env("DATABASE_URL")
}

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

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

生成 Prisma Client

根据数据模型生成 Prisma Client:

# 生成 Prisma Client
npx prisma generate

数据库迁移

创建数据库迁移并应用到数据库:

# 创建迁移
npx prisma migrate dev --name init

核心概念

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())
  name  String
  email String  @unique
  posts Post[]
}

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

Prisma Client

Prisma Client 是一个自动生成的数据库客户端,它提供了类型安全的查询 API。

// 导入 Prisma Client
const { PrismaClient } = require('@prisma/client');

// 创建 Prisma Client 实例
const prisma = new PrismaClient();

// 使用 Prisma Client 查询数据
async function getUsers() {
  const users = await prisma.user.findMany();
  return users;
}

数据模型

数据模型定义了数据库中的表结构和关系。

基本模型

model User {
  id    Int     @id @default(autoincrement())
  name  String
  email String  @unique
}

关系模型

model User {
  id    Int     @id @default(autoincrement())
  name  String
  posts Post[]
}

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

枚举类型

enum Role {
  USER
  ADMIN
}

model User {
  id    Int     @id @default(autoincrement())
  name  String
  role  Role    @default(USER)
}

查询操作

Prisma Client 提供了丰富的查询操作,用于增删改查数据。

查询所有记录

// 查询所有用户
const users = await prisma.user.findMany();

查询单个记录

// 根据 ID 查询用户
const user = await prisma.user.findUnique({
  where: { id: 1 }
});

条件查询

// 条件查询用户
const users = await prisma.user.findMany({
  where: {
    name: {
      contains: 'John'
    }
  }
});

排序和分页

// 排序和分页
const users = await prisma.user.findMany({
  orderBy: {
    name: 'asc'
  },
  skip: 0,
  take: 10
});

关联查询

// 关联查询
const users = await prisma.user.findMany({
  include: {
    posts: true
  }
});

创建记录

// 创建用户
const user = await prisma.user.create({
  data: {
    name: 'John Doe',
    email: 'john@example.com'
  }
});

更新记录

// 更新用户
const user = await prisma.user.update({
  where: { id: 1 },
  data: {
    name: 'John Smith'
  }
});

删除记录

// 删除用户
const user = await prisma.user.delete({
  where: { id: 1 }
});

实用案例分析

构建 RESTful API

下面是一个使用 Prisma 构建 RESTful API 的示例,实现了基本的 CRUD 操作。

项目结构

├── prisma/
│   └── schema.prisma
├── src/
│   ├── controllers/
│   │   └── userController.js
│   ├── routes/
│   │   └── userRoutes.js
│   ├── services/
│   │   └── userService.js
│   └── index.js
├── .env
└── package.json

代码实现

  1. 数据模型 (prisma/schema.prisma):
generator client {
  provider = "prisma-client-js"
}

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

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

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User    @relation(fields: [authorId], references: [id])
  authorId  Int
}
  1. 用户服务 (src/services/userService.js):
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

// 获取所有用户
const getUsers = async () => {
  return await prisma.user.findMany({
    include: {
      posts: true
    }
  });
};

// 根据 ID 获取用户
const getUserById = async (id) => {
  return await prisma.user.findUnique({
    where: { id: parseInt(id) },
    include: {
      posts: true
    }
  });
};

// 创建用户
const createUser = async (userData) => {
  return await prisma.user.create({
    data: userData
  });
};

// 更新用户
const updateUser = async (id, userData) => {
  return await prisma.user.update({
    where: { id: parseInt(id) },
    data: userData
  });
};

// 删除用户
const deleteUser = async (id) => {
  return await prisma.user.delete({
    where: { id: parseInt(id) }
  });
};

module.exports = {
  getUsers,
  getUserById,
  createUser,
  updateUser,
  deleteUser
};
  1. 用户控制器 (src/controllers/userController.js):
const userService = require('../services/userService');

// 获取所有用户
const getUsers = async (req, res) => {
  try {
    const users = await userService.getUsers();
    res.status(200).json(users);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

// 根据 ID 获取用户
const getUserById = async (req, res) => {
  try {
    const user = await userService.getUserById(req.params.id);
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }
    res.status(200).json(user);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

// 创建用户
const createUser = async (req, res) => {
  try {
    const user = await userService.createUser(req.body);
    res.status(201).json(user);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
};

// 更新用户
const updateUser = async (req, res) => {
  try {
    const user = await userService.updateUser(req.params.id, req.body);
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }
    res.status(200).json(user);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
};

// 删除用户
const deleteUser = async (req, res) => {
  try {
    const user = await userService.deleteUser(req.params.id);
    res.status(200).json({ message: 'User deleted successfully' });
  } catch (error) {
    res.status(404).json({ error: 'User not found' });
  }
};

module.exports = {
  getUsers,
  getUserById,
  createUser,
  updateUser,
  deleteUser
};
  1. 用户路由 (src/routes/userRoutes.js):
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');

// 路由定义
router.get('/', userController.getUsers);
router.get('/:id', userController.getUserById);
router.post('/', userController.createUser);
router.put('/:id', userController.updateUser);
router.delete('/:id', userController.deleteUser);

module.exports = router;
  1. 主应用 (src/index.js):
const express = require('express');
const userRoutes = require('./routes/userRoutes');

const app = express();
const port = 3000;

// 中间件
app.use(express.json());

// 路由
app.use('/api/users', userRoutes);

// 健康检查
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'ok' });
});

// 启动服务器
app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});
  1. 配置文件 (package.json):
{
  "name": "prisma-rest-api",
  "version": "1.0.0",
  "description": "RESTful API with Prisma",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "dev": "nodemon src/index.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "prisma": "^4.15.0",
    "@prisma/client": "^4.15.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.22"
  }
}
  1. 环境变量 (.env):
DATABASE_URL="postgresql://username:password@localhost:5432/prisma-demo?schema=public"

测试 API

  1. 初始化数据库:
# 创建迁移
npx prisma migrate dev --name init

# 生成 Prisma Client
npx prisma generate
  1. 启动服务器:
npm run dev
  1. 使用 curl 或 Postman 测试 API:
# 获取所有用户
curl http://localhost:3000/api/users

# 获取单个用户
curl http://localhost:3000/api/users/1

# 创建用户
curl -X POST http://localhost:3000/api/users -H "Content-Type: application/json" -d '{"name": "John Doe", "email": "john@example.com"}'

# 更新用户
curl -X PUT http://localhost:3000/api/users/1 -H "Content-Type: application/json" -d '{"name": "John Smith"}'

# 删除用户
curl -X DELETE http://localhost:3000/api/users/1

# 健康检查
curl http://localhost:3000/health

高级特性

事务

Prisma 支持数据库事务,确保多个操作的原子性:

// 使用事务
const result = await prisma.$transaction(async (prisma) => {
  // 创建用户
  const user = await prisma.user.create({
    data: {
      name: 'John Doe',
      email: 'john@example.com'
    }
  });

  // 创建帖子
  const post = await prisma.post.create({
    data: {
      title: 'First Post',
      content: 'Hello World!',
      authorId: user.id
    }
  });

  return { user, post };
});

批量操作

Prisma 支持批量创建、更新和删除操作:

// 批量创建用户
const users = await prisma.user.createMany({
  data: [
    { name: 'John Doe', email: 'john@example.com' },
    { name: 'Jane Doe', email: 'jane@example.com' }
  ]
});

// 批量更新用户
const updatedUsers = await prisma.user.updateMany({
  where: {
    name: {
      contains: 'Doe'
    }
  },
  data: {
    name: {
      append: ' Smith'
    }
  }
});

// 批量删除用户
const deletedUsers = await prisma.user.deleteMany({
  where: {
    email: {
      endsWith: '@example.com'
    }
  }
});

原生 SQL 查询

对于复杂的查询,Prisma 支持执行原生 SQL 查询:

// 执行原生 SQL 查询
const users = await prisma.$queryRaw`SELECT * FROM User WHERE name = ${name}`;

// 执行参数化原生 SQL 查询
const users = await prisma.$queryRaw(
  Prisma.sql`SELECT * FROM User WHERE name = ${name}`
);

Prisma Studio

Prisma Studio 是一个可视化的数据库管理工具,用于查看和编辑数据库数据:

# 启动 Prisma Studio
npx prisma studio

执行此命令后,会在浏览器中打开 Prisma Studio 界面,可以直观地查看和编辑数据库数据。

性能优化

1. 使用连接池

Prisma 默认使用连接池来管理数据库连接,提高查询性能:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
  shadowDatabaseUrl = env("SHADOW_DATABASE_URL") // 用于迁移
}

2. 选择字段

只选择需要的字段,减少数据传输量:

// 不推荐:选择所有字段
const users = await prisma.user.findMany();

// 推荐:只选择需要的字段
const users = await prisma.user.findMany({
  select: {
    id: true,
    name: true
  }
});

3. 批量查询

使用批量查询减少数据库请求次数:

// 不推荐:多次查询
const user = await prisma.user.findUnique({ where: { id: 1 } });
const posts = await prisma.post.findMany({ where: { authorId: 1 } });

// 推荐:单次查询
const user = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    posts: true
  }
});

4. 索引优化

在 Prisma Schema 中定义索引,提高查询性能:

model User {
  id    Int     @id @default(autoincrement())
  name  String
  email String  @unique // 自动创建唯一索引
  posts Post[]

  @@index([name]) // 创建普通索引
}

5. 使用缓存

对于频繁访问的数据,使用缓存来减少数据库查询:

const Redis = require('redis');
const client = Redis.createClient();

// 缓存用户数据
async function getUserWithCache(id) {
  const cacheKey = `user:${id}`;
  
  // 尝试从缓存获取
  const cachedUser = await client.get(cacheKey);
  if (cachedUser) {
    return JSON.parse(cachedUser);
  }
  
  // 从数据库获取
  const user = await prisma.user.findUnique({ where: { id: parseInt(id) } });
  
  // 存入缓存
  if (user) {
    await client.set(cacheKey, JSON.stringify(user), { EX: 3600 });
  }
  
  return user;
}

6. 分页查询

对于大量数据,使用分页查询避免一次性加载过多数据:

// 分页查询
const users = await prisma.user.findMany({
  skip: (page - 1) * pageSize,
  take: pageSize,
  orderBy: {
    createdAt: 'desc'
  }
});

总结

Prisma 是一个现代、强大的 ORM 工具,它通过提供类型安全、直观的查询 API,使数据库访问变得更加简单、可靠和高效。Prisma 的核心特性包括类型安全、直观的查询 API、数据库建模、迁移工具、数据库代理等,这些特性使得它成为构建现代应用的理想选择。

通过本教程,你应该已经了解了 Prisma 的核心概念和基本用法,包括安装配置、数据模型定义、查询操作、关系管理等,以及如何使用 Prisma 构建 RESTful API。你还学习了 Prisma 的高级特性,如事务、批量操作、原生 SQL 查询、Prisma Studio 等,以及一些性能优化的方法。

Prisma 的设计理念和功能使其成为 Node.js 和 TypeScript 项目中处理数据库操作的优秀选择,它的生态系统也在不断发展,提供了更多的工具和集成,以满足各种应用场景的需求。要深入学习 Prisma,建议查阅 官方文档 和实践更多的项目案例,以掌握其高级特性和最佳实践。

« 上一篇 Socket.io 教程 下一篇 » 59. Mongoose - MongoDB对象建模工具