TypeORM基本概念及使用其抽象一个CRUD框架

881 阅读12分钟

TypeORM通过对实体模型类和查询构建器的封装,提供了一套强大的ORM API,可以处理关系数据库中的数据和事务。TypeORM的实现原理主要包括以下几个方面:

  1. 实体模型类

TypeORM的实体模型类是一个普通的JavaScript类,它用于定义数据结构和映射到数据库中的数据。实体模型类使用装饰器来定义实体的属性、关系和元数据。TypeORM将实体模型类的属性映射到数据库中的表列,将实体模型类的关系映射到数据库中的关系。

TypeORM的实体模型类还支持继承、嵌套和多态等特性。可以通过实体模型类来定义单个表、联接表、关系等。

  1. 查询构建器

TypeORM的查询构建器是一个链式调用的API,用于构建和执行自定义查询。查询构建器提供了各种方法,如select()where()join()orderBy()等,用于构建查询。查询构建器还支持分页和限制等操作。

查询构建器提供的方法都是类型安全的,并使用TypeScript来提供完整的类型支持。

  1. 数据库驱动程序

TypeORM的数据库驱动程序负责管理连接池、生成SQL语句和与数据库交互。TypeORM支持多种数据库,如PostgreSQL、MySQL、SQLite、Oracle等。每种数据库都有自己的驱动程序,用于生成特定的SQL语句和处理特定的数据类型。

  1. 迁移

TypeORM支持数据迁移,可以通过迁移文件来管理数据库中的表结构和数据。TypeORM提供了一组命令行工具来管理迁移文件,可以方便地创建、运行和回滚迁移文件。迁移工具会读取迁移文件中的操作,并根据这些操作来生成SQL语句并执行。

  1. 实体关系映射

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操作的不同阶段执行自定义操作。例如,可以使用beforeInsertbeforeUpdatebeforeRemove等方法,在插入、更新和删除操作之前执行自定义逻辑。

总之,TypeORM的源码非常复杂,但非常值得一看。通过阅读TypeORM的源码,我们可以深入了解ORM的实现原理和设计思路,从而更好地使用它来处理关系数据库中的数据和事务。

  1. 实体模型类

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();

  1. 数据库驱动程序

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会根据这些参数选择适当的数据库驱动程序,并执行相应的操作。

  1. 迁移

TypeORM支持数据迁移,可以通过迁移文件来管理数据库中的表结构和数据。TypeORM提供了一组命令行工具来管理迁移文件,可以方便地创建、运行和回滚迁移文件。迁移工具会读取迁移文件中的操作,并根据这些操作来生成SQL语句并执行。例如: 在上面的代码中,我们使用typeorm-cli工具创建了一个名为create-user-table的迁移文件。在迁移文件中,我们定义了一个CreateUserTable类,并实现了up()down()方法。up()方法会创建一个名为user的表,down()方法会删除这个表。当我们运行迁移工具时,TypeORM会根据迁移文件生成SQL语句,并执行相应的操作。

  1. 实体关系映射

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

在上面的代码中,UserPost实体之间定义了一对多的关系。我们使用@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的实现原理,可以查看ConnectionOptionsReaderConnection类的源码。

以下是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 框架

  1. 抽象一个简单的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;
}

  1. 抽象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> {}

  1. 抽象订阅者基类

在Nest.js中,订阅者通常用于处理异步任务。如果您的应用程序需要处理消息队列或WebSocket等异步操作,您可以创建一个订阅者基类,以避免编写重复的代码。下面是一个示例:

import { OnModuleInit } from '@nestjs/common';

export abstract class BaseSubscriber implements OnModuleInit {
  abstract async onModuleInit(): Promise<void>;
}

  1. 抽象一个基础的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);
  }
}

  1. 构建一个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操作,以及其他与文章相关的操作。这使得代码更具可读性和可维护性,并减少了冗余和重复的代码。