likes
comments
collection
share

TypeORM框架中常用的装饰器

作者站长头像
站长
· 阅读数 17

装饰器应用在实体中,而实体是一个转换为数据库表的类

点击这里,查看实体详情

实体装饰器

// 指定实体的名字,也是数据库中表的名字
@Entity("users")
export class User { }

除了给实体设置名字,还能对实体进行配置,让该实体拥有更多得到功能

配置选项:

  • name:指定实体对象在数据库中对应的表名。
  • engine:指定实体对象在数据库中对应的表的存储引擎。
  • database:指定实体对象在数据库中对应的表所在的数据库名称。
  • schema:指定实体对象在数据库中对应的表所在的模式名称。
  • synchronize:指定是否自动同步实体对象和数据库表结构,若为 true 则会自动同步,否则需要手动同步(数据库表结构更新中跳过标有false的实体)
  • orderBy:指定查询该实体对象时返回结果的排序方式,其属性值为一个对象,包含要排序的属性名和排序方式,如例中的 { name: "ASC", id: "DESC" } 表示按照 name 属性升序排列,再按照 id 属性降序排列。
@Entity({
    name: "users",
    engine: "MyISAM",
    database: 'example_dev',
    schema: 'schema_with_best_tables',
    synchronize: false,
    orderBy: {
        name: "ASC",
        id: "DESC"
    }
})
export class User { }

代分析码:

  • 定义了一个名为 User 的实体对象,该实体对象对应数据库中的 users 表,表存储引擎为 MyISAM,所在的数据库名称为 example_dev,所在的模式名称为 schema_with_best_tables,不会自动同步数据库表结构,查询该实体对象时返回结果的排序方式为先按照 name 属性升序排列,再按照 id 属性降序排列。

列装饰器

    • @Column - 指定 定义属性对应数据库表中的列

常用配置选项:

  • type: ColumnType 表示该属性对应数据库表中的列类型。
  • length: number 表示该属性对应数据库表中的列长度。
  • unique: boolean true 表示该属性对应数据库表中的列为唯一索引。
  • nullable: booleantrue 表示该属性对应数据库表中的列允许为空。
  • default: boolean 表示该属性对应数据库表中的列,默认值为 false
  • width: number 表示列类型的显示宽度。
  • update: boolean 表示save操作是否更新列值。如果为false,则只能在第一次插入对象时编写该值, 默认值为true
  • primary: boolean 表示将列标记为主列。 与@PrimaryColumn使用相同
  • comment: string 表示列的注释。
@Entity("users")
export class User {
    @Column({ primary: true })
    id: number;

    @Column({ type: "varchar", length: 200, unique: true })
    firstName: string;

    @Column({ nullable: true })
    lastName: string;

    @Column({ default: false })
    isActive: string;
    
    // 简写
    @Column('varchar', {
      unique: true,
      length: 170,
      name: 'name',
      comment: '业务名称',
    })
    name: string | null;
}

代码分析:

  • 这段代码的作用是定义了一个名为 User 的实体对象,并为该实体对象定义了四个属性,分别是 idfirstNamelastNameisActive,并指定了它们在数据库表中对应的列的一些配置,如数据类型、长度、是否允许为空、默认值等。
    • @PrimaryColumn - 表示将实体中的属性标记为表主列。

@column装饰器相同,但需将其primary选项设置为 true

@Entity()
export class User {
    // id属性列 为主列
    @PrimaryColumn()
    id: number;
}
    • @PrimaryGeneratedColumn - 表示将属性标记为值自动生成的主列
@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;
}
    • @ObjectIdColumn - 属性标记为 ObjectID

此装饰器仅用于 MongoDB。 MongoDB 中的每个实体都必须具有 ObjectID 列。

@Entity()
export class User {
    @ObjectIdColumn()
    id: ObjectID;
}
    • @CreateDateColumn - 表示该属性对应数据库表中的列为创建时间列
@Entity()
export class User {
    @CreateDateColumn()
    createdDate: Date;
}

代码分析:

  • 这段代码的作用是定义了一个名为 User 的实体对象,并为该实体对象定义了一个名为 createdDate 的属性,并指定了它在数据库表中对应的列为创建时间列,即在创建记录时自动将该列的值设置为当前时间。
    • @UpdateDateColumn - 表示该属性对应数据库表中的列为更新时间列
@Entity()
export class User {
    @UpdateDateColumn()
    updatedDate: Date;
}

代码分析:

  • 这段代码的作用是定义了一个名为 User 的实体对象,并为该实体对象定义了一个名为 updatedDate 的属性,并指定了它在数据库表中对应的列为更新时间列,即在更新记录时自动将该列的值设置为当前时间。

关系装饰器

数据库表之间的关系,是操作数据库中极为重要并且复杂的部分,实际应用也是比较多,单独提出来分析,点击下方链接查看(在代码分析中,等待、冷静)

多种复杂关系,点击跳转

订阅者和监听者装饰器

  • @AfterLoad - 表示该方法在实体对象从数据库中加载后被调用
@Entity()
export class Post {
    @AfterLoad()
    updateCounters() {
        if (this.likesCount === undefined) this.likesCount = 0;
    }
}

代码分析:

  • 这段代码的作用是定义了一个名为 Post 的实体对象,并为该实体对象定义了一个名为 updateCounters 的方法,在实体对象从数据库中加载后被调用,用于检查 likesCount 属性是否为 undefined,如果是则将其设为 0
  • @BeforeInsert - 表示该方法在实体对象插入到数据库之前被调用
@Entity()
export class Post {
    @BeforeInsert()
    updateDates() {
        this.createdDate = new Date();
    }
}

代码分析:

  • 这段代码的作用是定义了一个名为 Post 的实体对象,并为该实体对象定义了一个名为 updateDates 的方法,该方法在实体对象插入到数据库之前被调用,用于将实体对象的 createdDate 属性设置为当前时间。
  • @AfterInsert - 表示该方法在实体对象插入到数据库之后被调用。
@Entity()
export class Post {
    @AfterInsert()
    resetCounters() {
        this.counters = 0;
    }
}

代码分析:

  • 这段代码的作用是定义了一个名为 Post 的实体对象,并为该实体对象定义了一个名为 resetCounters 的方法,该方法在实体对象插入到数据库之后被调用,用于将实体对象的 counters 属性设置为 0
  • @BeforeUpdate - 表示该方法在实体对象更新到数据库之前被调用
@Entity()
export class Post {
    @BeforeUpdate()
    updateDates() {
        this.updatedDate = new Date();
    }
}

代码分析:

  • 这段代码的作用是定义了一个名为 Post 的实体对象,并为该实体对象定义了一个名为 updateDates 的方法,该方法在实体对象更新到数据库之前被调用,用于将实体对象的 updatedDate 属性设置为当前时间。
  • @AfterUpdate - 表示该方法在实体对象更新到数据库之后被调用
@Entity()
export class Post {
    @AfterUpdate()
    updateCounters() {
        this.counter = 0;
    }
}

代码分析:

  • 这段代码的作用是定义了一个名为 Post 的实体对象,并为该实体对象定义了一个名为 resetCounters 的方法,该方法在实体对象更新到数据库之后被调用,用于将实体对象的 counters 属性设置为 0
  • @BeforeRemove - 表示该方法在实体对象从数据库删除之前被调用
@Entity()
export class Post {
    @BeforeRemove()
    updateStatus() {
        this.status = "removed";
    }
}

代码分析:

  • 这段代码的作用是定义了一个名为 Post 的实体对象,并为该实体对象定义了一个名为 updateDates 的方法,该方法在实体对象从数据库删除之前被调用,用于将实体对象的 status 属性设置为 removed
  • @AfterRemove - 表示该方法在实体对象从数据库删除之后被调用
@Entity()
export class Post {
    @AfterRemove()
    updateStatus() {
        this.status = "removed";
    }
}

代码分析:

  • 这段代码的作用是定义了一个名为 Post 的实体对象,并为该实体对象定义了一个名为 resetCounters 的方法,该方法在实体对象从数据库删除之后被调用,用于将实体对象的 status 属性设置为 removed
  • @BeforeRecover - 表示该方法在实体对象从软删除状态恢复到普通状态时被调用

在 TypeORM 中,实体对象可以被标记为软删除状态。当实体对象被软删除时,将不会在数据库中被物理删除,而是会被标记为已删除的状态。在后续的查询操作中,软删除的实体对象将不会被查询出来,除非在查询时显式地指定查询已删除的实体对象。当需要恢复已删除的实体对象时,只需要将该实体对象从软删除状态恢复到普通状态即可。

“从软删除状态恢复到普通状态”指的是将一个已经被软删除的实体对象恢复到普通状态,使其可以被查询出来并参与后续的数据库操作

@Entity()
export class Post {
    @BeforeRecover()
    updateStatus() {
        this.status = "recovered";
    }
}

代码分析:

  • 这段代码的作用是定义了一个名为 Post 的实体对象,并为该实体对象定义了一个名为 updateStatus 的方法,该方法在实体对象从软删除状态恢复到普通状态时被调用,用于将实体对象的 status 属性设置为 "recovered"
  • @AfterRecover - 表示该方法在实体对象从软删除状态恢复到普通状态时被调用
@Entity()
export class Post {
    @AfterRecover()
    updateStatus() {
        this.status = "recovered";
    }
}

代码分析:

  • 这段代码的作用是定义了一个名为 Post 的实体对象,并为该实体对象定义了一个名为 updateStatus 的方法,该方法在实体对象从软删除状态恢复到普通状态时被调用,用于将实体对象的 status 属性设置为 "recovered"
  • @EventSubscriber - 表示定义一个实体订阅者用
@EventSubscriber()
export class PostSubscriber implements EntitySubscriberInterface<Post> {
    /**
     * 表示此订阅者仅侦听Post事件。
     */
    listenTo() {
        // 只有返回的事件,才是监听的实体
        return Post;
    }

    /**
     * 在POST INSERTED之前调用。
     */
    beforeInsert(event: InsertEvent<Post>) {
        console.log(`BEFORE POST INSERTED: `, event.entity);
    }
}

代码分析:

  • 这段代码的作用是定义了一个名为 PostSubscriber 的实体订阅者,用于处理 Post 实体对象在数据库中的各种事件,其中通过 listenTo 方法指定该订阅者只关注 Post 实体对象的事件,在 beforeInsert 方法中处理 Post 实体对象在插入数据库之前的事件,输出日志信息和要插入的实体对象。

特殊装饰器(高级装饰器)

  • @Index - 为实体对象中的多个属性添加联合索引

联合索引是指在数据库中为多个列添加的一个索引,用于提高查询效率和优化数据库的性能

@Entity()
// 表示在数据库中为 `firstName` 和 `lastName` 这两列添加一个联合索引
@Index(["firstName", "lastName"])
@Index(["lastName", "middleName"])
// 表示在数据库中为 `firstName`、`lastName` 和 `middleName` 这三列添加一个联合唯一索引
@Index(["firstName", "lastName", "middleName"], { unique: true })
export class User {
    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    middleName: string;
}

代码分析:

  • 这段代码的作用是定义了一个名为 User 的实体对象,并为该实体对象定义了三个索引,分别为 firstNamelastName 的联合索引,lastNamemiddleName 的联合索引,以及 firstNamelastNamemiddleName 的联合唯一索引,用于提高查询效率和保证数据的唯一性。
  • @Unique - 为实体对象中的某个属性或多个属性组合起来添加唯一约束

唯一约束是指在数据库中为某个列或多个列添加一个限制条件,用于保证该列或这些列组合起来的唯一性,避免重复数据的出现

@Entity()
@Unique(["firstName"])
// 表示在数据库中为 `lastName` 和 `middleName` 这两列组合起来增加一个唯一约束,用于保证这两列组合起来的唯一性
@Unique(["lastName", "middleName"])
// 表示在数据库中为 `firstName`、`lastName` 和 `middleName` 这三列组合起来增加一个名为 `UQ_NAMES` 的唯一约束,用于保证这三列组合起来的唯一性
@Unique("UQ_NAMES", ["firstName", "lastName", "middleName"])
export class User {
    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    middleName: string;
}

事务装饰器

  • @Transaction@TransactionManager 和 @TransactionRepository

使用 @Transaction() 装饰器时,将要执行的方法标记为事务方法,所以也被称为事务装饰器

@Transaction()
save(@TransactionManager() manager: EntityManager, user: User) {
    return manager.save(user);
}

上方的方法是一个实体对象的保存方法,使用 @Transaction() 装饰器将该方法标记为一个事务方法。该方法有两个参数,第一个参数是一个 EntityManager 对象,用于处理数据库事务,第二个参数是一个 User 对象,表示要保存的实体对象。在方法体中,通过 manager.save(user) 方法将实体对象保存到数据库中,并返回保存后的实体对象。

@Transaction()
save(user: User, @TransactionRepository(User) userRepository: Repository<User>) {
    return userRepository.save(user);
}

上方的方法也是一个实体对象的保存方法,同样使用 @Transaction() 装饰器将该方法标记为一个事务方法。该方法有两个参数,第一个参数是一个 User 对象,表示要保存的实体对象,第二个参数是一个 Repository<User> 对象,用于操作数据库中的 User 表。在方法体中,通过 userRepository.save(user) 方法将实体对象保存到数据库中,并返回保存后的实体对象。

@Transaction()
save(@QueryParam("name") name: string, @TransactionRepository() userRepository: UserRepository) {
    return userRepository.findByName(name);
}

上方的方法是一个查询方法,同样使用 @Transaction() 装饰器将该方法标记为一个事务方法。该方法有两个参数,第一个参数是一个 string 类型的 name,表示要查询的实体对象的名称,第二个参数是一个 UserRepository 对象,用于操作数据库中的 User 表。在方法体中,通过 userRepository.findByName(name) 方法查询数据库中符合条件的实体对象,并返回查询结果。

自定义存储库装饰器

  • @EntityRepository - 用于自定义实体的存储库。

使用getCustomRepository()方法获取任何自定义创建的存储库。

创建自定义存储库

方法一:继承 Repository 类实现创建自定义存储库

import { EntityRepository, Repository } from "typeorm";
import { User } from "../entity/User";

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  findByName(firstName: string, lastName: string) {
    return this.findOne({ firstName, lastName });
  }
}

使用:

import { getCustomRepository } from "typeorm";
import { UserRepository } from "./repository/UserRepository";

const userRepository = getCustomRepository(UserRepository); // 或connection.getCustomRepository或manager.getCustomRepository()
const user = userRepository.create(); // 和 const user = new User();一样
user.firstName = "Timber";
user.lastName = "Saw";
await userRepository.save(user);

const timber = await userRepository.findByName("Timber", "Saw");

方法二:继承 AbstractRepository 类实现创建自定义存储库

import { EntityRepository, AbstractRepository } from "typeorm";
import { User } from "../entity/User";

@EntityRepository(User)
export class UserRepository extends AbstractRepository<User> {
  createAndSave(firstName: string, lastName: string) {
    const user = new User();
    user.firstName = firstName;
    user.lastName = lastName;
    return this.manager.save(user);
  }

  findByName(firstName: string, lastName: string) {
    return this.repository.findOne({ firstName, lastName });
  }
}

使用:

import { getCustomRepository } from "typeorm";
import { UserRepository } from "./repository/UserRepository";

const userRepository = getCustomRepository(UserRepository); // or connection.getCustomRepository or manager.getCustomRepository()
await userRepository.createAndSave("Timber", "Saw");
const timber = await userRepository.findByName("Timber", "Saw");

这两种方式在实现自定义存储库时有以下区别:

  1. Repository 类是 TypeORM 提供的默认存储库类,它包含了一些常用的实体操作方法,例如 save()delete()findOne() 等。如果选择继承 Repository 类,则可以直接使用这些内置方法进行数据库操作,无需重复编写这些方法。此外,还可以在自定义存储库中添加其他自定义方法来扩展功能。

  2. AbstractRepository 类是一个抽象类,它没有内置的实体操作方法,需要手动实现所有数据库操作方法。如果选择继承 AbstractRepository 类,则需要自己实现所有数据库操作方法,包括 save()delete()findOne() 等。同时,也可以在自定义存储库中添加其他自定义方法来扩展功能。 因此,选择继承 Repository 类可以更快速地实现自定义存储库,但是可能存在一些性能上的损失,因为该类中包含了很多通用的代码;而选择继承 AbstractRepository 类则需要手动实现所有数据库操作方法,但是可以更加灵活地控制存储库的行为和性能。

获取并操作数据库数据