今天这篇保姆级教程,手把手教你用 NestJS + TypeORM 自定义数据源,实现一套代码无缝切换达梦数据库和MySQL!全程实战代码,从环境搭建到CRUD完整示例,让你在国产化项目中既能享受达梦的合规性,又能保留MySQL的开发便利性。

数据库连接模块可以打包直接复用。

如果你觉得有用,请关注、点赞👍、收藏🌟、评论💬支持一下!文末还有粉丝福利!


1. 先搞懂核心原理

NestJS 本身不实现 ORM,而是通过模块化集成 TypeORM。但官方 @nestjs/typeorm 模块对达梦这种非原生数据库支持不够友好,容易遇到 oracledb 驱动冲突、方言包补丁失效等问题。

我们的解决方案:

  • 放弃 @nestjs/typeorm,改用自定义提供者手动创建数据源。

  • 通过环境变量 DB_TYPE 动态选择创建 MySQL 数据源DataSource)或 达梦数据源DmdbDataSource)。

  • 将创建好的数据源实例通过 'DATA_SOURCE' 令牌注入到各个服务中。

优势:
✅ 彻底避免驱动冲突,连接更稳定
✅ 一套 Service 代码,无需修改即可操作两种数据库
✅ 完全掌控数据库连接生命周期


2. 环境准备与依赖安装

Node.js 版本要求

  • 推荐 Node.js 16.x 或 18.x LTS(达梦驱动对高版本支持不完善)

  • 可用 nvm 或 nvm-windows 管理版本

创建项目并安装依赖

bash

# 创建 NestJS 项目
nest new nest-dm-mysql-demo
cd nest-dm-mysql-demo

# 安装必要依赖(注意:不安装 @nestjs/typeorm)
npm install @nestjs/config typeorm mysql2 dmdb typeorm-dm reflect-metadata

依赖说明:

  • @nestjs/config:读取环境变量

  • typeorm:TypeORM 核心(被 typeorm-dm 依赖)

  • mysql2:MySQL 驱动

  • dmdb:达梦官方 Node.js 驱动

  • typeorm-dm:达梦方言包(提供 DmdbDataSource


3. 配置环境变量(核心!)

在项目根目录创建 .env 文件,通过 DB_TYPE 控制使用哪种数据库:

env

# 数据库类型:mysql 或 dm
DB_TYPE=mysql

# 通用数据库连接信息
DB_HOST=localhost
DB_USERNAME=root
DB_PASSWORD=123456

# MySQL 专用配置
MYSQL_PORT=3306
MYSQL_DB=test_db

# 达梦专用配置
DM_PORT=5236
DM_SCHEMA=TEST   # 达梦模式名,通常与用户名相同

切换数据库只需修改 DB_TYPE 这一个变量!


4. 数据库模块:动态创建数据源(关键代码)

创建 src/database/database.module.ts,这是整个方案的核心:

import { Module, Global, Logger } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { DataSource } from 'typeorm';
import { DmdbDataSource } from 'typeorm-dm';
import { User } from '../user/user.entity';

@Global()
@Module({
  imports: [ConfigModule],
  providers: [
    {
      provide: 'DATA_SOURCE',
      useFactory: async (configService: ConfigService) => {
        const logger = new Logger('DatabaseModule');
        const dbType = configService.get<string>('DB_TYPE');

        let dataSource: DataSource;

        if (dbType === 'mysql') {
          // MySQL 数据源
          dataSource = new DataSource({
            type: 'mysql',
            host: configService.get<string>('DB_HOST'),
            port: configService.get<number>('MYSQL_PORT'),
            username: configService.get<string>('DB_USERNAME'),
            password: configService.get<string>('DB_PASSWORD'),
            database: configService.get<string>('MYSQL_DB'),
            entities: [User],
            synchronize: true,      // 开发环境使用,生产请关闭
            logging: false,
          });
        } else if (dbType === 'dm') {
          // 达梦数据源
          dataSource = new DmdbDataSource({
            type: 'oracle',
            innerType: 'dmdb',
            host: configService.get<string>('DB_HOST'),
            port: configService.get<number>('DM_PORT'),
            username: configService.get<string>('DB_USERNAME'),
            password: configService.get<string>('DB_PASSWORD'),
            schema: configService.get<string>('DM_SCHEMA'),
            entities: [User],
            synchronize: true,
            logging: false,
            extra: {
              connectTimeout: 30000,
              loginEncrypt: false,   // 避免达梦加密错误
            },
          });
        } else {
          throw new Error(`不支持的数据库类型: ${dbType}`);
        }

        await dataSource.initialize();
        logger.log(`${dbType} 数据库连接成功!`);
        return dataSource;
      },
      inject: [ConfigService],
    },
  ],
  exports: ['DATA_SOURCE'],
})
export class DatabaseModule {}

要点说明:

  • type: 'oracle' 和 innerType: 'dmdb' 是达梦方言包的固定写法。

  • loginEncrypt: false 用于解决达梦加密错误 [6071]

  • entities 数组注册了所有实体,可随时添加。


5. 定义用户实体

创建 src/user/user.entity.ts

 

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity('t_user')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 50 })
  name: string;

  @Column({ type: 'int', nullable: true })
  age: number;
}

注意:实体定义完全兼容两种数据库,无需任何修改。


6. 用户模块与服务

用户模块 src/user/user.module.ts

typescript

import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
  providers: [UserService],
  controllers: [UserController],
})
export class UserModule {}

用户服务 src/user/user.service.ts

typescript

import { Injectable, Inject, NotFoundException } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService {
  private userRepository: Repository<User>;

  constructor(@Inject('DATA_SOURCE') private dataSource: DataSource) {
    this.userRepository = this.dataSource.getRepository(User);
  }

  async create(userData: Partial<User>): Promise<User> {
    const user = this.userRepository.create(userData);
    return this.userRepository.save(user);
  }

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

  async findOne(id: number): Promise<User | null> {
    return this.userRepository.findOneBy({ id });
  }

  async update(id: number, updateData: Partial<User>): Promise<User> {
    const user = await this.findOne(id);
    if (!user) {
      throw new NotFoundException(`用户 ID ${id} 不存在`);
    }
    Object.assign(user, updateData);
    return this.userRepository.save(user);
  }

  async remove(id: number): Promise<void> {
    const result = await this.userRepository.delete(id);
    if (result.affected === 0) {
      throw new NotFoundException(`用户 ID ${id} 不存在`);
    }
  }
}

关键点:服务中注入的是 DataSource 类型,无论底层是 MySQL 还是达梦,都能正确获取 Repository

用户控制器 src/user/user.controller.ts

typescript

import { Controller, Get, Post, Body, Param, Put, Delete } from '@nestjs/common';
import { UserService } from './user.service';
import { User } from './user.entity';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  create(@Body() userData: Partial<User>): Promise<User> {
    return this.userService.create(userData);
  }

  @Get()
  findAll(): Promise<User[]> {
    return this.userService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string): Promise<User | null> {
    return this.userService.findOne(+id);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateData: Partial<User>): Promise<User> {
    return this.userService.update(+id, updateData);
  }

  @Delete(':id')
  remove(@Param('id') id: string): Promise<void> {
    return this.userService.remove(+id);
  }
}

7. 根模块整合

修改 src/app.module.ts,导入数据库模块和用户模块:

typescript

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { DatabaseModule } from './database/database.module';
import { UserModule } from './user/user.module';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.env',
    }),
    DatabaseModule,
    UserModule,
  ],
})
export class AppModule {}

8. 运行测试

启动应用

bash

npm run start:dev

看到如下日志表示连接成功:

text

[Nest] ... LOG [DatabaseModule] mysql 数据库连接成功!
或
[Nest] ... LOG [DatabaseModule] dm 数据库连接成功!

测试 CRUD 接口

方法 路径 功能 请求体示例
POST /users 创建用户 {"name": "张三", "age": 30}
GET /users 获取所有用户 -
GET /users/:id 获取单个用户 -
PUT /users/:id 更新用户 {"age": 35}
DELETE /users/:id 删除用户 -

使用 Postman 或 curl 测试:

bash

# 创建用户
curl -X POST http://localhost:3000/users -H "Content-Type: application/json" -d '{"name":"李四","age":25}'

# 查询所有用户
curl http://localhost:3000/users


9. 常见问题与解决方案

❌ 问题1:达梦连接时报 [6071] 消息加密失败

解决:在达梦配置的 extra 中添加 loginEncrypt: false

❌ 问题2:启动后仍提示 Oracle package not found

原因typeorm-dm 的补丁未生效,或错误地安装了 @nestjs/typeorm
解决:确保卸载 @nestjs/typeorm,并删除 node_modules 重新安装。

❌ 问题3:MySQL 连接时区问题

解决:在 MySQL 配置中添加 timezone: 'Z' 或 timezone: '+08:00'

❌ 问题4:Node.js 版本过高导致编译失败

解决:降级到 Node.js 16 或 18 LTS。

 

如果你觉得有用,请点赞👍、收藏🌟、评论💬支持一下!文末还有粉丝福利!

粉丝提供完整源码!

 

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐