typeorm-入门

typeorm-入门本文详细介绍了 TypeORM 框架 包括其在 NestJS 中的应用 DataSource Connection Entity Relation 等概念 以及如何通过 TypeORMCLI 快速创建项目 定义实体 列装饰器和关系 以及提供了丰富的增删改查 API

大家好,欢迎来到IT知识分享网。

简述

typeorm是一个数据库orm框架,在nestjs官网中有提到,可以充分发挥利用typescript的特性,当然也支持js其中涉及的概念包括

  • DataSource 数据源,Connection 连接数据库
  • Entity 实体,实体类映射数据库表
  • Relation 关系,定义实体类之间的关系,也就是数据库表之间的关系,一对一,多对一,多对多。
  • Entity Manager和Repository,Entity Manager可以管理创建连接时配置的所有实体,而Repository只能操作对应的实体,两个的api几乎的差不多的
  • QueryBuilder,用来创建更复杂的sql查询,灵活度比较高。

使用typeorm cli快速创建项目

npm install typeorm -g 

初始化项目

typeorm init --name MyProject 

安装连接数据库驱动包,这里以msyql为2,安装msyql2

npm install mysql2 

创建连接

import "reflect-metadata" import { 
    DataSource } from "typeorm" export const AppDataSource = new DataSource({ 
    type: "mysql", host: "localhost", port: 3306, username: "root", password: "zhuang", database: "typeorm_test",// 连接的数据库 synchronize: true,// 开发过程使用,可以同步修改表结构,生产切忌使用。 logging: true,// 打印输出sql语句 connectorPackage: "mysql2",// 驱动包 entities: [".//entity/*.ts"],// 指定entity文件,也可以是实体数组,[User] migrations: [], subscribers: [] }) 

dataSource.query()写sql语句

const studentList = await dataSource.query(` select * from student `) 

定义实体Entity

快速成实体

typeorm entity:create -n User typeorm entity:create src/entity/HelloWorld # 快速生成实体类 

实体装饰器

  • @Entity(),@Entity({name:“指定表名”}) ,声明该类是实体类,对应表,表名默认是类名 Class 的小写下划线分割。
@Entity() @Entity("user") @Entity({ 
    name: "users",// 表名 engine: "MyISAM", // 数据库引擎 database: 'example_dev',// 数据库 synchronize: false,// 是否同步更该表结构 orderBy: { 
   // 查询时的默认排序 name: "ASC", id: "DESC" } }) 

在这里插入图片描述

  • @ViewEntity(),视图实体,不会对应表
@ViewEntity({ 
    expression: ` SELECT "post"."id" "id", "post"."name" AS "name", "category"."name" AS "categoryName" FROM "post" "post" LEFT JOIN "category" "category" ON "post"."categoryId" = "category"."id" ` }) export class PostCategory { 
   } 

列装饰器

  • @Column(),用来定义实体对应表列,默认对应的列名就是实体的属性名,可以配置,属性的数据类型没有显示指定的话,typeorm会根据ts的类型自动推断,在mysql中,string - varchar(255), boolean - tinyint, number - int,Date - datetime(6)
  • @PrimaryGeneratedColumn(), 相当于@Column({primary:true})具体的列装饰器都是固定了一些配置的装饰器.
  • @CreateDateColumn(),自动插入
  • @UpdateDateColumn(),自动更新
@Entity("users") export class User { 
    @Column({ 
    primary: true }) // 配置主键, @PrimaryGeneratedColumn("uuid"),配置uuid主键 id: number; @Column({ 
    type: "varchar", length: 200, unique: true })// 配置数据库中具体类型,长度,唯一 firstName: string; @Column({ 
    nullable: true })// 配置是否为空 lastName: string; @Column({ 
    default: false })// 配置默认值,boolen默认类型被转换为 tinyint, false 对应 0,true 对应 1 isActive: boolean; @CreateDateColumn() // 创建时自动插入 createdDate: Date; @UpdateDateColumn() // 更新时自动更新 updatedDate: Date; } 

关系装饰器

指定表之间的关系,默认配置会生成物理外键,(实际项目中建议不使用外键,通过关联表逻辑外键来关联,并且关联表一般也是自己创建,不使用ManyToMany这个装饰器来生成关联表),可以通过配置RelationOptions{createForeignKeyConstraints:false},不生成外键约束。一般该配置位于装饰器的最后一个参数,如下配置:

/ * 描述表之间关系的配置 */ export interface RelationOptions { 
    / * 配置不同表之间插入或更新时,相关的对象怎么配置,cascade:true 表示级联,如 user.roles, 当save的时候,把关联的roles实体也保存或者更新 * If set to true then it means that related object can be allowed to be inserted or updated in the database. * You can separately restrict cascades to insertion or updation using following syntax: * * cascade: ["insert", "update", "remove", "soft-remove", "recover"] // include or exclude one of them */ cascade?: boolean | ("insert" | "update" | "remove" | "soft-remove" | "recover")[]; / * Indicates if relation column value can be nullable or not. */ nullable?: boolean; / * 配置外键的onDelete * Database cascade action on delete. */ onDelete?: OnDeleteType; / * 配置外键的onUpdate * Database cascade action on update. */ onUpdate?: OnUpdateType; / * Indicate if foreign key constraints can be deferred. */ deferrable?: DeferrableType; / * Indicates whether foreign key constraints will be created for join columns. * Can be used only for many-to-one and owner one-to-one relations. * Defaults to true. * 创建外键,默认是true */ createForeignKeyConstraints?: boolean; / * Set this relation to be lazy. Note: lazy relations are promises. When you call them they return promise * which resolve relation result then. If your property's type is Promise then this relation is set to lazy automatically. */ lazy?: boolean; / * Set this relation to be eager. * Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods. * Only using QueryBuilder prevents loading eager relations. * Eager flag cannot be set from both sides of relation - you can eager load only one side of the relationship. */ eager?: boolean; / * Indicates if persistence is enabled for the relation. * By default its enabled, but if you want to avoid any changes in the relation to be reflected in the database you can disable it. * If its disabled you can only change a relation from inverse side of a relation or using relation query builder functionality. * This is useful for performance optimization since its disabling avoid multiple extra queries during entity save. */ persistence?: boolean; / * When a parent is saved (with cascading but) without a child row that still exists in database, this will control what shall happen to them. * delete will remove these rows from database. * nullify will remove the relation key. * disable will keep the relation intact. Removal of related item is only possible through its own repo. */ orphanedRowAction?: "nullify" | "delete" | "soft-delete" | "disable"; } 
  • OneToOne(),拥有该列的是从表,拥有关系的一方,即拥有xxxId的一方必须和@JoinColumn()配合使用。第一个参数是一个函数,返回关联的实体类,第二个参数如果有,是指定关系的反方,即关联字段的那方的实体类可以通过它的xxx属性来查询关系,第三个参数是配置关系的选项,包括 cascade级联,createForeignKeyConstraints创建外键,eager查询时总是把关系类也查出来。其他的关系装饰器也大抵如此。

cascade:true只能有一方配置,如果两边都配置cascade:true会报错,如果两边都配置,只要有一边不要配置cascade:[“remove”]就行。如果没有cascade:true关系,那么保存实体时,关联的实体必须先保存到数据库中,否则报错。
报错信息如下
在这里插入图片描述

@Entity() export class User { 
    // 指定关联实体,假如Profile实体通过@OnetoOne() 声明 user:User 字段,那么查询profile时也可以查询到user类 @OneToOne(type => Profile, profile => profile.user,{ 
    createForeignKeyConstraints:false// 不创建外键 }) @JoinColumn() // 表示user表有profileId字段关联 profile 表, profile: Profile; } 
  • ManyToOne(),拥有该列的表是从表,默认生成关联id,多对一的情况下,可以省略 @JointColumn(), 除非想指定 关联id 和关联列
  • OneToMany(),反向关系,用在一的一方
@Entity() export class Photo { 
    @PrimaryGeneratedColumn() id: number; @Column() url: string; // 第二个参数指定另一面关系的关联属性,这里可以省略 @JoinColumn(),默认生成字段 userId  @ManyToOne(() => User, user => user.photos) user: User; } @Entity() export class User { 
    @PrimaryGeneratedColumn() id: number; @Column() name: string; @OneToMany(() => Photo, photo => photo.user) photos: Photo[]; } 
  • ManyToMany()
    双方都必须使用,而且必须有一方使用@JoinTable()
@Entity() export class Post { 
    @ManyToOne(type => Category) @JoinColumn({ 
    name: "cat_id",// 指定列名 referencedColumnName: "name" // 引用的Category的列名 }) category: Category; } 
  • JoinColumn()
    指定关系列字段,用于一对一,多对一和多对多,可以设置 cascade,createForeignKeyConstraints,以及另一面的关系属性。
  • JoinTable()
    用于多对多添加中间表,并指定拥有关系的列,只需要一方添加即可。一般要配合@ManyToMany()使用,还可以配置关联表名称和指定关联的列名和关联表的列名
  • RelationId()
    可以获取关联的实体的id,包括多对一和多对多,此 id 仅用于展示,对其修改并不会增删改关系
@Entity() export class Post { 
    @ManyToOne(type => Category) category: Category; @RelationId((post: Post) => post.category) // 需要指定目标关系 categoryId: number; } @Entity() export class Post { 
    @ManyToMany(type => Category) categories: Category[]; @RelationId((post: Post) => post.categories) categoryIds: number[]; } 

增删改查api

typeorm增删改查有三种途径:

  1. 通过 Entity Manager 的api,
  2. 通过 Repository 的api, 其中还包括了 TreeRepository(树结构的仓库) 和 MongoRepsotiry
  3. 通过 createQueryBuilder 的api构建sql

Entity Manager 和 Repository 的api基本相同,区别是 Manager 的api要指定实体类, 而 Repository 一般是通过 manager 指定 实体类获取到, 所以 Respository 可以少传一个类, 而 QueryBuilder 可以通过 数据源 DataSource.createQueryBuilder ,或者 实体管理器 Entity Manager 或者 Repository 来获取.

Entity Manager 和 Repository

这两者的api几乎相同,可以通过 manager.find(User,{where:{id:1}}) 等价与 manager.getRepositry(User).find({where:{id:1}})

简单示例:

import { 
    DataSource } from "typeorm" import { 
    User } from "./entity/User" const myDataSource = new DataSource(/*...*/) // 这里 manager 指定了 User 类 const user = await myDataSource.manager.findOneBy(User, { 
    id: 1, }) user.name = "Umed" await myDataSource.manager.save(user) import { 
    User } from "./entity/User" // 这里通过 getRepository(User) 获取仓库 const userRepository = dataSource.getRepository(User) const user = await userRepository.findOneBy({ 
    id: 1, }) user.name = "Umed" await userRepository.save(user) 

创建实体
可以通过 new User() 这种来创建实体,也可以通过 create 方法

const user = manager.create(User, { 
    lastName: "xxx", firstName: "xxx", age: 20 }); const users = manager.create(User, [ { 
    firstName: "xxx", lastName: "xxx", age: 20 }, { 
    firstName: "yyy", lastName: "yyy", age: 22 } ]); 

新增
save
保存给定的实体或实体数组,如果该实体已存在,那么就是更新,所有这个方法会先使用 select 语句后看是否存在决定是 insert 还是update

await manager.save(user) await manager.save([user1,user2,user3]) 

insert
插入实体或实体数组

await manager.insert(User,{ 
   username:"xxx"}) await manager.insert(User,[{ 
   username:"xxx"},{ 
   username:"yyy"}]) 

删除
remove
删除给定的实体或实体数组

await manager.remove(user) await manager.remove([user1,user2,user3]) 

delete
根据id,ids或者给定条件删除

await manager.delete(User,1) await manager.delete(User,[1,2,3]) await manager.delete(User,{ 
   firstName:"xxx"}) 

修改
update
根据给定的id或条件修改实体,直接就是update语句,不像save方法要先select查询后再修改.

// 第二个参数是条件,第三个参数是修改的部分字段 await manager.update(User,{ 
   id:1},{ 
   firstName:"xxx"}) 

save
如果实体有id属性,并且在数据库中存在,那么save方法也可以修改数据

const user = await manager.findOneBy(User,{ 
   id:1}) await manager.save(User,user) 

查询
查询方法最全的是 find 方法,其他的都是如 fineBy , findOne , findOneBy , findAndCount , findAndCountBy ,

find
查询与 FindOptions 相匹配的对象

await manager.find(User,{ 
    select:["firstName","lastName","age"], where:{ 
    id:1, firstName:Like("%jac%") // like }, take:10, // 获取 10 条 skip:1 // 跳过 1 条 }) 

findBy
相当于是指定 where 条件的 find 方法

await manager.findBy(User,{ 
   id:1}) 

findOne
相当于 find 方法,然后 take 1 ,只获取第一条

await manager.findOne(User,{ 
    where:{ 
    firstName:"xxx" } }) 

findOneBy
相当于 findOne , 然后只传 where 条件

await manager.findOneBy(User,{ 
   lastName:'xxx'}) 

findAndCount
查找并统计返回结果,比 find 方法多了一个统计结果

await manager.findAndCount(User,{ 
    where:{ 
    firstName:"xxx" } }) 

findAndCountBy
类似 findAndCount , 多了一个 where 条件

await manager.findAndCountBy(User,{ 
   firstName:"xxx"}) 

计数
用于分页等
count
对匹配查询条件的进行计数,常用于分页


await manager.count(User,{ 
    where:{ 
    firstName:"xxx" } }) 

countBy
与count类似,固定了 where 参数

await manager.countBy(User,{ 
   firstName:"xxx"}) 

query
可以书写原本的 sql 语句

await manager.query("select * from user") 
find方法的 findOptions 选项

Entity Manager 和 Repository 的 find* 方法的选项,可以极大效率的帮助我们写常规的sql,复杂的sql再通过 QueryBuilder 去构建.

基本配置选项如下

userRepository.find({ 
    select: { 
    // 选中字段 firstName: true, lastName: true, userRoleEntities: { 
    roleId: true,// 中间关联表这个字段必须带,否则role字段查不出来 role: { 
    roleName: true, }, }, }, relations: { 
    // 关联关系是否一并查出来,子实体的关联关系也可以查出来,相当于 join 和 leftJoinAndSelect profile: true, photos: true, videos: true, videos: { 
    videoAttributes: true, }, }, where: { 
    firstName: "Timber", // 查询条件,等于 lastName: "Saw", profile: { 
    userName: "tshaw", }, }, order: { 
    name: "ASC", // 排序字段 id: "DESC", // 降序 , ASC 升序 }, skip: 5, // 跳过 5 条 take: 10, // 拿 10 条 cache: true, // 是否缓存 }) 
  • Not
import { 
    Not } from "typeorm" const loadedPosts = await dataSource.getRepository(Post).findBy({ 
    title: Not("About #1"), }) 
  • LessThan,LessThanOrEqual
  • MoreThan,MoreThanOrEqual
  • Equal
  • Like
import { 
    Not } from "typeorm" const loadedPosts = await dataSource.getRepository(Post).findBy({ 
    title: Like("%out #%"), }) 
  • Between
  • In
  • IsNull
QueryBuilder的增删改查方式

QueryBuildertypeorm最强大的功能之一,它允许你使用优雅且方便的语法构建 SQL 查询,执行它们并获得自动转换的实体.

简单示例
例如:

const firstUser = await this.manager .getRepository(User) .createQueryBuilder("user") .where("user.id = :id", { 
    id: 1 }) .getOne() 

对应的sql语句为:

SELECT user.id as userId, user.firstName as userFirstName, user.lastName as userLastName FROM users user WHERE user.id = 1 

注意事项:在where语句中,参数名必须是唯一的,例如同一个查询中不能出现两次 where userId = :id,应该改为 :xxId 和 :yyId

const result = await dataSource .getRepository(User) .createQueryBuilder('user') .leftJoinAndSelect('user.linkedSheep', 'linkedSheep') .leftJoinAndSelect('user.linkedCow', 'linkedCow') .where('user.linkedSheep = :id', { 
    id: sheepId }) .andWhere('user.linkedCow = :id', { 
    id: cowId }); 

创建QueryBuilder的来源
xxx.createQueryBuilder()

  • dataSource
  • entityManager
  • repository

5种不同类型的QueryBuilder

  • SelectQueryBuilder
const user = await dataSource .createQueryBuilder() .select("user") .from(User, "user") .where("user.id = :id", { 
    id: 1 }) .getOne() // 或者 const user = await dataSource .createQueryBuilder(User,'user') .getMany() // 添加 select 字段 const { 
    sum } = await dataSource .getRepository(User) .createQueryBuilder("user") .select("SUM(user.photosCount)", "sum") .where("user.id = :id", { 
    id: 1 }) .getRawOne() // 有关系的查询,只想返回定义的实体种的某些字段 this.entityManager .getRepository(UserEntity) .createQueryBuilder('user') .innerJoin('user.userRoleEntities', 'userRole') .innerJoin('userRole.role', 'role') .select(['user.username', 'user.id', 'userRole.roleId', 'role']) // 关键在于userRole.roleId,每个实体必须至少有一个字段出现,否则其关联的字段不会出现 .getMany(); 

如果代码之间定义了通过 @ManyToOne(),@OneToMany()定义了多对多关系,也可以通过 innerJoin()和innerJoinAndMapMany()来查询多对多关系而不显示关联表的信息,实际项目中不要外键,并且多对多的中间关联表可能有其他列,不使用 @ManyToMany()来定义多对多关系,而是通过一张多对一的表来实现多对多,@JoinTable() 装饰器也用不上,所以查询时只要多对多不要中间关联表的代码示例如下:

return this.entityManager .createQueryBuilder() .select('user') .from(UserEntity, 'user') .innerJoin(UserRoleEntity, 'userRole') // 这是实体里定义好的中间表,只用innerJoin来关联 .innerJoinAndMapMany('user.roles', RoleEntity, 'role') // 这是另外多的一方的表,使用innerJoinAndMap添加到user实体上的roles属性,非常实用. .innerJoin(RolePermissionEntity, 'rolePermission') .innerJoinAndMapMany('user.permissions', PermissionEntity, 'permission') .getMany(); 

如果代码的 entity 没有通过 @OneToOne(),@ManyToOne(),@OneToMany()等装饰器定义实体间的关系,但是可以通过某些列之间的字段来关系,那么 typeOrmQueryBuilder 也可以支持关联和加载实体,如下代码
这段代码实现了两张关联表,三张实体表,查询后没有不出现中间表的结果.(如果使用typeorm的@ManyToOne这种装饰器定义的关系来查询必然出现关联表的实体),其中中间表用innerJoin(),目标表用innerJoinAndMapMany()来关联并添加到user上.选择字段时建议使用 .addSelect('user.*'),也可以使用 select(['user.id',''role.xxx,'permission.xx'])

// 以下是不通过 typeorm 加载中间表,直接通过 innerJoin() 和 innerJoinAndMapMany() 来实现联表查询 // 如果不使用 innerJoinAndMapMany() ,那么getMany()只会得到第一个表即user表的实体,getRawMany()才会得到和sql一样的多行记录 this.entityManager .createQueryBuilder(UserEntity, 'user') .innerJoin(UserRoleEntity, 'userRole', 'userRole.userId=user.id') .innerJoinAndMapMany( 'user.roles',// 将 role 实体添加到user实体的roles属性中,而不经过中间表 RoleEntity, 'role', 'role.id=userRole.roleId', // 如果实体定义里有定义了关系了,那么这个条件可以省略 ) .innerJoin( RolePermissionEntity, 'rolePermission', 'rolePermission.roleId=role.id', ) .innerJoinAndMapMany( 'user.permissions', PermissionEntity, 'permission', 'permission.id=rolePermission.permissionId', ) // .addSelect('user.*') // 要么用这个,记得带*号,可以加载 user 实体定义的除了关系实体外的所有字段 .select(['user.id', 'role.roleName', 'permission.permName']) // 也可以用 select 手动指定要选取的字段名,这样的话全部是部分选择,如果是 createQueryBuilder().select('user').from(USer,'user'),一开始就选择了字段了,那么后面addSelect的单独选择字段不会起作用了. .getMany() 
  • InsertQueryBuilder
 await dataSource .createQueryBuilder() .insert() .into(User) .values([ { 
    firstName: "Timber", lastName: "Saw" }, { 
    firstName: "Phantom", lastName: "Lancer" }, ]) .execute() 
  • UpdateQueryBuilder
await dataSource .createQueryBuilder() .update(User) .set({ 
    firstName: "Timber", lastName: "Saw" }) .where("id = :id", { 
    id: 1 }) .execute() 
  • DeleteQueryBuilder
await dataSource .createQueryBuilder() .delete() .from(User) .where("id = :id", { 
    id: 1 }) .execute() 
  • RelationQueryBuilder
await dataSource .createQueryBuilder() .relation(User,"photos") .of(id) .loadMany(); 

Bug

entityMananger的查询api有个bug,如 findOne(),当传递的id为null时,其生成的sql语句里没有where id = null,于是查出了第一条数据

return this.entityManager .getRepository(UserEntity) .findOne({ 
    where: { 
    id: null } }); 

以下是其构成的sql语句:

SELECT `UserEntity`.`id` AS `UserEntity_id`, `UserEntity`.`create_at` AS `UserEntity_create_at`, `UserEntity`.`update_at` AS `UserEntity_update_at`, `UserEntity`.`username` AS `UserEntity_username`, `UserEntity`.`birthday` AS `UserEntity_birthday` FROM `user` `UserEntity` LIMIT 1 

而 QueryBuilder创建的sql就符合预期,当id为null时,where语句正确的构造

return this.entityManager .createQueryBuilder(UserEntity, 'user') .where('user.id=:id', { 
    id: null }) .getOne(); 

以下是 QueryBuilder 当id为null时构建的sql语句

 SELECT `user`.`id` AS `user_id`, `user`.`create_at` AS `user_create_at`, `user`.`update_at` AS `user_update_at`, `user`.`username` AS `user_username`, `user`.`birthday` AS `user_birthday` FROM `user` `user` WHERE `user`.`id`=? -- PARAMETERS: [null] 

QueryBuilder 和 entityManager的find,save,udpate,delete等api的区别:
find等api基于定义好的实体类的属性及其关系来操作表,优点是简单,缺点是没有那么灵活.而QueryBuilder能干find等api的事,而且即使没有事先在entity中定义好关系,也可以通过 innerJoin 来关联

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/117985.html

(0)
上一篇 2025-11-16 17:45
下一篇 2025-11-16 18:10

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信