2.2 数据基础:数据库集成与 ORM(TypeORM / Prisma)

🚀 关于 ai-data-analyzer

本文档属于开源项目 ai-data-analyzer 的技术文档系列。这是一个专为 AI 数据分析场景打造的全栈解决方案,包含前端可视化、Nest.js 后端服务、数据库设计、AI Agent 集成等完整模块。欢迎 Star、Fork 和贡献代码!

在 AI Agent 应用中,数据是核心资产:原始数据、特征与中间结果、用户请求历史、任务状态、分析产物、评估与审计记录等都需要可靠的持久化。

本节聚焦如何在 Nest.js 后端建立数据基础,介绍两类常见 ORM 方案:TypeORM 与 Prisma,并给出在 Nest.js 中的典型集成方式与注意事项。

2.2.1 为什么使用 ORM?

直接手写 SQL 能获得极高控制力,但在复杂业务中会带来重复、耦合与易错点。ORM(Object-Relational Mapping)提供了一层抽象:用面向对象的方式建模与访问数据,再由 ORM 生成参数化 SQL 并执行。

常见收益:

  • 开发效率:CRUD 与查询构建器减少重复劳动
  • 类型与结构:与 TypeScript 配合可在编译期发现接口/字段错误
  • 可维护性:实体、迁移、仓库/服务层边界清晰
  • 安全性:默认参数化查询可显著降低 SQL 注入风险(但仍需避免拼接原始 SQL)

2.2.2 数据库选型(以 PostgreSQL 为例)

AI 数据分析的后端通常需要:

  • 大量结构化数据(用户、任务、权限、配置)
  • 半结构化数据(模型输入输出、日志、评估指标等)

PostgreSQL 常作为首选之一,原因包括:

  • 支持 JSONB、索引与丰富查询能力,适合存放半结构化数据
  • 成熟的事务与一致性能力,适合任务状态与审计数据

当然,MySQL、SQLite 也都能胜任部分场景:SQLite 适合本地开发或轻量部署;生产环境常用 PostgreSQL/MySQL。

2.2.3 方案 A:在 Nest.js 中集成 TypeORM

Nest.js 提供官方集成包 @nestjs/typeorm。下面以 PostgreSQL 为例。

步骤 1:安装依赖

bash 复制代码
pnpm add @nestjs/typeorm typeorm pg @nestjs/config

步骤 2:配置环境变量(示例)

在项目根目录的 .env

ini 复制代码
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_USER=your_username
DATABASE_PASSWORD=your_password
DATABASE_NAME=ai_analysis_db

步骤 3:配置数据库连接

推荐使用 ConfigModule + TypeOrmModule.forRootAsync(),避免在模块装配时直接散落读取 process.env,并便于测试与多环境切换。

这段配置写在哪里?

  • 最常见(推荐入门) :直接写在后端项目的根模块 src/app.module.tsimports 里。
  • 更清晰(项目变大后) :抽到单独的 DatabaseModule(例如 src/database/database.module.ts),再由 AppModule 导入这个模块。本文先用第一种写法,最直观。
ts 复制代码
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        type: 'postgres',
        host: config.get('DATABASE_HOST', 'localhost'),
        port: Number(config.get('DATABASE_PORT', 5432)),
        username: config.get<string>('DATABASE_USER'),
        password: config.get<string>('DATABASE_PASSWORD'),
        database: config.get<string>('DATABASE_NAME'),
        autoLoadEntities: true,
        synchronize: false,
        logging: ['error'],
      }),
    }),
  ],
})
export class AppModule {}

重要提示:

  • synchronize: true 仅建议用于本地开发/一次性原型验证;生产环境不要使用,避免误删/误改表结构。
  • autoLoadEntities: true 会自动加载各模块通过 TypeOrmModule.forFeature() 注册的实体,避免维护 glob 路径。

步骤 4:使用 Nest CLI 生成资源(推荐)

最专业、高效的方式是使用 Nest CLI 的 generate resource 命令。它不仅会创建文件,还会自动生成模块(Module)、控制器(Controller)、服务(Service)、实体(Entity)和 DTO 的完整骨架,并自动配置依赖注入。

  1. 运行生成命令
bash 复制代码
# 在 backend 目录下运行
pnpm dlx @nestjs/cli g resource analysis-results

交互式提示选择:

  • What transport layer do you use? -> 选择 REST API
  • Would you like to generate CRUD entry points? -> 选择 Y (Yes)

这会自动创建 src/analysis-results/ 目录及所有必要文件,并在 app.module.ts 中自动导入新模块。

  1. 编写实体代码

CLI 默认会在 src/analysis-results/entities/ 下生成一个空的实体文件。请将其内容替换为以下代码:

ts 复制代码
// src/analysis-results/entities/analysis-result.entity.ts
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';

@Entity('analysis_results')
export class AnalysisResult {
    @PrimaryGeneratedColumn('uuid')
    id: string;

    @Column({ type: 'text' })
    taskName: string;

    @Column({ type: 'jsonb', nullable: true })
    inputData?: Record<string, unknown>;

    @Column({ type: 'jsonb' })
    outputData: Record<string, unknown>;

    @Column({ type: 'text', nullable: true })
    modelId?: string;

    @Column({ type: 'varchar', length: 50, default: 'pending' })
    status: string;

    @CreateDateColumn({ type: 'timestamp with time zone' })
    createdAt: Date;

    @UpdateDateColumn({ type: 'timestamp with time zone' })
    updatedAt: Date;
}

注意jsonb 类型仅在 PostgreSQL 中可用。

步骤 5:注册实体与实现 CRUD

虽然 CLI 生成了模块,但 TypeORM 需要我们显式注册实体,才能让 Repository 正常工作。

  1. 在 Feature Module 中注册实体

打开 src/analysis-results/analysis-results.module.ts,添加 TypeOrmModule.forFeature

ts 复制代码
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AnalysisResultsService } from './analysis-results.service';
import { AnalysisResultsController } from './analysis-results.controller';
import { AnalysisResult } from './entities/analysis-result.entity';

@Module({
    imports: [TypeOrmModule.forFeature([AnalysisResult])], // <--- 关键:注册实体
    controllers: [AnalysisResultsController],
    providers: [AnalysisResultsService],
})
export class AnalysisResultsModule {}
  1. 在 Service 中注入 Repository

打开 src/analysis-results/analysis-results.service.ts,注入 Repository 并实现增删改查:

ts 复制代码
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AnalysisResult } from './entities/analysis-result.entity';

@Injectable()
export class AnalysisResultsService {
    constructor(
    @InjectRepository(AnalysisResult)
    private readonly analysisResultRepository: Repository<AnalysisResult>,
    ) {}

    async create(data: Partial<AnalysisResult>): Promise<AnalysisResult> {
    const entity = this.analysisResultRepository.create(data);
    return this.analysisResultRepository.save(entity);
    }

    async findAll(): Promise<AnalysisResult[]> {
    return this.analysisResultRepository.find();
    }
}

如果你希望对入参做验证,通常会配合 DTO + class-validator

bash 复制代码
pnpm add class-validator class-transformer

2.2.4 数据库迁移(Migrations)实战指南

在生产环境中,严禁使用 synchronize: true,因为它可能导致数据丢失。标准的做法是使用 Migrations(迁移文件) 来管理数据库结构变更。

由于 TypeORM 的 CLI 需要独立的配置源,我们按以下步骤配置,确保新手也能一次跑通。

步骤 1:创建 CLI 专用配置文件

src 目录下新建 data-source.ts,这个文件专门给 TypeORM CLI 使用,不影响 NestJS 应用本身的运行。

ts 复制代码
// src/data-source.ts
import { DataSource } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { join } from 'path';

// 为了让 dotenv 在 TS 环境下安全运行
import 'dotenv/config';

// 手动配置路径
process.env.DOTENV_CONFIG_PATH = join(__dirname, '../../.env');

// 实例化一个临时的 ConfigService 供 TypeORM CLI 使用
const configService = new ConfigService();

export const AppDataSource = new DataSource({
  type: 'postgres',
  host: configService.get<string>('DATABASE_HOST', 'localhost'),
  port: Number(configService.get<number>('DATABASE_PORT', 5432)),
  username: configService.get<string>('DATABASE_USER'),
  password: configService.get<string>('DATABASE_PASSWORD'),
  database: configService.get<string>('DATABASE_NAME'),
  // 自动扫描实体文件
  entities: [join(__dirname, '**/*.entity{.ts,.js}')],
  // 迁移文件存放路径
  migrations: [join(__dirname, 'migrations/*{.ts,.js}')],
  synchronize: false, // CLI 模式下必须为 false
  logging: true,
});

步骤 2:配置 package.json 命令

为了方便使用,我们在 package.jsonscripts 中添加快捷命令。请将以下内容合并到你的 scripts 字段中:

json 复制代码
"scripts": {
  // ... 其他原有命令
  "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
  "migration:create": "pnpm typeorm migration:create",
  "migration:generate": "pnpm typeorm migration:generate -d src/data-source.ts",
  "migration:run": "pnpm typeorm migration:run -d src/data-source.ts",
  "migration:revert": "pnpm typeorm migration:revert -d src/data-source.ts"
}

步骤 3:生成并运行迁移

提示:执行迁移命令前,请确保你的 PostgreSQL 数据库服务已经启动并且可以正常连接(配置的 host、port、user、password 和 database 必须正确)。如果没有安装本地数据库,你可以通过 Docker 快速启动一个:

bash 复制代码
# 在项目根目录创建一个 docker-compose.yml 文件
# 然后运行以下命令启动数据库:
docker compose up -d

当你修改了实体(Entity)代码后(比如添加了一个字段),按照以下流程操作:

  1. 生成迁移文件
    TypeORM 会自动对比实体代码和数据库当前的差异,生成 SQL 语句。
bash 复制代码
# 这里的 src/migrations/AddStatus 是迁移文件名,可以自定义
pnpm migration:generate src/migrations/AddStatus

成功后,你会看到 src/migrations/ 下多了一个时间戳开头的文件,里面包含了 up (升级) 和 down (回滚) 的 SQL 逻辑。

  1. 应用迁移
    将生成的 SQL 执行到数据库中。
bash 复制代码
pnpm migration:run
  1. 回滚(可选)
    如果刚才的迁移有问题,可以撤销最后一次迁移。
bash 复制代码
pnpm migration:revert

常见问题 :如果执行命令报错 Connect ECONNREFUSED,请检查 Docker 容器是否开启,或 .env 中的数据库端口配置是否正确。

2.2.5 方案 B:Prisma(概览)

Prisma 采用 Schema-First:先在 schema.prisma 定义数据模型,再生成类型安全的客户端代码。它的迁移工具链与开发体验通常更"现代化"。

安装与初始化

bash 复制代码
pnpm add prisma @prisma/client
pnpm dlx prisma init

示例 Schema(PostgreSQL)

prisma 复制代码
generator client {
  provider = "prisma-client-js"
}

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

model AnalysisResult {
  id         String   @id @default(uuid())
  taskName   String
  inputData  Json?
  outputData Json
  modelId    String?
  status     String   @default("pending")
  createdAt  DateTime @default(now()) @map("created_at")
  updatedAt  DateTime @updatedAt @map("updated_at")

  @@map("analysis_results")
}

迁移与生成

bash 复制代码
# 开发环境快速同步(不生成迁移文件)
pnpm dlx prisma db push

# 推荐的开发迁移(生成并应用迁移)
pnpm dlx prisma migrate dev --name init

在 Nest.js 中通常会封装一个 PrismaService 管理连接,并在业务服务中注入使用。

2.2.6 最佳实践(建议保留)

  • 环境变量管理:数据库凭证、连接串只放在服务端环境变量中,避免写入仓库。
  • 迁移优先 :生产环境关闭 synchronize,用迁移控制变更并可回滚。
  • 事务与一致性:涉及多表更新或"写入 + 状态推进"的链路务必使用事务。
  • 索引与查询 :对高频查询字段(例如 statuscreatedAt、业务维度字段)建立合适索引。
  • 错误处理与重试:连接失败、死锁、唯一键冲突要在服务层做清晰的错误语义与必要重试。
  • 日志与审计:开发环境可开启更详细 SQL 日志,生产环境建议记录慢查询、错误与关键审计事件。

总结

本节完成了"数据基础"这一层的关键认知与落地方式:

  • 理解为什么在 Nest.js 中使用 ORM
  • 明确数据库选型要点(以 PostgreSQL 为例)
  • 掌握 TypeORM 在 Nest.js 中的典型集成路径(配置、实体、仓库注入)
  • 了解生产环境应采用迁移管理表结构
  • 对 Prisma 的集成方式与迁移策略建立整体认知

现在你的 Nest.js 应用已具备"可持续演进"的数据层基础,为后续 AI Agent 的任务记录、结果沉淀、评估与审计打下底座。下一节将讨论如何更安全地管理敏感配置(例如 API Key 等)。

相关推荐
weixin_397574091 小时前
从AI问答到AI执行:企业智能体平台的定位跃迁
人工智能·microsoft
wayz111 小时前
Overlap:HWMA(Holt-Winter移动平均线)技术指标详解
算法·金融·数据分析·量化交易·特征工程
机器学习是魔鬼1 小时前
文献管理+实验复现一体化?矩阵智研V0.1.8 深度体验与招募
人工智能·机器学习·矩池云
wp123_11 小时前
ALPS SPVQ120500与同于科技Tonevee检测开关国产兼容评估
大数据·人工智能·科技
沪漂阿龙1 小时前
Context Engineering:比 Prompt Engineering 更重要的上下文工程
人工智能·langchain·prompt
Zzj_tju1 小时前
Prompt Engineering 为什么不够了:从“写好提示词”到“构建可靠上下文系统”
人工智能·语言模型
杨云龙UP1 小时前
Oracle/ODA RAC /u01 空间告警处理指南:grid 用户监听日志清理_2026-06-15
linux·数据库·oracle·oracle linux·oda·监听日志·在线清理
码农小白AI1 小时前
染料中间体杂质数据都正常,为何报告仍不过审?AI报告审核通审Agent版×IACheck拆解化工检测审核盲点
人工智能
果丁智能1 小时前
物联网智能锁在网约房、民宿领域的落地实践:身份核验与远程授权的全链路技术方案
人工智能·物联网·智能家居