返回介绍

typeORM 操作 Mysql 数据库

发布于 2024-01-18 22:07:39 字数 9883 浏览 0 评论 0 收藏 0

mac 中,直接使用 brew install mysql 安装 mysql,然后启动服务 brew services start mysql 查看服务已经启动 ps aux | grep mysql

Nest 操作 Mysql 官方文档: https://docs.nestjs.com/techniques/database

npm install --save @nestjs/typeorm typeorm mysql

配置数据库连接地址

// src/config/typeorm.ts

const { MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE } =
  process.env;

const config = {
  type: 'mysql',
  host: MYSQL_HOST,
  port: MYSQL_PORT,
  username: MYSQL_USER,
  password: MYSQL_PASSWORD,
  database: MYSQL_DATABASE,
  synchronize: process.env.NODE_ENV !== 'production', // 生产环境不要开启
  autoLoadEntities: true, // 如果为 true,将自动加载实体(默认:false)
  keepConnectionAlive: true, // 如果为 true,在应用程序关闭后连接不会关闭(默认:false)
  retryDelay: 3000, // 两次重试连接的间隔(ms)(默认:3000)
  retryAttempts: 10, // 重试连接数据库的次数(默认:10)
  dateStrings: 'DATETIME', // 转化为时间
  timezone: '+0800', // +HHMM -HHMM
  // 自动需要导入模型
  entities: ['dist/**/*.entity{.ts,.js}'],
};

export default config;
// app.module.ts 中配置
import { resolve, join } from 'path';
import { ConfigModule, ConfigService } from 'nestjs-config';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    // 加载配置文件目录
    ConfigModule.load(resolve(__dirname, 'config', '**/!(*.d).{ts,js}')),

    // 连接 mysql 数据库
    TypeOrmModule.forRootAsync({
      useFactory: (config: ConfigService) => config.get('typeorm'),
      inject: [ConfigService],
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule implements NestModule {}

配置实体 entity

// photo.entity.ts

import {
  Column,
  Entity,
  ManyToMany,
  OneToMany,
  PrimaryGeneratedColumn,
} from 'typeorm';
import { PostsEntity } from './post.entity';

@Entity('photo')
export class PhotoEntity {
  // @PrimaryGeneratedColumn()
  // id: number; // 标记为主列,值自动生成
  @PrimaryGeneratedColumn('uuid')
  id: string; // 该值将使用 uuid 自动生成

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

  // 多对一关系,多个图片对应一篇文章
  @ManyToMany(() => PostsEntity, (post) => post.photos)
  posts: PostsEntity;
}
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { PhotoEntity } from './photo.entity';

export type UserRoleType = 'admin' | 'editor' | 'ghost';
export type postStatus = 1 | 2 | 3;

// mysql 的列类型: type
/**
 * int, tinyint, smallint, mediumint, bigint, float, double, dec, decimal,
 * numeric, date, datetime, timestamp, time, year, char, varchar, nvarchar,
 * text, tinytext, mediumtext, blob, longtext, tinyblob, mediumblob, longblob, enum,
 * json, binary, geometry, point, linestring, polygon, multipoint, multilinestring,
 * multipolygon, geometrycollection
 */
/**
 * ColumnOptions 中可用选项列表:
 * length: number - 列类型的长度。 例如,如果要创建 varchar(150)类型,请指定列类型和长度选项。
  width: number - 列类型的显示范围。 仅用于 MySQL integer types(opens new window)
  onUpdate: string - ON UPDATE 触发器。 仅用于 MySQL (opens new window).
  nullable: boolean - 在数据库中使列 NULL 或 NOT NULL。 默认情况下,列是 nullable:false。
  update: boolean - 指示"save"操作是否更新列值。如果为 false,则只能在第一次插入对象时编写该值。 默认值为"true"。
  select: boolean - 定义在进行查询时是否默认隐藏此列。 设置为 false 时,列数据不会显示标准查询。 默认情况下,列是 select:true
  default: string - 添加数据库级列的 DEFAULT 值。
  primary: boolean - 将列标记为主要列。 使用方式和 @ PrimaryColumn 相同。
  unique: boolean - 将列标记为唯一列(创建唯一约束)。
  comment: string - 数据库列备注,并非所有数据库类型都支持。
  precision: number - 十进制(精确数字)列的精度(仅适用于十进制列),这是为值存储的最大位数。仅用于某些列类型。
  scale: number - 十进制(精确数字)列的比例(仅适用于十进制列),表示小数点右侧的位数,且不得大于精度。 仅用于某些列类型。
  zerofill: boolean - 将 ZEROFILL 属性设置为数字列。 仅在 MySQL 中使用。 如果是 true,MySQL 会自动将 UNSIGNED 属性添加到此列。
  unsigned: boolean - 将 UNSIGNED 属性设置为数字列。 仅在 MySQL 中使用。
  charset: string - 定义列字符集。 并非所有数据库类型都支持。
  collation: string - 定义列排序规则。
  enum: string[]|AnyEnum - 在 enum 列类型中使用,以指定允许的枚举值列表。 你也可以指定数组或指定枚举类。
  asExpression: string - 生成的列表达式。 仅在 MySQL (opens new window) 中使用。
  generatedType: "VIRTUAL"|"STORED" - 生成的列类型。 仅在 MySQL (opens new window) 中使用。
  hstoreType: "object"|"string" -返回 HSTORE 列类型。 以字符串或对象的形式返回值。 仅在 Postgres 中使用。
  array: boolean - 用于可以是数组的 postgres 列类型(例如 int [])
  transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType } - 用于将任意类型 EntityType 的属性编组为数据库支持的类型 DatabaseType。
  注意:大多数列选项都是特定于 RDBMS 的,并且在 MongoDB 中不可用
 */

@Entity('posts')
export class PostsEntity {
  // @PrimaryGeneratedColumn()
  // id: number; // 标记为主列,值自动生成
  @PrimaryGeneratedColumn('uuid')
  id: string; // 该值将使用 uuid 自动生成

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

  @Column({ length: 18 })
  author: string;

  @Column({ type: 'longtext', default: null })
  content: string;

  @Column({ default: null })
  cover_url: string;

  @Column({ default: 0 })
  type: number;

  @Column({ type: 'text', default: null })
  remark: string;

  @Column({
    type: 'enum',
    enum: [1, 2, 3],
    default: 1,
  })
  status: postStatus;

  @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
  create_time: Date;

  @Column({
    type: 'timestamp',
    default: () => 'CURRENT_TIMESTAMP',
  })
  update_time: Date;

  @Column({
    type: 'enum',
    enum: ['admin', 'editor', 'ghost'],
    default: 'ghost',
    select: false, // 定义在进行查询时是否默认隐藏此列
  })
  role: UserRoleType;

  // 一对多关系,一篇文章对应多个图片
  // 在 service 中查询使用 .find({relations: ['photos]}) 查询文章对应的图片
  @OneToMany(() => PhotoEntity, (photo) => photo.posts)
  photos: [];
}

参数校验

Nest 与 class-validator 配合得很好。这个优秀的库允许您使用基于装饰器的验证。装饰器的功能非常强大,尤其是与 Nest 的 Pipe 功能相结合使用时,因为我们可以通过访问 metatype 信息做很多事情,在开始之前需要安装一些依赖。

npm i --save class-validator class-transformer
// posts.dto.ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';

export class CreatePostDto {
  @IsNotEmpty({ message: '文章标题必填' })
  readonly title: string;

  @IsNotEmpty({ message: '缺少作者信息' })
  readonly author: string;

  readonly content: string;

  readonly cover_url: string;

  @IsNotEmpty({ message: '缺少文章类型' })
  readonly type: number;

  readonly remark: string;
}

在控制器对应的 Module 中配置 Model

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
import { PostsEntity } from './entities/post.entity';

@Module({
  imports: [TypeOrmModule.forFeature([PostsEntity])],
  controllers: [PostsController],
  providers: [PostsService],
})
export class PostsModule {}

在服务里面使用 @InjectRepository 获取数据库 Model 实现操作数据库

// posts.services.ts
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Not, Between, Equal, Like, In } from 'typeorm';
import * as dayjs from 'dayjs';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { PostsEntity } from './entities/post.entity';
import { PostsRo } from './interfaces/posts.interface';

@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(PostsEntity)
    private readonly postsRepository: Repository<PostsEntity>,
  ) {}

  async create(post: CreatePostDto) {
    const { title } = post;
    const doc = await this.postsRepository.findOne({ where: { title } });
    console.log('doc', doc);
    if (doc) {
      throw new HttpException('文章标题已存在', HttpStatus.BAD_REQUEST);
    }
    return {
      data: await this.postsRepository.save(post),
      message: '创建成功',
    };
  }

  // 分页查询列表
  async findAll(query = {} as any) {
    // eslint-disable-next-line prefer-const
    let { pageSize, pageNum, orderBy, sort, ...params } = query;
    orderBy = query.orderBy || 'create_time';
    sort = query.sort || 'DESC';
    pageSize = Number(query.pageSize || 10);
    pageNum = Number(query.pageNum || 1);
    console.log('query', query);

    const queryParams = {} as any;
    Object.keys(params).forEach((key) => {
      if (params[key]) {
        queryParams[key] = Like(`%${params[key]}%`); // 所有字段支持模糊查询、%%之间不能有空格
      }
    });
    const qb = await this.postsRepository.createQueryBuilder('post');

    // qb.where({ status: In([2, 3]) });
    qb.where(queryParams);
    // qb.select(['post.title', 'post.content']); // 查询部分字段返回
    qb.orderBy(`post.${orderBy}`, sort);
    qb.skip(pageSize * (pageNum - 1));
    qb.take(pageSize);

    return {
      list: await qb.getMany(),
      totalNum: await qb.getCount(), // 按条件查询的数量
      total: await this.postsRepository.count(), // 总的数量
      pageSize,
      pageNum,
    };
  }

  // 根据 ID 查询详情
  async findById(id: string): Promise<PostsEntity> {
    return await this.postsRepository.findOne({ where: { id } });
  }

  // 更新
  async update(id: string, updatePostDto: UpdatePostDto) {
    const existRecord = await this.postsRepository.findOne({ where: { id } });
    if (!existRecord) {
      throw new HttpException(`id 为${id}的文章不存在`, HttpStatus.BAD_REQUEST);
    }
    // updatePostDto 覆盖 existRecord 合并,可以更新单个字段
    const updatePost = this.postsRepository.merge(existRecord, {
      ...updatePostDto,
      update_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
    });
    return {
      data: await this.postsRepository.save(updatePost),
      message: '更新成功',
    };
  }

  // 删除
  async remove(id: string) {
    const existPost = await this.postsRepository.findOne({ where: { id } });
    if (!existPost) {
      throw new HttpException(`文章 ID ${id} 不存在`, HttpStatus.BAD_REQUEST);
    }
    await this.postsRepository.remove(existPost);
    return {
      data: { id },
      message: '删除成功',
    };
  }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文