深入探究 TypeORM:前端开发者的后端数据操作利器
在现代的软件开发中,前后端分离已经成为了一种趋势。作为前端开发者,我们有时也需要涉足后端领域,特别是在处理数据持久化时。当我们谈论数据持久化,数据库无疑是其中的核心组件。但对于前端开发者来说,直接操作数据库可能会显得有些陌生和复杂。这时,ORM(对象关系映射)工具就派上了用场。在Node.js生态系统中,TypeORM是一个非常受欢迎的ORM选择。
什么是 TypeORM?
TypeORM 是一个在 Node.js、Browser、Cordova、PhoneGap、Ionic、React Native、NativeScript、Expo 和 Electron 平台上支持 TypeScript 和 JavaScript(ES5, ES6, ES7, ES8)的 ORM。它可以在 NodeJS、浏览器、Cordova、PhoneGap 和 Ionic 中运行,支持 MySQL / MariaDB / Postgres / SQLite / Microsoft SQL Server / Oracle / sql.js 等多种数据库,并提供了强大的模型定义、数据操作等功能。
为何选择 TypeORM?
- 类型安全:由于 TypeORM 支持 TypeScript,因此它提供了类型安全的数据操作。这意味着在编译时就能捕获许多常见的错误。
- 易于上手:对于前端开发者来说,TypeORM 的 API 设计直观且友好,很多概念与前端框架中的模式相似。
- 活跃的社区:TypeORM 有一个庞大的用户基础和活跃的社区,这意味着遇到问题时可以迅速找到帮助。
- 支持多种数据库:无论是关系型数据库还是非关系型数据库,TypeORM 都提供了良好的支持。
开始使用 TypeORM
在使用 TypeORM 之前,需要先安装相应的依赖:
npm install typeorm
连接数据库
首先,需要创建一个数据库连接。这通常在一个名为 app.ts
或 index.ts
的文件中完成:
import { createConnection } from 'typeorm'; createConnection({ type: 'mysql', host: 'localhost', port: 3306, username: 'test', password: 'test', database: 'test', entities: [__dirname + '/entity/*.js'], synchronize: true, }).then(connection => { console.log('Connected to the database'); }).catch(error => { console.log('Error connecting to the database:', error); });
在上面的代码中,createConnection
方法接受一个包含数据库连接信息的对象。entities
属性指定了实体类的位置,这些实体类将映射到数据库中的表。synchronize
属性设置为 true
时,TypeORM 将自动同步实体类和数据库表的结构。
定义实体
实体是 TypeORM 中的核心概念,它代表了数据库中的一个表。例如,我们可以定义一个 User
实体:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() email: string; }
在上面的代码中,@Entity
装饰器标记了这个类是一个实体类。@PrimaryGeneratedColumn
装饰器表示 id
属性是主键,并且是自动生成的。@Column
装饰器标记了其他的列。
数据操作
有了实体类之后,就可以进行数据的增删改查了。以下是一些基本的操作示例:
import { getRepository } from 'typeorm'; import { User } from './entity/User'; // 查找用户 const userRepository = getRepository(User); const user = await userRepository.findOne({ email: 'example@example.com' }); // 创建用户 const newUser = new User(); newUser.name = 'John Doe'; newUser.email = 'john@example.com'; await userRepository.save(newUser); // 更新用户 user.name = 'Jane Doe'; await userRepository.save(user); // 删除用户 await userRepository.delete(user.id);
在上面的代码中,getRepository
方法用于获取指定实体的存储库,这个存储库提供了对实体进行增删改查的方法。
进阶使用
当然,让我们更深入地探讨TypeORM的一些进阶功能,这些功能在构建复杂应用程序时特别有用。
关系映射
在关系型数据库中,数据通常分布在多个相关联的表中。TypeORM通过关系映射装饰器(如@OneToOne
、@OneToMany
、@ManyToOne
和@ManyToMany
)允许你在实体类之间建立这些关系。这意味着你可以轻松地在TypeORM实体之间创建外键关系,并通过简单的API调用访问相关联的数据。
例如,如果你有一个User
实体和一个Profile
实体,每个用户都有一个个人资料,你可以这样设置关系:
@Entity() export class User { // ...其他字段 @OneToOne(type => Profile, profile => profile.user) profile: Profile; } @Entity() export class Profile { // ...其他字段 @OneToOne(type => User, user => user.profile) user: User; }
使用关系映射后,你可以通过简单的API调用获取用户的个人资料,或者通过个人资料获取用户信息。
事务管理
事务是一系列必须作为单个工作单元执行的数据库操作。如果事务中的所有操作都成功,则事务被提交,并且更改永久保存在数据库中。如果事务中的任何操作失败,则整个事务都会回滚,数据库恢复到事务开始之前的状态。
TypeORM提供了强大的事务管理功能,允许你在多个操作之间保持数据一致性。你可以使用QueryRunner
类或getConnection().transaction()
方法来管理事务。
const queryRunner = connection.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { // 执行一些数据库操作 // ... await queryRunner.commitTransaction(); } catch (error) { await queryRunner.rollbackTransaction(); } finally { await queryRunner.release(); }
或者使用更简洁的transaction
方法:
await connection.transaction(async manager => { // 执行一些数据库操作,使用manager代替常规repository // ... });
查询构建器
查询构建器是TypeORM中用于构建和执行复杂数据库查询的强大工具。它提供了链式调用语法,使得构建查询变得简单且直观。你可以使用查询构建器来创建各种类型的查询,包括选择、插入、更新和删除操作。
下面是一个使用查询构建器选择用户的例子:
const users = await connection.getRepository(User).createQueryBuilder("user") .where("user.name = :name", { name: "John" }) .orWhere("user.email = :email", { email: "john@example.com" }) .getMany();
查询构建器还支持更复杂的查询,如连接多个表、分组、排序以及使用原生SQL片段等。
通过这些进阶功能,TypeORM为开发者提供了一个全面且灵活的解决方案,以应对各种数据持久化挑战。无论是构建简单的CRUD应用还是复杂的业务逻辑,TypeORM都能提供所需的工具和抽象,使数据库操作变得更加容易和可维护。
注意:orm框架中,尽量不要加外键关联
弱弱的问一下
你们觉得typeorm好用嘛?为啥我觉得挺麻烦的啊?联查必须要给定一个关系,也就是说必须有外键,而且设置起来必须有manytoone和onetomany,而且储存的时候也必须按照他的一套来,而且manytoone的关联表竟然不返回主表的id,还要自己再指定一个比如说user_id的字段自己保存了,才能返回,他自己生成的userId并不返回。而且他的tree实体,看起来挺好的,但是用起来,真是难用,因为没法指定where条件啊。还是我不会用啊?就问问大家所有的orm框架都这么难搞嘛?我已经没写过sql很多年了。就拿一个查tree模型部门结构来说,我是自己写的先查根,再遍历根查孩子,这样子递归,可是这样子就有点拉跨,多查了很多次数据库,这样算法复杂度一下子上去了,部门少还没事。我就很郁闷,想知道大家都是怎么用orm框架的啊
连表查没有关联关系的实体
Dictionary_type实体
//定义Dictionary_type实体,字典类型 @EntityModel() export class Dictionary_type { @PrimaryGeneratedColumn("uuid") id: string; @Column({comment: "编码",nullable: true,}) code: string; @Column({comment: "状态", nullable: true,default:1}) state: number; @Column({comment: "字典类型名称", nullable: true}) text: string; @CreateDateColumn() creatDate: string; @UpdateDateColumn() updateDate: string; }
Dictionary_item 实体
在实体中设置type_id存放Dictionary_type主键id
@EntityModel() export class Dictionary_item { @PrimaryGeneratedColumn("uuid") id: string; @Column({comment: "序号",nullable: true,}) sort: number; @Column({comment: "字典类型", nullable: true,}) type_id: string; @Column({comment: "字典类型值", nullable: true}) value: number; @Column({comment: "字典类型内容", nullable: true}) text: string; @CreateDateColumn() creatDate: string; @UpdateDateColumn() updateDate: string; }
Post和PostExtend中没有设置关联关系,所以我们并不能在find option中关联两个实体进行连表查询。
但是可以用queryBuilder
const posts = await getConnection() .createQueryBuilder(Post, 'post') .leftJoinAndSelect(PostExtend, 'postExtend', 'post.id=postExtend.postId') .getManyAndCount() return posts;
查询结果:
[ [ { "id": 1, "title": "北京申奥成功", "content": "2003年奥林匹克运动会将在北京举行,北京欢迎你!" } ], 1 ]
上面的查询结果中并没有PostExtend的数据,这是因为不能确定两个实体之间的关联关系,所以无法确定查询结果的显示形式。
当然,也可以通过 getRawMany() 方法获取原生字段来获取PostExtend的信息,但是这样的查询结果显示并不友好。
1 const posts = await getConnection() 2 .createQueryBuilder(Post, 'post') 3 .leftJoinAndSelect(PostExtend, 'postExtend', 'post.id=postExtend.postId') 4 .getRawMany() 5 return posts;
结果:
1 [ 2 { 3 "post_id": 1, 4 "post_title": "北京申奥成功", 5 "post_content": "2003年奥林匹克运动会将在北京举行,北京欢迎你!", 6 "postExtend_id": 1, 7 "postExtend_postId": 1, 8 "postExtend_likeCount": 999, 9 "postExtend_readCount": 10000, 10 "postExtend_forwardCount": 666 11 } 12 ]
如果想要将原生字段映射到属性,可以使用 leftJoinAndMapOne() ,如果时一对多还可以使用 leftJoinAndMapMany()
const posts = await getConnection() .createQueryBuilder(Post, 'post') .leftJoinAndMapOne('post.postExtend',PostExtend, 'postExtend', 'post.id=postExtend.postId') .getManyAndCount() return posts;
结果:
[ 2 [ 3 { 4 "id": 1, 5 "title": "北京申奥成功", 6 "content": "2003年奥林匹克运动会将在北京举行,北京欢迎你!", 7 "postExtend": { 8 "id": 1, 9 "postId": 1, 10 "likeCount": 999, 11 "readCount": 10000, 12 "forwardCount": 666 13 } 14 } 15 ], 16 1 17 ]
postExtend的数据被映射到post.postExtend,这样的结果清晰明了。