Skip to content

对于ORM的理解

环境信息:Node.js 20.x、TypeORM 0.3.x、MySQL 8.x、@nestjs/typeorm 10.x

一、对于ORM的理解#

面向对象编程和关系型数据库,都是目前最流行的技术,但是它们的模型是不一样的。

面向对象编程把所有实体看成对象(object),关系型数据库则是采用实体之间的关系(relation)连接数据。很早就有人提出,关系也可以用对象表达,这样的话,就能使用面向对象编程,来操作关系型数据库。

image-20250201152823282

简单说,ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术,是”对象-关系映射”(Object/Relational Mapping) 的缩写。

ORM 把数据库映射成对象

举例来说,下面是一行 SQL 语句。

SELECT id, first_name, last_name, phone, birth_date, sex
FROM persons
WHERE id = 10

如果程序直接操作SQL,大致语法如下:

res = db.execSql(sql)
name = res[0]['FIRST_NAME']

如果改成 ORM 的写法大致如下:

p = Person.get(10)
name = p.first_name

一比较就可以发现,ORM 使用对象,封装了数据库操作,因此可以不碰 SQL 语言。开发者只使用面向对象编程,与数据对象直接交互,不用关心底层数据库。

ORM 有下面这些优点:

  • 数据模型都在一个地方定义,更容易更新和维护,也利于重用代码。
  • ORM 有现成的工具,很多功能都可以自动完成,比如数据消毒、预处理、事务等等。
  • 它迫使你使用 MVC 架构,ORM 就是天然的 Model,最终使代码更清晰。
  • 基于 ORM 的业务代码比较简单,代码量少,语义性好,容易理解。
  • 你不必编写性能不佳的 SQL。

但是,ORM 也有很突出的缺点。

  • ORM 库不是轻量级工具,需要花精力学习和设置。
  • 对于复杂的查询,ORM 要么是无法表达,要么是性能不如原生的 SQL。
  • ORM 抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的 SQL。

二、TypeORM#

TypeORM(中文文档)是一个ORM,可以在 NodeJS、浏览器、Cordova、PhoneGap、Ionic、React Native、NativeScript、Expo 和 Electron 平台上运行,并且可以与 TypeScript 和 JavaScript(ES5、ES6、ES7、ES8)一起使用。

TypeORM 受到其他 ORM 的很大影响,例如 HibernateDoctrineEntity Framework

直接开始#

直接运行命令创建TypeORM项目

npx typeorm init --name typeorm-test --database mysql2

没有安装TypeORM包的话,使用npx命令时,系统会自动检测是否已经安装这个包。

Terminal window
npm http fetch GET 200 https://registry.npmjs.org/npm 729ms (cache updated)
npm http fetch GET 200 https://registry.npmjs.org/typeorm 1538ms (cache miss)
Need to install the following packages:
typeorm@0.3.20
Ok to proceed? (y) y

选择“y”后,脚本将自动安装相关的依赖包

不过项目并不会自动帮你安装mysql的依赖包,所以项目创建好之后,还需在项目中安装mysql2的相关依赖

npm i mysql2 --save

创建模型及实体#

要处理数据库,首先当然要创建表,在TypeORM中,我们通过定义实体来告诉他如何创建一个数据表,比如,我们要创建一个用户表,就需要定义用户的实体模型

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 100 })
name: string
@Column({ length: 100 })
nickname: string
@Column()
age: number
@Column({ length: 11 })
phone: string
@Column('text')
desc: string
@Column('double', {
default: 0,
})
other: number
}

我们仅仅定义一个User模型并不够,还需要使用装饰器@Entity将其定义为一个实体

用于装饰列的装饰器包括 @Column@PrimaryGeneratedColumn@PrimaryColumn@PrimaryColumn用于定义普通主键列,而@PrimaryGeneratedColumn用于定义自增主键列。

string类型默认映射为数据库中的varchar(255)number类型默认映射为int整数类型。如果我们希望某个字段是文本(Text)类型或者(Double)类型,可以通过@Column装饰器指定

连接数据库#

为了和数据库进行交互,我们需要设置一个数据源(DataSource)。数据源中包含了与数据库连接相关的配置。通过这些配置,我们可以建立与数据库的初始连接或者连接池。

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_test',
synchronize: true,
logging: true,
entities: [User],
migrations: [],
subscribers: [],
})

还有一些其他的配置:

创建DataSource实例之后,需要调用该实例的initialize方法来创建数据库连接。

使用实体管理器操作数据库#

一旦数据库连接成功,我们就可以使用实体管理器(manager)来执行数据库操作,在src/index.ts文件下,稍作修改

import { AppDataSource } from './data-source'
import { User } from './entity/User'
AppDataSource.initialize()
.then(async () => {
const user1 = new User()
user1.name = 'jack'
user1.nickname = '张三'
user1.phone = '13311111111'
user1.age = 25
user1.desc = 'This is a desc1'
// 插入数据
await AppDataSource.manager.save(user1)
console.log('Saved a new user with id: ' + user1.id)
const user2 = new User()
user2.name = 'rose'
user2.nickname = '李四'
user2.phone = '13322222222'
user2.age = 26
user2.desc = 'This is a desc2'
await AppDataSource.manager.save(user2)
console.log('Saved a new user with id: ' + user2.id)
// 查询数据
const users = await AppDataSource.manager.find(User)
console.log('查询所有用户信息: ', users)
// 更新数据
user1.name = 'tom'
await AppDataSource.manager.save(user1)
// 查询数据
const user = await AppDataSource.manager.findOneBy(User, {
id: 2,
})
console.log('用户信息:', user)
})
.catch((error) => console.log(error))

使用Repository操作CRUD#

除了使用实体管理器(EntityManager)来管理实体外,我们还可以使用存储库(Repository)来管理每个实体,我们直接通过代码来看看

import { AppDataSource } from './data-source'
import { User } from './entity/User'
async function main() {
await AppDataSource.initialize()
const userRepository = AppDataSource.getRepository(User)
const user1 = new User()
user1.name = 'jack'
user1.nickname = '张三'
user1.phone = '13311111111'
user1.age = 25
user1.desc = 'This is a desc1'
// 新增 user1
await userRepository.save(user1)
console.log('新增成功...', user1)
const user2 = new User()
user2.name = 'rose'
user2.nickname = '李四'
user2.phone = '13322222222'
user2.age = 26
user2.desc = 'This is a desc2'
// 新增 user2
await userRepository.save(user2)
console.log('新增成功...', user2)
// 查询
const users = await userRepository.find()
console.log('查询所有用户信息:', users)
// 根据条件查询
const user = await userRepository.findOneBy({ id: 2 })
console.log('查询id=2的用户信息:', user)
// 更新
user.name = 'tom'
user.nickname = '王五'
await userRepository.save(user)
console.log('更新成功...', user)
// 删除
// const userDelete = await userRepository.findOneBy({ id: 2 });
// await userRepository.delete(userDelete);
// console.log("删除成功...");
}
main()

使用QueryBuilder操作CRUD#

QueryBuilder是TypeORM中最强大的功能之一,它提供了更加底层的数据库查询操作,灵活度高,支持组合更复杂的SQL查询语句,比如多条件查询、多表查询等等。

创建QueryBuilder的方式有很多,可以通过DataSource创建,也可以通过Repository创建,还能通过EntityManager创建

// DataSource创建
async function main() {
await AppDataSource.initialize()
const user = await AppDataSource.createQueryBuilder()
.select('user')
.from(User, 'user')
.where('user.id = :id', { id: 1 })
.getOne()
console.log('查询id=1的用户信息:', user)
}
main()
// Repository创建
const user = await AppDataSource.getRepository(User)
.createQueryBuilder('user')
.where('user.id = :id', { id: 1 })
.getOne()
console.log('查询id=1的用户信息:', user)
// EntityManager创建
const user = await AppDataSource.manager
.createQueryBuilder(User, 'user')
.where('user.id = :id', { id: 1 })
.getOne()
console.log('查询id=1的用户信息:', user)

使用哪种方式创建都可以。

比如我们现在要新增一条数据

async function main() {
await AppDataSource.initialize()
const user = new User()
user.name = 'lily'
user.nickname = '丽丽'
user.phone = '13333333333'
user.age = 18
user.desc = '......'
const queryBuilder = AppDataSource.createQueryBuilder()
const result = await queryBuilder.insert().into(User).values(user).execute()
console.log('插入数据:', result)
}
main()

上面代码使用insert和into方法进行插入操作,语法上接近原始的SQL语句,value方法中可以传入对象或者数组,如果要插入多条记录,就可以选择传入数组。最后调用execute方法执行语句

如果要执行查询:

const saveUsers = await queryBuilder.select('u').from(User, 'u').getMany()
console.log('查询所有用户信息:', saveUsers)

同样查询操作类似于**“select from”的SQL语句,其实uUser**实体的别名,getMany方法可以把多条记录查询出来。注意查询出来的记录和我们代码的实体对象一一对应。

可能有时候我们数据库的字段和实体字段并不一致,如果仅仅就只想查询数据库中的字段,可以使用getRawMany,比如我们有这样的实体:

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({
name: 'u_name',
length: 100,
})
name: string
@Column({
name: 'u_nickname',
length: 100,
})
nickname: string
@Column()
age: number
@Column({ length: 11 })
phone: string
@Column('text')
desc: string
@Column('double', {
default: 0,
})
other: number
}

查询的时候

const saveUsers = await queryBuilder.select().from(User, 'u').getRawMany()
console.log('查询所有用户信息:', saveUsers)

就能获取数据库原始的字段。

如果我们想查询单条信息并修改

const userUpdate = await queryBuilder
.select('u')
.from(User, 'u')
.where('u.id = :id', { id: 1 })
.getOne()
console.log('查询id=1的用户信息:', userUpdate)
userUpdate.name = 'lucy'
userUpdate.nickname = '露西'
userUpdate.phone = '13344444444'
userUpdate.age = 20
userUpdate.desc = '123456'
const resultUpdate = await queryBuilder
.update(User)
.set(userUpdate)
.where('id = :id', { id: 1 })
.execute()
console.log('更新数据:', resultUpdate)

最后是删除数据

const resultDelete = await queryBuilder
.delete()
.from(User)
.where('id = :id', { id: 1 })
.execute()
console.log('删除数据:', resultDelete)

三、TypeORM处理多表关系#

一对一关系#

以用户表和身份证表为例,用户表上面已经创建过了,接下来创建一个身份证实例

import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn,
} from 'typeorm'
import { User } from './User'
@Entity()
export class IdCard {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 18 })
cardNo: string
@Column()
name: string
@Column()
address: string
@Column()
birthday: Date
@Column()
email: string
@OneToOne(() => User)
@JoinColumn()
user: User
}

上面最关键的是,是 @OneToOne装饰器来建立与User实体之间的一对一关联关系。同时用 @JoinColumn()装饰器来定义user列,用来维护一个外键,TypeORM会自动帮我们生成外键id

**注意:**添加了实体,就需要将实体添加到DataSource配置选择entites中

export const AppDataSource = new DataSource({
......
entities: [User, IdCard],
});

当然,如果你配置的是./**/entity/*.ts这样的通配符路径,则可以忽略

接下来,我们在index.ts中分别为User和IdCard插入数据,关键点是要确保他们关联起来

import { AppDataSource } from './data-source'
import { IdCard } from './entity/IdCard'
import { User } from './entity/User'
async function main() {
await AppDataSource.initialize()
// 创建一个用户
const user = new User()
user.name = 'hmm'
user.nickname = '韩梅梅'
user.phone = '13355555555'
user.age = 18
user.desc = '......'
// 创建一个身份证信息
const idCard = new IdCard()
idCard.cardNo = '123456789012345678'
idCard.name = 'hmm'
idCard.address = '北京市朝阳区'
idCard.birthday = new Date('2000-01-01')
idCard.email = 'hmm2000@163.com'
// 关联两个实体
idCard.user = user
// 获取实体存储库
const userRepository = AppDataSource.getRepository(User)
const idCardRepository = AppDataSource.getRepository(IdCard)
// 先保存用户信息
await userRepository.save(user)
console.log('保存用户信息成功...', user)
// 再保存身份证信息
await idCardRepository.save(idCard)
console.log('保存身份证信息成功...', idCard)
}
main()

在数据库中,我们已经可以看到两张表的关联关系,比如查看id_card表的相关DDL信息,就能看到自动生成的建表信息

image-20250202200943113

如果在数据库中,我们要查询两者信息,可以使用SQL语句

select * from user join id_card on user.id = id_card.userid

image-20250202201103808

在代码中,我们可以使用Repository提供的find方法来查询。

但是两个实体的关系是单向的,也就是我们只在IdCard实体中持有User实体的外键列,这就使得IdCard有权访问User,但是反过来User其实并不知道IdCard的存在

在这种情况下应该使用idCardRepository来查询,并且设置关联(relations)

async function main() {
await AppDataSource.initialize()
// 获取idCard实体存储库
const idCardRepository = AppDataSource.getRepository(IdCard)
const idCards = await idCardRepository.find({
relations: { user: true },
})
console.log(idCards)
}
main()

双向关联#

所以,为了让User实体能够访问到IdCard实体,我们需要修改实体类建立双向关联

@Entity()
export class IdCard {
//...... 省略
@OneToOne(() => User, (user) => user.card)
@JoinColumn()
user: User
}
@Entity()
export class User {
// ...... 省略
@OneToOne(() => IdCard, (idCard) => idCard.user)
card: IdCard
}

此时,如果我们用userRepository同样也就可以加载关联表数据了

import { AppDataSource } from './data-source'
import { IdCard } from './entity/IdCard'
import { User } from './entity/User'
async function main() {
await AppDataSource.initialize()
// 获取idCard实体存储库
const idCardRepository = AppDataSource.getRepository(IdCard)
// 通过idCard实体查询关联表数据
const idCards = await idCardRepository.find({
relations: { user: true },
})
console.log('idCards---', idCards)
// 获取user实体存储库
const userRepository = AppDataSource.getRepository(User)
const users = await userRepository.find({
relations: { card: true },
})
console.log('users---', users)
}
main()

级联处理#

前面我们保存User和IdCard其实是分别用了两个存储库去处理,但是这两个有关联的数据,我们完全可以进行级联更新或者删除。只需要在实体类中加上级联处理就行:

@Entity('user')
export class User {
// ......省略
@OneToOne(() => IdCard, (idCard) => idCard.user, {
cascade: true,
})
card: IdCard
}

还是之前的代码,但是现在我们只需要使用User库就能直接进行保存

async function main() {
await AppDataSource.initialize()
// 创建一个用户
const user = new User()
user.name = 'lilei'
user.nickname = '李雷'
user.phone = '13366666666'
user.age = 18
user.desc = '......'
// 创建一个身份证信息
const idCard = new IdCard()
idCard.cardNo = '123456789012345678'
idCard.name = 'lilei'
idCard.address = '北京市朝阳区'
idCard.birthday = new Date('2000-02-02')
idCard.email = 'lilei2000@163.com'
// 注意这里需要通过user来关联idCard
user.card = idCard
// 获取实体存储库
const userRepository = AppDataSource.getRepository(User)
// 直接保存用户信息即可关联保存idCard信息
await userRepository.save(user)
console.log('保存用户信息成功...', user)
}
main()

那如果是更新呢?其实还是一样的。

async function main() {
await AppDataSource.initialize()
// 获取user实体存储库
const userRepository = AppDataSource.getRepository(User)
const loadUser = await userRepository.findOne({
relations: { card: true },
where: { id: 1 },
})
console.log('user:', loadUser)
loadUser.name = 'lucy'
loadUser.nickname = '露西'
loadUser.phone = '13377777777'
loadUser.age = 20
loadUser.card.name = 'lucy'
loadUser.card.cardNo = '999999999999999'
loadUser.card.address = '成都市武侯区'
loadUser.card.birthday = new Date('2000-03-03')
loadUser.card.email = 'lucy2000@163.com'
const result = await userRepository.save(loadUser)
console.log(result)
}
main()

一对多/多对一关系#

一对多/多对一关系是我们最常见的关系,班级和学生,部门和员工,可以拿这些简单的内容来举例。比如部门可员工(一个部门可以有多个员工,一个员工只属于一个部门)

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'
import { Employee } from './Employee'
@Entity()
export class Department {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 100 })
name: string
@Column()
desc: string
@OneToMany(() => Employee, (employee) => employee.department, {
cascade: true,
})
employees: Employee[]
}

使用@OneToMany装饰器来定义与员工表的关系,并设置级联(cascade)选项用来自动更新相关联的Employee记录。由于是多,所以employee肯定是一个数组。

不过@OneToMany不能单独使用,它必须与@ManyToOne装饰器配对使用,以确保关系的双向性。

import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
} from 'typeorm'
import { Department } from './Department'
@Entity()
export class Employee {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 100 })
name: string
@ManyToOne(() => Department, (department) => department.employees)
@JoinColumn()
department: Department
}

上面使用**@JoinColumn装饰器定义了外键列,在一对多/多对一关系中,外键列肯定会设置在”多”的一方**

实体定义完成之后,在index.ts中插入数据:

async function main() {
await AppDataSource.initialize()
const depart = new Department()
depart.name = '技术部'
depart.desc = '技术部门'
const emp1 = new Employee()
emp1.name = '员工1'
const emp2 = new Employee()
emp2.name = '员工2'
// 设置部门和员工的关联关系
depart.employees = [emp1, emp2]
const departmentRepository = AppDataSource.getRepository(Department)
const result = await departmentRepository.save(depart)
console.log(result)
}
main()

多对多关系#

这里我们使用订单和商品的关系,一个订单可以包含多件商品,一件商品也可以出现在多个订单中。首先,当然还是需要先定义实体。

import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm'
import { Product } from './Product'
@Entity()
export class Order {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 100 })
name: string
@ManyToMany(() => Product, (product) => product.orders, {
cascade: true,
})
products: Product[]
}
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from 'typeorm'
import { Order } from './Order'
@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 100 })
name: string
@ManyToMany(() => Order, (order) => order.products)
@JoinTable()
orders: Order[]
}

这里使用@JoinTable()来生成中间表,TypeORM会创建一个中间表来管理多对多关系。

在index.ts中添加数据来看看效果

async function main() {
await AppDataSource.initialize()
const order1 = new Order()
order1.name = '订单1'
const order2 = new Order()
order2.name = '订单2'
const product1 = new Product()
product1.name = '产品1'
const product2 = new Product()
product2.name = '产品2'
const product3 = new Product()
product3.name = '产品3'
const product4 = new Product()
product4.name = '产品4'
order1.products = [product1, product2, product3]
order2.products = [product1, product2, product3, product4]
const orderRepository = AppDataSource.getRepository(Order)
await orderRepository.save(order1)
await orderRepository.save(order2)
}
main()

上面的代码会自动帮我们生成一张中间表product_orders_order,而且生成了productId和orderId作为复合主键,并插入数据:

image-20250203113105685

image-20250203113301771

除此之外,我们还可以自行定义中间表的名称和列名

@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 100 })
name: string
@ManyToMany(() => Order, (order) => order.products)
@JoinTable({
name: 'order_products',
joinColumn: {
name: 'order_id',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'product_id',
referencedColumnName: 'id',
},
})
orders: Order[]
}

image-20250203182806956

四、在Nest中使用TypeORM#

掌握了TypeORM的基本用法之后,在Nest中集成TypeORM就特别简单了。

我们先重新创建一个nest工程

Terminal window
nest n nest-typeorm-mysql -p -pnpm

为了方便,我们直接创建一个User模块

Terminal window
nest g res user

然后导入我们需要的TypeORM相关的包

Terminal window
pnpm add typeorm mysql2 @nestjs/typeorm -s

TypeORM提供了初始化数据库连接的方式,在app.module.ts文件中引入

@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
username: 'root',
port: 3306,
password: '123456',
database: 'nest_typeorm',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
timezone: 'Z',
}),
UserModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

**注意:**这里是为了方便,打开了synchronize: true,会自动加载或更新数据库结构匹配实体定义。但是在生产环境中,我们肯定要禁用同步,不然随便就改变了数据库结构这是非常不安全的。

由于docker中mysql数据库我们并没有处理时区问题,这样存储进去的时间类型,反馈给前端的时候就会出现东8区的时间差问题。

上面的代码通过forRoot方法在根模块(AppModule)中注册了TypeORM模块。这样可以使得TypeORM服务在应用程序的各个模块中都可以共享使用,不过需要注意,这里需要node版本在20以上

完善一下User实体,新增一些字段

import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@Column()
sex: string
@Column({
type: 'timestamp',
transformer: {
to: (value: string | Date): Date => {
if (typeof value === 'string') {
return new Date(`${value}T00:00:00.000Z`)
}
return value
},
from: (value: Date): Date => {
return value
},
},
})
birthday: Date
}

使用命令pnpm run start:dev启动服务,就能帮我们创建User表(注意:数据库需要自己手动创建)

image-20250203185212181

EntityManager操作实体#

我们可以通过nest直接注入EntityManager来操作实体,这里唯一需要注意的一点是,TypeORM是天生的Repository,所以,我们可以直接在Service层中注入EntityManager

user.service.ts

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

user.controller.ts

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)
}
}

Repository操作实体#

除了使用EntityManager操作实体之外,同样可以使用Repository操作实体,其实在功能和使用上和EntityManager几乎一致,唯一的区别是,使用Repository不需要每次操作都传入实体对象

使用EntityManager的小问题:

image-20250203194409778

我们注入Repository,替换一下就行:

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

但是要注意的是:由于Nest的依赖注入作用域限定在模块级别,因此userRepository存储库还需要在User模块中引入并注册存储库,所以,在user.module.ts文件中,还需要添加如下的代码:

@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}

QueryBuilder操作实体#

当然,如果我们有比较复杂的SQL语句,还能使用QueryBuilder对实体进行操作。比如有下面的查询,需要根据姓名和性别进行查询,如果使用Repository我们可能会写成下面这个样子

async create(createUserDto: CreateUserDto) {
const user = await this.userRepository.save(createUserDto);
return user;
}
async findAll(name: string, sex: string) {
return await this.userRepository.find({
where: {
name,
sex,
},
});
}

为了查询方便,controller中稍微做一下修改:

@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
@Get()
findAll(@Query('name') name: string, @Query('sex') sex: string) {
return this.userService.findAll(name, sex);
}

如果我们已经有下面的数据

image-20250204124419712

经过上面的查询,实际上where中的连接条件是and

查询之后的结果如下:

image-20250204124534146

如果我们希望使用or进行连接,就可以使用QueryBuilder

async create(createUserDto: CreateUserDto) {
// const user = await this.userRepository.save(createUserDto);
const user = await this.userRepository
.createQueryBuilder()
.insert()
.into(User)
.values(createUserDto)
.execute();
return user;
}
async findAll(name: string, sex: string) {
// return await this.userRepository.find({
// where: {
// name,
// sex,
// },
// });
return this.userRepository
.createQueryBuilder('u')
.where('u.name = :name or u.sex = :sex', { name, sex })
.getMany();
}

查询出来的结果:

image-20250204125206831