TypeORM 怎么用我们已经学会了,在 Nest 里用那不就是再封装一层的事情么?
那怎么封装呢?
先回忆下 TypeORM 的流程:
DataSource 里存放着数据库连接的配置,比如用户名、密码、驱动包、连接池配置等等。
而 Entity 里通过 @Entity、@PrimaryGeneratedColumn、@Column 等装饰器来建立数据库表的映射关系。
同时还有 Entity 之间的 @OneToOne、@OneToMany、@ManyToMany 的关系,这些会映射成数据库表通过外键、中间表来建立的关系。
DataSource.initialize 的时候,会和数据库服务建立连接,如果配置了 synchronize,还会生成建表 sql 语句来创建表。
DataSource 初始化之后就可以拿到 EntityManager 了,由它负责对各种 Entity 进行增删改查,比如 find、delete、save 等方法,还可以通过 query builder 来创建复杂的查询。
如果你只是想做对单个 Entity 的 CRUD,那可以拿到这个 Entity 的 Repository 类,它同样有上面的那些方法,只是只能用来操作单个 Entity。
这就是 TypeORM 的流程。
那如果让你把 TypeORM 的 api 封装一层,做成一个 TypeOrmModule,你会怎么封装呢?
很明显,这里的 datasource 的配置是需要手动传入的,也就是说需要做成动态模块,支持根据传入的配置来动态产生模块内容。
而动态模块的规范里就 3 种方法名: register、forRoot、forFeature。
这里很明显要用 forRoot,也就是只需要注册一次,然后这个模块会在各处被使用。
类似这样:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from 'xxx';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [],
synchronize: true,
}),
],
})
export class AppModule {}
然后内部就根据传入的配置来创建 DataSource,调用 intialize 方法,之后就拿到 EntityManager,可以做 CRUD 了。
但是 Entity 肯定会分散在各个业务模块,每个模块都通过 forRoot 引入那个模块太麻烦,我们干脆把它用 @Global 声明成全局的。
这样每个模块里就都可以注入 EntityManager 来用了,不需要 imports。
那如果我想用 Repository 的方式来 CRUD 呢?
那可以先注入 EntityManager,然后再通过 EntityManager.getRepository(XxxEntity) 来拿呀。
或者可以再做一个动态模块,传入 Entity,返回它的 Repository。
这种局部的动态模块,一般都是用 forFeature 的名字:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from 'xxxx';
import { UsersService } from './users.service';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
controllers: [],
})
export class UsersModule {}
比如传入 User,内部通过 EntityManager.getRepository(User) 来拿到 UserEntity。
这样 UserService 里就可以通过 UserRepository 来实现增删改查了。
这个封装思路貌似挺完美。
那我们来看看 @nestjs/typeorm 是怎么封装的吧。
创建个 Nest 项目:
nest new nest-typeorm -p npm
然后创建一个 crud 的模块:
nest g resource user
生成的 service 里的 crud 并没有真正实现:
我们引入 typeorm 来实现下:
npm install --save @nestjs/typeorm typeorm mysql2
typeorm、mysql2 的包我们很熟悉了,而 @nestjs/typeorm 就是把 typeorm api 封装了一层的包。
它提供了一个模块,我们在入口引入下:
连接配置和前几节一样,引入 User 的 Entity。
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { User } from './user/entities/user.entity';
import { UserModule } from './user/user.module';
@Module({
imports: [UserModule,
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "guang",
database: "typeorm_test",
synchronize: true,
logging: true,
entities: [User],
poolSize: 10,
connectorPackage: 'mysql2',
extra: {
authPlugin: 'sha256_password',
}
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
然后在 User 的 Entity 里加一些映射的信息:
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity({
name: 'aaa_user'
})
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({
name: 'aaa_name',
length: 50
})
name: string;
}
给映射的表给个名字叫 aaa_user,然后有两个字段,分别是 id 和 name。
我们跑一下试试:
nest start --watch
看到建表 sql 了没:
这部分和我们单独跑 typeorm 没啥区别:
然后是增删改查,我们可以注入 EntityManager:
用它来做增删改查:
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);
}
}
这里的 save、findOne、delete 方法我们都用过。
然后我们用 postman 来试一下:
发个 post 请求,带上要添加的数据:
服务端打印了 insert 的 sql 语句:
表里也可以看到这条数据了:
对应的是这个 handler:
然后再试下查询:
单个查询和全部查询都是可以的。
再就是修改:
在 controller 里是接受 patch 的请求。
在 postman 里发一下:
可以看到生成了 update 的 sql 语句:
数据库中的数据也被修改了:
再试试删除:
在 postman 里发送 delete 的请求:
可以看到生成了 delete 的 sql 语句:
数据库里的数据确实被删除了:
至此,我们就正式打通了从请求到数据库的整个流程!
这里的 CRUD 部分用到的 api 我们都用过好多遍了。
只不过现在是通过 TypeOrm.forRoot 来传入的数据源的配置,通过 @InjectEntityManager 来注入的 entityManager 对象。
直接用 EntityManager 的缺点是每个 api 都要带上对应的 Entity:
简便方法就是先 getRepository(User) 拿到 user 对应的 Repository 对象,再调用这些方法。
比如这样:
那还不如直接注入 User 对应的 Respository 就好了。
Nest 对这个做了封装,在 user 模块引入 TypeOrmModule.forFeature 对应的动态模块,传入 User 的 Entity:
就可以在模块里注入 Repository 了:
它有的方法和 EntityManager 一样,只是只能用来操作当前 Entity。
此外,你还可以注入 DataSource:
不过这个不常用。
这就是 Nest 里集成 TypeOrm 的方式。
有了 TypeOrm 的使用基础之后,学起来还是非常简单的。
那它是怎么实现的呢?
我们来看下源码:
首先,我们通过引入 TypeOrmModule.forRoot 的动态模块的时候:
它会引入 TypeOrmCoreModule.forRoot 的动态模块:
这里面根据 options 创建 DataSource 和 EntityManager 放到模块的 provider 里,并放到了 exports 里。
而且,更重要的是这个模块是 @Global 的全局模块。
因此,dataSource 和 entityManager 就可以在任意的地方注入了。
上面那两个方法里,创建 DataSource 的过程就是传入参数,调用 intialize 方法:
而创建 entityManager,则是注入 dataSource 取 manager 属性就好了:
然后 TypeOrmModule.forFeature 则是通过全局的 dataSource.getRepository 拿到参数对应的 Repository 对象,作为模块内的 provider。
这样引入这个动态模块的模块内就可以注入这些 Entity 对应的 Repository 了。
这就是 @nestjs/typeorm 的 TypeOrmModule.forRoot 和 TypeOrmModule.forFeature 的实现原理。
案例代码在小册仓库。
总结
我们会了用 TypeOrm 来连接和增删改查数据库表,在 Nest 里集成只是对 TyprOrm 的 api 封装了一层。
使用方式是在根模块 TypeOrmModule.forRoot 传入数据源配置。
然后就可以在各处注入 DataSource、EntityManager 来做增删改查了。
如果想用 Repository 来简化操作,还可以在用到的模块引入 TypeOrmModule.forFeature 的动态模块,传入 Entity,会返回对应的 Repository。
这样就可以在模块内注入该 Repository 来用了。
它的原理是 TypeOrmModule.forRoot 对应的动态模块是全局的,导出了 dataSource、entityManager,所以才可以到处注入。
而 TypeOrmModule.forFeature 则会根据吧传入 Entity 对应的 Repository 导出,这样就可以在模块内注入了。
这就是 Nest 里集成 TypeOrm 的方式和实现原理。
至此,我们就可以打通从请求到数据库的流程了。