Nest.js 系列——在 Nest.js 中使用 Typeorm

2023年 8月 18日 56.5k 0

在 nest 中使用 typeorm

typeorm 基础使用

初始化一个 typeorm 项目

npx typeorm init --name typeorm_learn --database mysql
  • --name: 项目名称
  • --database: 数据库类型

执行以上命令后,会在当前目录下生成一个 typeorm_learn 的文件夹,里面包含了一个 typeorm 项目的基本结构。一般是使用 mysql2 来连接 mysql 数据库,所以需要安装 mysql2 依赖。

pnpm i mysql2

配置数据库连接

import 'reflect-metadata'
import { DataSource } from 'typeorm'
import { User } from './entity/User'

export const AppDataSource = new DataSource({
  // 数据库类型
  type: 'mysql',
  // 数据库地址
  host: 'localhost',
  // 数据库端口
  port: 3306,
  // 用户名
  username: 'root',
  // 密码
  password: '123456',
  // 数据库名称
  database: 'typeorm_learn',
  // 同步数据库
  synchronize: true,
  // 开启打印生成sql语句
  logging: false,
  // 实体类
  entities: ['./**/entity/*.ts'],

  migrations: [],
  // 订阅
  subscribers: [],
  // 连接池
  connectorPackage: 'mysql2'
})

创建实体类

数据库中的表是根据实体类来创建的,所以需要创建实体类。看下生成的默认实体类 User.ts

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

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

  @Column()
  firstName: string

  @Column()
  lastName: string

  @Column()
  age: number
}

启动下项目,会发现数据库中多了一个 user 表,表中有 idfirstNamelastNameage 四个字段。而且控制台还打印出了生成的 sql 语句。

image.png

image.png

通过控制台和数据库表可以看出,typeorm 会根据实体类来创建表,而且会根据实体类中的字段来创建表中的字段。然后会有默认的数据类型映射到数据库中,比如 firstNamelastNameage 都是 string 类型,但是在数据库中都是 varchar 类型。但是有些时候想要自定义类型,并不是使用这样的默认类型,这时候就需要使用装饰器来自定义类型。

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

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

  @Column({
    type: 'varchar',
    length: 20,
    name: 'first_name',
    comment: '姓氏'
  })
  firstName: string

  @Column({
    type: 'varchar',
    name: 'last_name',
    length: 20,
    comment: '名字'
  })
  lastName: string

  @Column({
    type: 'int',
    comment: '年龄'
  })
  age: number

  @Column({
    type: 'double',
    comment: 'num'
  })
  num: number

  @Column({
    type: 'text',
    comment: 'text'
  })
  text: string
}
  • @Entity: 标识这是一个实体类
  • @PrimaryGeneratedColumn: 标识这是一个主键,并且是自增的
  • @Column: 标识这是一个字段,并且可以自定义字段的类型、长度、名称、注释等
  • @Colum 中可以自定义字段的类型,但是需要注意的是,如果是 mysql 数据库,那么 type 的值需要是 mysql 数据库中的数据类型,比如 varcharintdoubletext 等。按照这个实体重新生成 user

image.png

image.png

通过对实体的定义就可以新建表以及确定数据的类型。

对数据库表进行增删改查

新增和修改

  • save 方法
  • index.ts 进行测试添加

    import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        console.log('Inserting a new user into the database...')
        const user = new User()
        user.firstName = 'Timber'
        user.lastName = 'Saw'
        user.age = 25
        user.count = 1.1
        user.text = 'text'
        await AppDataSource.manager.save(user)
        console.log('Saved a new user with id: ' + user.id)
    
        console.log('Loading users from the database...')
        const users = await AppDataSource.manager.find(User)
        console.log('Loaded users: ', users)
    
        console.log('Here you can setup and run express / fastify / any other framework.')
      })
      .catch((error) => console.log(error))
    

    在命令行重新启动下项目,可以看到控制台打印出了新增的 user 数据

    image.png

    如果在save的时候指定了id,那么会更新数据

    import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        console.log('Inserting a new user into the database...')
        const user = new User()
        user.id = 1
        user.firstName = 'water'
        user.lastName = 'Saw'
        user.age = 25
        user.count = 1.1
        user.text = 'text'
        await AppDataSource.manager.save(user)
        console.log('Saved a new user with id: ' + user.id)
    
        console.log('Loading users from the database...')
        const users = await AppDataSource.manager.find(User)
        console.log('Loaded users: ', users)
    
        console.log('Here you can setup and run express / fastify / any other framework.')
      })
      .catch((error) => console.log(error))
    

    在命令行重新启动下项目,可以看到控制台打印出了更新的 user 数据

    image.png

    如果想要批量插入或者修改数据,可以使用 save 方法,修改的话和上面的一样,传入id就行了

    import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        await AppDataSource.manager.save(User, [
          {
            firstName: 'water1',
            lastName: 'Saw',
            age: 25,
            count: 1.1,
            text: 'text'
          },
          {
            firstName: 'water2',
            lastName: 'Saw',
            age: 18,
            count: 1.1,
            text: 'text11'
          }
        ])
      })
      .catch((error) => console.log(error))
    

    image.png

  • insert 方法
  • import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        console.log('Inserting a new user into the database...')
        const user = new User()
        user.firstName = 'water'
        user.lastName = 'Saw'
        user.age = 25
        user.count = 1.1
        user.text = 'text'
        await AppDataSource.manager.insert(User, user)
        console.log('Saved a new user with id: ' + user.id)
    
        console.log('Loading users from the database...')
        const users = await AppDataSource.manager.find(User)
        console.log('Loaded users: ', users)
    
        console.log('Here you can setup and run express / fastify / any other framework.')
      })
      .catch((error) => console.log(error))
    

    image.png
    save的区别是不会先查一遍表,而是直接插入数据。

  • update 方法
  • import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        console.log('Inserting a new user into the database...')
        const user = new User()
        user.firstName = 'water22'
        user.lastName = 'Saw'
        user.age = 25
        user.count = 1.1
        user.text = 'text'
        await AppDataSource.manager.update(User, 1, user)
        console.log('Saved a new user with id: ' + user.id)
    
        console.log('Loading users from the database...')
        const users = await AppDataSource.manager.find(User)
        console.log('Loaded users: ', users)
    
        console.log('Here you can setup and run express / fastify / any other framework.')
      })
      .catch((error) => console.log(error))
    

    image.png

    删除数据

  • remove 方法
  • import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        console.log('Inserting a new user into the database...')
        const user = new User()
        user.id = 1
        await AppDataSource.manager.remove(User, user)
        console.log('Saved a new user with id: ' + user.id)
    
        console.log('Loading users from the database...')
        const users = await AppDataSource.manager.find(User)
        console.log('Loaded users: ', users)
    
        console.log('Here you can setup and run express / fastify / any other framework.')
      })
      .catch((error) => console.log(error))
    

    image.png

  • delete 方法
  • import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        console.log('Inserting a new user into the database...')
        await AppDataSource.manager.delete(User, 2)
        await AppDataSource.manager.delete(User, [3, 4])
      })
      .catch((error) => console.log(error))
    

    如果传递的是一个id就是单个删除,如果传递的是个数组就是批量删除

    image.png

    removedelete的区别是remove会先查询一遍表,然后再删除,而delete是直接删除。

    查询数据

  • find 方法
  • import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        console.log('Inserting a new user into the database...')
        const users = await AppDataSource.manager.find(User)
        console.log(users)
      })
      .catch((error) => console.log(error))
    

    image.png

    也可以添加where条件

    import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        const users = await AppDataSource.manager.find(User, {
          where: {
            age: 18
          }
        })
        console.log(users)
      })
      .catch((error) => console.log(error))
    
  • findOne 方法
  • import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        console.log('Inserting a new user into the database...')
        const user = await AppDataSource.manager.findOne(User, {
          select: {
            firstName: true,
            age: true
          },
          where: {
            id: 7
          },
          order: {
            age: 'ASC'
          }
        })
        console.log(user)
      })
      .catch((error) => console.log(error))
    

    image.png

  • findBy 方法
  • import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        const users = await AppDataSource.manager.findBy(User, {
          age: 18
        })
        console.log(users)
      })
      .catch((error) => console.log(error))
    

    image.png

  • findAndCount 方法
  • import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        const [users, count] = await AppDataSource.manager.findAndCount(User)
        console.log(users, count)
      })
      .catch((error) => console.log(error))
    

    image.png

    也可以添加where条件

    import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        const [users, count] = await AppDataSource.manager.findAndCount(User, {
          where: {
            age: 18
          }
        })
        console.log(users, count)
      })
      .catch((error) => console.log(error))
    
  • findOneBy 方法
  • import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        const user = await AppDataSource.manager.findOneBy(User, {
          age: 18
        })
        console.log(user)
      })
      .catch((error) => console.log(error))
    

    image.png

  • findOneOrFailfindOneByOrFail 方法
  • import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        const user = await AppDataSource.manager.findOneOrFail(User, { where: { id: 1 } })
        console.log(user)
      })
      .catch((error) => console.log(error))
    

    image.png

    如果没有查询到数据,会抛出异常

    query

    有时候可能想直接执行一个简单的sql语句,那么可以使用query方法

    import { AppDataSource } from './data-source'
    
    AppDataSource.initialize()
      .then(async () => {
        const users = await AppDataSource.manager.query(
          'select * from user where age in(?, ?)',
          [18, 30]
        )
        console.log(users)
      })
      .catch((error) => console.log(error))
    

    image.png

    query builder

    query builder 是一个链式调用的方式来构建sql语句,可以使用它来构建复杂的sql语句。

    import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        const queryBuilder = await AppDataSource.manager.createQueryBuilder()
    
        const user = await queryBuilder
          .select('user')
          .from(User, 'user')
          .where('user.age = :age', { age: 18 })
          .getOne()
    
        console.log(user)
      })
      .catch((error) => console.log(error))
    

    image.png

    事务 transaction

    import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        await AppDataSource.manager.transaction(async (manager) => {
          await manager.save(User, {
            id: 4,
            firstName: 'water55',
            lastName: 'ice',
            age: 20
          })
        })
      })
      .catch((error) => console.log(error))
    

    这样就开启了事务

    image.png

    这里只是模拟下开启,实际上没有做任何操作,所以没有打印出任何数据。

    getRepository

    在上面的方法中,每次的操作都会在manager上传递实体,如果想要简化操作,可以使用getRepository方法,拿到这个实体的repository,然后就可以直接使用这个repository来操作数据库了。

    import { AppDataSource } from './data-source'
    import { User } from './entity/User'
    
    AppDataSource.initialize()
      .then(async () => {
        const userRepository = AppDataSource.manager.getRepository(User)
        const user = await userRepository.findOne(1)
        console.log(user)
      })
      .catch((error) => console.log(error))
    

    所有的方法用法同之前的直接调用,只是这里改成了实体的repository来调用。

    上面介绍了typeorm的基本使用,包括了数据库的连接、实体类的创建、实体类的字段类型、实体类的增删改查、事务、queryquery buildergetRepository。确实总体就是这些方法,用的时候只需要根据自己的需求来选择合适的方法就行了。

    • save:新增或者修改 Entity,如果传入了 id 会先 select 再决定修改还新增
    • update:直接修改 Entity,不会先 select
    • insert:直接插入 Entity
    • delete:删除 Entity,通过 id
    • remove:删除 Entity,通过对象
    • find:查找多条记录,可以指定 where、order by 等条件
    • findBy:查找多条记录,第二个参数直接指定 where 条件,更简便一点
    • findAndCount:查找多条记录,并返回总数量
    • findByAndCount:根据条件查找多条记录,并返回总数量
    • findOne:查找单条记录,可以指定 where、order by 等条件
    • findOneBy:查找单条记录,第二个参数直接指定 where 条件,更简便一点
    • findOneOrFail:查找失败会抛 EntityNotFoundError 的异常
    • query:直接执行 sql 语句
    • createQueryBuilder:创建复杂 sql 语句,比如 join 多个 Entity 的查询
    • transaction:包裹一层事务的 sql
    • getRepository:拿到对单个 Entity 操作的类,方法同 EntityManager

    nest 中接入并使用typeorm

    新建一个 nest 项目,然后引入需要使用的依赖

    pnpm add @nestjs/typeorm typeorm mysql2
    

    然后在app.module.ts中引入typeorm模块

    import { Module } from '@nestjs/common'
    import { AppController } from './app.controller'
    import { AppService } from './app.service'
    import { TypeOrmModule } from '@nestjs/typeorm'
    
    @Module({
      imports: [
        UserModule,
        TypeOrmModule.forRoot({
          type: 'mysql',
          host: 'localhost',
          port: 3306,
          username: 'root',
          password: '123456',
          database: 'typeorm_test',
          synchronize: true,
          logging: true,
          entities: ['./**/entity/*.ts'],
          connectorPackage: 'mysql2'
        })
      ],
      controllers: [AppController],
      providers: [AppService]
    })
    export class AppModule {}
    

    然后在就是定义实体,这个和单独用 typeorm 没什么区别,这里就不再赘述了。

    一般操作数据库都是在service中,所以在user.service.ts中引入typeorm,然后就可以使用typeorm的方法来操作数据库了。

    import { Injectable } from '@nestjs/common'
    import { InjectEntityManager } from '@nestjs/typeorm'
    import { EntityManager } from 'typeorm'
    import { CreateUserDto } from './dto/create-user.dto'
    import { UpdateUserDto } from './dto/update-user.dto'
    import { User } from './entities/user.entity'
    
    @Injectable()
    export class UserService {
      @InjectEntityManager()
      private manager: EntityManager
    
      create(createUserDto: CreateUserDto) {
        this.manager.save(User, createUserDto)
      }
    
      findAll() {
        return this.manager.find(User)
      }
    
      findOne(id: number) {
        return this.manager.findOne(User, {
          where: { id }
        })
      }
    
      update(id: number, updateUserDto: UpdateUserDto) {
        this.manager.save(User, {
          id: id,
          ...updateUserDto
        })
      }
    
      remove(id: number) {
        this.manager.delete(User, id)
      }
    }
    

    然后就是在controller中调用service中的方法了。

    import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'
    import { UserService } from './user.service'
    import { CreateUserDto } from './dto/create-user.dto'
    import { UpdateUserDto } from './dto/update-user.dto'
    
    @Controller('user')
    export class UserController {
      constructor(private readonly userService: UserService) {}
    
      @Post()
      create(@Body() createUserDto: CreateUserDto) {
        return this.userService.create(createUserDto)
      }
    
      @Get()
      findAll() {
        return this.userService.findAll()
      }
    
      @Get(':id')
      findOne(@Param('id') id: string) {
        return this.userService.findOne(+id)
      }
    
      @Patch(':id')
      update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
        return this.userService.update(+id, updateUserDto)
      }
    
      @Delete(':id')
      remove(@Param('id') id: string) {
        return this.userService.remove(+id)
      }
    }
    

    这样就可以在nest中使用typeorm了。这时候已经可以做增删改查的业务了,但是有一个小的地方可以优化下,就是在service中每次都需要注入manager,这样会比较麻烦,所以可以使用@InjectRepository来注入repository,然后就可以直接使用repository来操作数据库了。

    import { Injectable } from '@nestjs/common'
    import { InjectRepository } from '@nestjs/typeorm'
    import { Repository } from 'typeorm'
    import { CreateUserDto } from './dto/create-user.dto'
    import { UpdateUserDto } from './dto/update-user.dto'
    import { User } from './entities/user.entity'
    
    @Injectable()
    export class UserService {
      constructor(
        @InjectRepository(User)
        private userRepository: Repository
      ) {}
    
      create(createUserDto: CreateUserDto) {
        this.userRepository.save(createUserDto)
      }
    
      findAll() {
        return this.userRepository.find()
      }
    
      findOne(id: number) {
        return this.userRepository.findOne(id)
      }
    
      update(id: number, updateUserDto: UpdateUserDto) {
        this.userRepository.save({
          id: id,
          ...updateUserDto
        })
      }
    
      remove(id: number) {
        this.userRepository.delete(id)
      }
    }
    

    这样就可以直接使用repository来操作数据库了。还需要在当前模块引入 TypeOrmModule.forFeature 对应的动态模块 user 实体类

    import { Module } from '@nestjs/common'
    import { UserService } from './user.service'
    import { UserController } from './user.controller'
    import { TypeOrmModule } from '@nestjs/typeorm'
    import { User } from './entities/user.entity'
    
    @Module({
      imports: [TypeOrmModule.forFeature([User])],
      controllers: [UserController],
      providers: [UserService]
    })
    export class UserModule {}
    

    这样就可以使用repository来操作数据库了。会简洁一些

    小结

    本文就简单介绍了typeorm的基本使用,以及在nest中使用typeorm的方法。typeorm的使用还是比较简单的,只需要根据自己的需求来选择合适的方法就行了。希望对你有帮助。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论