TypeORM通过对实体模型类和查询构建器的封装,提供了一套强大的ORM API,可以处理关系数据库中的数据和事务。TypeORM的实现原理主要包括以下几个方面:
- 实体模型类
TypeORM的实体模型类是一个普通的JavaScript类,它用于定义数据结构和映射到数据库中的数据。实体模型类使用装饰器来定义实体的属性、关系和元数据。TypeORM将实体模型类的属性映射到数据库中的表列,将实体模型类的关系映射到数据库中的关系。
TypeORM的实体模型类还支持继承、嵌套和多态等特性。可以通过实体模型类来定义单个表、联接表、关系等。
- 查询构建器
TypeORM的查询构建器是一个链式调用的API,用于构建和执行自定义查询。查询构建器提供了各种方法,如select()、where()、join()、orderBy()等,用于构建查询。查询构建器还支持分页和限制等操作。
查询构建器提供的方法都是类型安全的,并使用TypeScript来提供完整的类型支持。
- 数据库驱动程序
TypeORM的数据库驱动程序负责管理连接池、生成SQL语句和与数据库交互。TypeORM支持多种数据库,如PostgreSQL、MySQL、SQLite、Oracle等。每种数据库都有自己的驱动程序,用于生成特定的SQL语句和处理特定的数据类型。
- 迁移
TypeORM支持数据迁移,可以通过迁移文件来管理数据库中的表结构和数据。TypeORM提供了一组命令行工具来管理迁移文件,可以方便地创建、运行和回滚迁移文件。迁移工具会读取迁移文件中的操作,并根据这些操作来生成SQL语句并执行。
- 实体关系映射
TypeORM支持实体之间的关系映射和级联操作。可以使用装饰器来定义实体之间的关系,如一对一、一对多、多对多等关系。TypeORM会自动将关系映射到数据库中的表和外键,从而简化了数据访问的逻辑。
总之,TypeORM的实现原理主要是基于实体模型类、查询构建器和数据库驱动程序。通过这些概念的封装和组合,TypeORM提供了一套强大的ORM API,可以方便地处理关系数据库中的数据和事务。
下面我来展示一下TypeORM的源码,以Repository类为例:
export class Repository<Entity extends ObjectLiteral> extends Subject<Entity[]> {
// ...
createQueryBuilder(alias?: string, queryRunner?: QueryRunner): SelectQueryBuilder<Entity> {
const qb = this.connection.createQueryBuilder<Entity>(this.metadata.target, alias, queryRunner);
qb.setLock(this.queryRunnerProvider);
return qb;
}
createQueryBuilder<T>(alias?: string, queryRunner?: QueryRunner): SelectQueryBuilder<T> {
const qb = this.connection.createQueryBuilder<T>(this.metadata.target, alias, queryRunner);
qb.setLock(this.queryRunnerProvider);
return qb;
}
// ...
async findOne(id: string|string[]|number|number[]|Date|Date[]|ObjectID|ObjectID[]|UUID|UUID[], options?: FindOneOptions<Entity>): Promise<Entity|undefined> {
const metadata = this.metadata;
const alias = metadata.targetName;
const condition = this.createWhereExpression({ [metadata.primaryColumn.propertyName]: id });
const qb = this.createQueryBuilder(alias);
this.applyOptionsToQueryBuilder(qb, options);
return qb
.where(condition)
.getOne();
}
async find(options?: FindManyOptions<Entity>): Promise<Entity[]> {
const alias = this.metadata.targetName;
const qb = this.createQueryBuilder(alias);
this.applyOptionsToQueryBuilder(qb, options);
return qb.getMany();
}
// ...
async save(entities: Entity[], options?: SaveOptions): Promise<Entity[]>;
async save(entity: Entity, options?: SaveOptions): Promise<Entity>;
async save(entityOrEntities: Entity|Entity[], options?: SaveOptions): Promise<Entity|Entity[]> {
if (entityOrEntities instanceof Array) {
return this.manager.save(this.metadata.target, entityOrEntities, options);
} else {
return this.manager.save(this.metadata.target, entityOrEntities, options);
}
}
// ...
}
在上面的代码中,Repository类定义了各种实现CRUD操作的方法,如findOne()、find()、save()等。这些方法都基于TypeORM的查询构建器和数据库驱动程序实现,可以执行与数据库交互相关的操作。
Repository类的createQueryBuilder()方法是一个非常重要的方法,用于创建查询构建器。它使用实体模型类、别名和查询运行器来创建一个SelectQueryBuilder实例,可以用于构建和执行自定义查询。
另外,Repository类还提供了一些钩子方法,用于在执行CRUD操作的不同阶段执行自定义操作。例如,可以使用beforeInsert、beforeUpdate和beforeRemove等方法,在插入、更新和删除操作之前执行自定义逻辑。
总之,TypeORM的源码非常复杂,但非常值得一看。通过阅读TypeORM的源码,我们可以深入了解ORM的实现原理和设计思路,从而更好地使用它来处理关系数据库中的数据和事务。
- 实体模型类
TypeORM的实体模型类是一个普通的JavaScript类,它用于定义数据结构和映射到数据库中的数据。实体模型类使用装饰器来定义实体的属性、关系和元数据。例如:
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Post, post => post.author)
posts: Post[];
}
在上面的代码中,User类使用@Entity()装饰器将其定义为实体模型类。@PrimaryGeneratedColumn()和@Column()装饰器定义了实体的属性和类型。@OneToMany()装饰器定义了实体之间的关系。
2. 查询构建器
TypeORM的查询构建器是一个链式调用的API,用于构建和执行自定义查询。查询构建器提供了各种方法,如select()、where()、join()、orderBy()等,用于构建查询。查询构建器还支持分页和限制等操作。例如:
const users = await connection
.getRepository(User)
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('user.id = :id', { id: 1 })
.getMany();
- 数据库驱动程序
TypeORM的数据库驱动程序负责管理连接池、生成SQL语句和与数据库交互。TypeORM支持多种数据库,如PostgreSQL、MySQL、SQLite、Oracle等。每种数据库都有自己的驱动程序,用于生成特定的SQL语句和处理特定的数据类型。例如:
const connection = await createConnection({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'password',
database: 'test',
entities: [User, Post],
});
在上面的代码中,我们使用createConnection()方法创建一个TypeORM连接。我们需要指定数据库类型、连接信息、实体模型类等参数。TypeORM会根据这些参数选择适当的数据库驱动程序,并执行相应的操作。
- 迁移
TypeORM支持数据迁移,可以通过迁移文件来管理数据库中的表结构和数据。TypeORM提供了一组命令行工具来管理迁移文件,可以方便地创建、运行和回滚迁移文件。迁移工具会读取迁移文件中的操作,并根据这些操作来生成SQL语句并执行。例如:
在上面的代码中,我们使用typeorm-cli工具创建了一个名为create-user-table的迁移文件。在迁移文件中,我们定义了一个CreateUserTable类,并实现了up()和down()方法。up()方法会创建一个名为user的表,down()方法会删除这个表。当我们运行迁移工具时,TypeORM会根据迁移文件生成SQL语句,并执行相应的操作。
- 实体关系映射
TypeORM支持实体之间的关系映射和级联操作。可以使用装饰器来定义实体之间的关系,如一对一、一对多、多对多等关系。TypeORM会自动将关系映射到数据库中的表和外键,从而简化了数据访问的逻辑。例如:
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Post, post => post.author)
posts: Post[];
}
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne(() => User, user => user.posts)
author: User;
}
在上面的代码中,User和Post实体之间定义了一对多的关系。我们使用@OneToMany()和@ManyToOne()装饰器来定义这个关系,TypeORM会自动将关系映射到数据库中的表和外键。
Connection类是TypeORM的核心之一,它负责管理数据库连接、事务和查询,并提供了各种方法来处理数据库的操作。我们在使用TypeORM时,需要深入了解Connection类的API和实现原理,以便更好地处理关系数据库中的数据和事务。
createConnection()是TypeORM的一个全局函数,用于创建一个新的Connection实例,并连接到数据库。createConnection()函数接收一个TypeORM连接配置对象,用于指定连接的数据库和其他配置参数。
以下是TypeORM中createConnection()方法的简化版源码:
export async function createConnection(options: ConnectionOptions): Promise<Connection> {
const connectionOptions = new ConnectionOptionsReader(options).get('default');
const connection = new Connection(connectionOptions);
await connection.connect();
return connection;
}
在上面的代码中,我们定义了一个全局函数createConnection(),用于创建一个新的TypeORM连接。createConnection()方法接收一个连接配置对象,并根据该配置对象创建一个新的Connection实例。
在createConnection()方法中,我们首先使用ConnectionOptionsReader类来解析连接配置参数,从而获取到具体的连接参数。然后,我们使用这些参数来创建一个新的Connection实例,并调用connect()方法连接到数据库。
最后,我们将新创建的Connection实例返回给调用者。
总之,createConnection()方法是TypeORM中用于创建新连接的全局函数,它使用ConnectionOptionsReader类解析连接参数,创建一个新的Connection实例,并连接到数据库。如果我们需要深入理解TypeORM的实现原理,可以查看ConnectionOptionsReader和Connection类的源码。
以下是TypeORM中的Connection类的简化版源码:
export class Connection extends BaseConnection {
async connect(): Promise<void> {
const driver = this.driver;
await driver.connect();
}
async close(): Promise<void> {
const driver = this.driver;
await driver.disconnect();
}
getRepository<Entity>(entityClass: ObjectType<Entity>): Repository<Entity> {
const metadata = this.getMetadata(entityClass.name);
return new Repository<Entity>(this, metadata);
}
}
在上面的代码中,我们定义了一个Connection类,继承了BaseConnection类,并提供了连接、关闭和获取Repository实例的方法。
在Connection类的connect()方法中,我们获取数据库驱动程序,然后调用其connect()方法来连接到数据库。在Connection类的close()方法中,我们也是通过获取数据库驱动程序,然后调用其disconnect()方法来关闭连接。
在Connection类的getRepository()方法中,我们首先根据实体模型类的名称获取元数据信息,然后根据Connection实例和元数据信息创建一个Repository实例。Repository实例是TypeORM的一个关键类,用于封装常见的CRUD操作。
Connection类是TypeORM中的一个核心类,负责管理数据库连接、事务和查询。Connection类通过连接、关闭和获取Repository实例等方法,提供了处理数据库操作的基本API。如果我们需要深入理解TypeORM的实现原理,可以查看BaseConnection和具体的驱动程序实现类的源码。
下面我们用TypeORM + Nest.js 抽象出一个CRUD 框架
- 抽象一个简单的Entity模型基类
在nest.js中,实体模型通常用于定义数据库中的数据结构。您可以创建一个基础实体模型类,以避免编写重复的代码。此外,使用TypeORM和Nest.js提供的装饰器,可以轻松地定义实体和它们之间的关系。下面是一个示例:
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Base extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
- 抽象Repository以及TreeRepository基类
Repository是用于在数据库中执行操作的类。您可以创建一个Repository基类,用于抽象常见的CRUD操作。如果您的实体模型使用了树状结构,可以使用TreeRepository基类。下面是一个示例:
import { EntityRepository, Repository, TreeRepository } from 'typeorm';
import { Base } from './base.entity';
@EntityRepository(Base)
export class BaseRepository extends Repository<Base> {}
@EntityRepository(Base)
export class BaseTreeRepository extends TreeRepository<Base> {}
- 抽象订阅者基类
在Nest.js中,订阅者通常用于处理异步任务。如果您的应用程序需要处理消息队列或WebSocket等异步操作,您可以创建一个订阅者基类,以避免编写重复的代码。下面是一个示例:
import { OnModuleInit } from '@nestjs/common';
export abstract class BaseSubscriber implements OnModuleInit {
abstract async onModuleInit(): Promise<void>;
}
- 抽象一个基础的CRUD服务类
服务类用于实现应用程序的业务逻辑。您可以创建一个基础的CRUD服务类,以避免编写重复的代码。下面是一个示例:
import { Injectable } from '@nestjs/common';
import { DeepPartial } from 'typeorm';
import { BaseRepository } from './base.repository';
@Injectable()
export class BaseService<T> {
constructor(private readonly repository: BaseRepository<T>) {}
async findAll(): Promise<T[]> {
return this.repository.find();
}
async findById(id: number): Promise<T> {
return this.repository.findOne(id);
}
async create(data: DeepPartial<T>): Promise<T> {
const entity = this.repository.create(data);
return this.repository.save(entity);
}
async update(id: number, data: DeepPartial<T>): Promise<T> {
const entity = await this.findById(id);
this.repository.merge(entity, data);
return this.repository.save(entity);
}
async delete(id: number): Promise<void> {
await this.repository.delete(id);
}
}
- 构建一个CRUD装饰器以实现控制器基类
最后,您可以创建一个CRUD装饰器,以简化控制器的编写。下面是一个示例:
import { applyDecorators, Controller, Post, Body, Param, Get, Patch, Delete } from '@nestjs/common';
import { DeepPartial } from 'typeorm';
import { BaseService } from './base.service';
export function CrudController<T>(basePath: string, service: BaseService<T>) {
@Controller(basePath)
class CrudController {
@Post()
async create(@Body() data: DeepPartial<T>): Promise<T> {
return service.create(data);
}
@Get()
async findAll(): Promise<T[]> {
return service.findAll();
}
@Get(':id')
async findById(@Param('id') id: number): Promise<T> {
return service.findById(id);
}
@Patch(':id')
async update(@Param('id') id: number, @Body() data: DeepPartial<T>): Promise<T> {
return service.update(id, data);
}
@Delete(':id')
async delete(@Param('id') id: number): Promise<void> {
return service.delete(id);
}
}
return applyDecorators(Controller(basePath), Post(), Get(), Patch(), Delete());
}
最后,在您的实体模型中,您可以扩展Base实体模型类,定义自己的实体模型类,并使用它们来创建基于TypeORM和Nest.js的CRUD应用程序。
import { Entity } from 'typeorm';
import { Base } from './base.entity';
@Entity()
export class User extends Base {}
假设您正在开发一个博客应用程序,其中需要对文章进行CRUD操作。首先,您需要创建一个文章实体模型类,并在其中定义文章的属性和关系。
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { User } from './user.entity';
@Entity()
export class Article {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
content: string;
@ManyToOne(() => User, (user) => user.articles)
author: User;
}
然后,我们可以创建一个文章Repository类,并继承BaseRepository类。
import { EntityRepository } from 'typeorm';
import { BaseRepository } from './base.repository';
import { Article } from './article.entity';
@EntityRepository(Article)
export class ArticleRepository extends BaseRepository<Article> {}
接下来,我们可以创建一个文章服务类,并继承BaseService类。
import { Injectable } from '@nestjs/common';
import { DeepPartial } from 'typeorm';
import { Article } from './article.entity';
import { ArticleRepository } from './article.repository';
import { BaseService } from './base.service';
@Injectable()
export class ArticleService extends BaseService<Article> {
constructor(private readonly repository: ArticleRepository) {
super(repository);
}
}
最后,我们可以使用CrudController装饰器创建一个文章控制器类,并在其中实现CRUD操作。
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
import { DeepPartial } from 'typeorm';
import { Article } from './article.entity';
import { ArticleService } from './article.service';
import { CrudController } from './crud.controller';
const basePath = 'articles';
@Controller(basePath)
export class ArticleController {
constructor(private readonly service: ArticleService) {}
@Post()
async create(@Body() data: DeepPartial<Article>): Promise<Article> {
return this.service.create(data);
}
@Get()
async findAll(): Promise<Article[]> {
return this.service.findAll();
}
@Get(':id')
async findById(@Param('id') id: number): Promise<Article> {
return this.service.findById(id);
}
@Patch(':id')
async update(@Param('id') id: number, @Body() data: DeepPartial<Article>): Promise<Article> {
return this.service.update(id, data);
}
@Delete(':id')
async delete(@Param('id') id: number): Promise<void> {
return this.service.delete(id);
}
}
export const ArticlesController = CrudController(basePath, ArticleController);
专业,我们就可以使用ArticlesController控制器类来处理文章的CRUD操作,以及其他与文章相关的操作。这使得代码更具可读性和可维护性,并减少了冗余和重复的代码。