TypeORM核心概念以及使用
实体定义
实体是 TypeORM 中的核心概念,用于映射数据库表。通过装饰器,可以定义实体的属性、主键、外键、关系等。以下是一个详细的实体定义示例:
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany, ManyToOne, JoinColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 100, unique: true })
username: string;
@Column({ type: 'varchar', length: 100, select: false })
password: string;
@Column({ type: 'int', default: 0 })
age: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
// 一对多关系
@OneToMany(() => Post, (post) => post.author)
posts: Post[];
}
@Entity('posts')
export class Post {
@PrimaryGeneratedColumn('increment')
id: number;
@Column('varchar')
title: string;
@Column('text')
content: string;
// 多对一关系
@ManyToOne(() => User, (user) => user.posts)
@JoinColumn()
author: User;
}
在 TypeORM 中,实体定义是通过使用装饰器(Decorators)来完成的。装饰器是 TypeScript 的一个特性,允许你在类、方法、属性或参数上添加元数据,TypeORM 利用这些元数据来生成对应的数据库表和列。以下是实体定义中常用的装饰器及其详细解释:
1. <span class="ne-text">@Entity()</span>
- 作用**:标记一个类为实体,该类将映射到数据库中的一个表。**
- 参数**:可以传递一个字符串参数来指定表名,如果不传递,则默认使用类名作为表名。**
@Entity('users')
export class User {
// ...
}
2. <span class="ne-text">@PrimaryGeneratedColumn()</span>
- 作用**:定义主键列,并自动生成值。**
- 参数**:可以指定生成策略,如**
<span class="ne-text">'increment'</span>
(自增)、<span class="ne-text">'uuid'</span>
(UUID)等。
@PrimaryGeneratedColumn('uuid')
id: string;
3. <span class="ne-text">@PrimaryColumn()</span>
- 作用**:定义主键列,但不自动生成值,需要手动指定。**
- 参数**:可以指定列的类型和其他约束。**
@PrimaryColumn()
customId: string;
4. <span class="ne-text">@Column()</span>
- 作用**:定义普通列。**
- 参数**:**
<span class="ne-text">type</span>
:列的数据类型,如<span class="ne-text">'int'</span>
、<span class="ne-text">'varchar'</span>
、<span class="ne-text">'text'</span>
等。<span class="ne-text">length</span>
:字符串类型的最大长度。<span class="ne-text">nullable</span>
:是否允许为空。<span class="ne-text">unique</span>
:是否唯一。<span class="ne-text">default</span>
:默认值。<span class="ne-text">select</span>
:是否在查询时默认选择该列(用于敏感数据,如密码)。<span class="ne-text">updateDateColumn</span>
、<span class="ne-text">createDateColumn</span>
的类似功能可以通过自定义实现。
@Column({ type: 'varchar', length: 100, unique: true })
username: string;
5. <span class="ne-text">@CreateDateColumn()</span>
- 作用**:定义一个列,在插入记录时自动设置为当前时间。**
- 参数**:通常不需要传递参数,但可以通过选项自定义行为。**
@CreateDateColumn()
createdAt: Date;
6. <span class="ne-text">@UpdateDateColumn()</span>
- 作用**:定义一个列,在更新记录时自动设置为当前时间。**
- 参数**:通常不需要传递参数,但可以通过选项自定义行为。**
@UpdateDateColumn()
updatedAt: Date;
@Column({
type: 'Date',
default: () => 'CURRENT_TIMESTAMP',
onUpdate: 'CURRENT_TIMESTAMP',
transformer: {
from: v => new Date(v).getTime(),
to: () => new Date(),
},
})
updateTime: Date; // 更新时间
在 TypeORM 中,<span class="ne-text">@CreateDateColumn()</span>
和 <span class="ne-text">@UpdateDateColumn()</span>
装饰器提供了自动管理实体创建时间和更新时间的功能。然而,如果你需要更灵活的控制或自定义行为,可以通过在实体类中手动定义这些列,并在实体的生命周期钩子(如 <span class="ne-text">@BeforeInsert</span>
和 <span class="ne-text">@BeforeUpdate</span>
)中设置它们的值来实现类似的功能。
以下是如何通过自定义实现 <span class="ne-text">createDateColumn</span>
和 <span class="ne-text">updateDateColumn</span>
功能的示例:
import {
Entity,
PrimaryGeneratedColumn,
Column,
BeforeInsert,
BeforeUpdate,
} from 'typeorm';
@Entity()
export class MyEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
// 自定义创建时间列
@Column({ type: 'timestamp', nullable: false, default: () => 'CURRENT_TIMESTAMP' }) // 也可以不设默认值,完全在代码中控制
createdAt: Date;
// 自定义更新时间列
@Column({ type: 'timestamp', nullable: false })
updatedAt: Date;
@BeforeInsert()
setCreatedAt() {
this.createdAt = new Date();
}
@BeforeUpdate()
setUpdatedAt() {
this.updatedAt = new Date();
}
}
解释
- 列定义**:**
<span class="ne-text">@Column({ type: 'timestamp', nullable: false })</span>
:定义<span class="ne-text">createdAt</span>
和<span class="ne-text">updatedAt</span>
列为<span class="ne-text">timestamp</span>
类型,且不允许为空。- 对于
<span class="ne-text">createdAt</span>
,你可以选择设置一个默认值(如<span class="ne-text">CURRENT_TIMESTAMP</span>
),但这取决于你的数据库是否支持以及你是否希望在数据库层面也设置默认值。如果你希望在代码中完全控制,可以不设置默认值。
- 生命周期钩子**:**
<span class="ne-text">@BeforeInsert()</span>
:在实体被插入数据库之前调用。在这里,我们设置<span class="ne-text">createdAt</span>
的值为当前时间。<span class="ne-text">@BeforeUpdate()</span>
:在实体被更新数据库之前调用。在这里,我们设置<span class="ne-text">updatedAt</span>
的值为当前时间。
注意事项
- 时区问题**:确保你的应用程序和数据库在处理时间时保持一致的时区设置,以避免时间不一致的问题。**
- 性能考虑**:在高频率更新的场景下,使用生命周期钩子来设置时间戳可能会对性能产生一定影响。然而,在大多数情况下,这种影响是可以忽略不计的。**
- 数据库默认值**:如果你选择在数据库层面设置默认值(如**
<span class="ne-text">CURRENT_TIMESTAMP</span>
),那么当实体通过纯 SQL 插入或更新时,这些默认值仍然会生效。但在 TypeORM 的生命周期钩子中,你将覆盖这些默认值。
通过自定义实现创建时间和更新时间的功能,你可以获得更大的灵活性,例如根据业务需求调整时间戳的格式或添加额外的逻辑。
7. <span class="ne-text">@OneToMany()</span>
- 作用**:定义一对多关系。**
- 参数**:**
- 第一个参数:关联的实体类。
- 第二个参数(可选):一个回调,用于指定反向关系。
@OneToMany(() => Post, (post) => post.author)
posts: Post[];
8. <span class="ne-text">@ManyToOne()</span>
- 作用**:定义多对一关系。**
- 参数**:**
- 第一个参数:关联的实体类。
- 第二个参数(可选):一个回调,用于指定反向关系。
@ManyToOne(() => User, (user) => user.posts)
author: User;
9. <span class="ne-text">@ManyToMany()</span>
- 作用**:定义多对多关系。**
- 参数**:与
<span class="ne-text">@OneToMany()</span>
和<span class="ne-text">@ManyToOne()</span>
类似,但通常与<span class="ne-text">@JoinTable()</span>
一起使用来定义中间表。**
10. <span class="ne-text">@JoinColumn()</span>
- 作用**:在多对一或一对一关系中,指定外键列。**
- 参数**:可以指定外键列的名称和其他约束。**
@ManyToOne(() => User, (user) => user.posts)
@JoinColumn({ name: 'author_id' })
author: User;
11. <span class="ne-text">@JoinTable()</span>
- 作用**:在多对多关系中,定义中间表。**
- 参数**:可以指定中间表的名称、外键列等。**
12. <span class="ne-text">@Index()</span>
- 作用**:在列上创建索引,以提高查询性能。**
- 参数**:可以指定索引的名称、列的顺序等。**
@Index()
@Column()
username: string;
13. <span class="ne-text">@Unique()</span>
- 作用**:在列或列组合上创建唯一约束。**
- 参数**:可以指定多个列,以创建组合唯一约束。**
@Unique(['username', 'email'])
@Column()
email: string;
14. <span class="ne-text">@Exclude()</span>
(来自 <span class="ne-text">class-transformer</span>
)
- 作用**:虽然不是 TypeORM 的内置装饰器,但常与 TypeORM 一起使用,用于在序列化实体时排除某些属性。**
- 参数**:无参数,直接标记在属性上。**
@Exclude()
@Column()
password: string;
<span class="ne-text">@Column()</span>
装饰器 <span class="ne-text">type</span>
参数
在 TypeORM 中,<span class="ne-text">@Column()</span>
装饰器用于定义实体类的属性如何映射到数据库表的列。<span class="ne-text">type</span>
参数是 <span class="ne-text">@Column()</span>
装饰器中的一个重要选项,用于指定数据库列的数据类型。以下是 <span class="ne-text">type</span>
参数支持的具体类型及其详细说明:
1. 基本数据类型
-
<strong><span class="ne-text">int</span></strong>
/<strong><span class="ne-text">integer</span></strong>
:- 用于存储整数。
- 示例:
<span class="ne-text">@Column('int') age: number;</span>
-
<strong><span class="ne-text">smallint</span></strong>
:- 用于存储小范围整数。
- 示例:
<span class="ne-text">@Column('smallint') smallNumber: number;</span>
-
<strong><span class="ne-text">bigint</span></strong>
:- 用于存储大范围整数。
- 示例:
<span class="ne-text">@Column('bigint') bigNumber: string;</span>
(通常使用字符串来存储大整数,以避免 JavaScript 的 Number 类型精度问题)
-
<strong><span class="ne-text">boolean</span></strong>
:- 用于存储布尔值(true/false)。
- 示例:
<span class="ne-text">@Column('boolean') isActive: boolean;</span>
-
<strong><span class="ne-text">float</span></strong>
/<strong><span class="ne-text">double</span></strong>
/<strong><span class="ne-text">real</span></strong>
:- 用于存储浮点数。
- 示例:
<span class="ne-text">@Column('float') price: number;</span>
-
<strong><span class="ne-text">decimal</span></strong>
/<strong><span class="ne-text">numeric</span></strong>
:- 用于存储精确的小数,可以指定精度和小数位数。
- 示例:
<span class="ne-text">@Column('decimal', { precision: 10, scale: 2 }) amount: number;</span>
-
<strong><span class="ne-text">char</span></strong>
:- 用于存储定长字符串。
- 示例:
<span class="ne-text">@Column('char', { length: 10 }) fixedString: string;</span>
-
<strong><span class="ne-text">varchar</span></strong>
:- 用于存储变长字符串。
- 示例:
<span class="ne-text">@Column('varchar', { length: 255 }) name: string;</span>
-
<strong><span class="ne-text">text</span></strong>
:- 用于存储大文本数据。
- 示例:
<span class="ne-text">@Column('text') description: string;</span>
-
<strong><span class="ne-text">tinyint</span></strong>
:- 用于存储非常小的整数(通常用于布尔值或状态标志)。
- 示例:
<span class="ne-text">@Column('tinyint') status: number;</span>
2. 日期和时间类型
-
<strong><span class="ne-text">date</span></strong>
:- 用于存储日期(年、月、日)。
- 示例:
<span class="ne-text">@Column('date') birthDate: Date;</span>
-
<strong><span class="ne-text">time</span></strong>
:- 用于存储时间(时、分、秒)。
- 示例:
<span class="ne-text">@Column('time') appointmentTime: Date;</span>
-
<strong><span class="ne-text">datetime</span></strong>
/<strong><span class="ne-text">timestamp</span></strong>
:- 用于存储日期和时间。
- 示例:
<span class="ne-text">@Column('datetime') createdAt: Date;</span>
-
<strong><span class="ne-text">timestamptz</span></strong>
(仅在某些数据库如 PostgreSQL 中支持):- 用于存储带时区的时间戳。
- 示例(PostgreSQL):
<span class="ne-text">@Column('timestamptz') updatedAt: Date;</span>
3. 二进制类型
<strong><span class="ne-text">binary</span></strong>
:- 用于存储二进制数据。
- 示例:
<span class="ne-text">@Column('binary') imageData: Buffer;</span>
(在 Node.js 中,通常使用<span class="ne-text">Buffer</span>
类型来处理二进制数据)
4. JSON 类型
-
<strong><span class="ne-text">json</span></strong>
:- 用于存储 JSON 数据。
- 示例:
<span class="ne-text">@Column('json') metadata: any;</span>
(在 TypeScript 中,可以使用<span class="ne-text">any</span>
类型或定义具体的接口来表示 JSON 数据)
-
<strong><span class="ne-text">jsonb</span></strong>
(仅在某些数据库如 PostgreSQL 中支持):- 用于存储 JSONB 数据(二进制 JSON,提供更快的查询性能)。
- 示例(PostgreSQL):
<span class="ne-text">@Column('jsonb') settings: any;</span>
5. 数组类型(仅在某些数据库如 PostgreSQL 中支持)
<strong><span class="ne-text">array</span></strong>
:- 用于存储数组数据,可以与其他类型结合使用。
- 示例(PostgreSQL):
<span class="ne-text">@Column('int', { array: true }) tags: number[];</span>
6. 枚举类型
- 虽然 TypeORM 没有直接的
<span class="ne-text">enum</span>
类型作为<span class="ne-text">@Column()</span>
的<span class="ne-text">type</span>
参数,但可以通过字符串或整数来模拟枚举。 - 示例:
enum Role {
ADMIN = 'admin',
USER = 'user',
}
@Column({
type: 'enum',
enum: Role, // 注意:这里需要数据库支持枚举类型,或者通过字符串存储
default: Role.USER,
})
role: Role;
注意**:不是所有数据库都原生支持枚举类型,有些数据库可能需要通过字符串或整数来模拟。**
7. 自定义类型
- TypeORM 还允许通过自定义类型来扩展
<span class="ne-text">@Column()</span>
的功能。 - 这通常涉及创建自定义的数据库类型映射和转换逻辑。
注意事项
- 在选择
<span class="ne-text">type</span>
参数时,应确保所选类型与数据库支持的类型相匹配。 - 某些类型(如
<span class="ne-text">jsonb</span>
、<span class="ne-text">array</span>
)可能仅在特定数据库(如 PostgreSQL)中支持。 - 对于复杂的数据结构,可以考虑使用 JSON 类型或自定义类型来存储。
通过合理使用 <span class="ne-text">@Column()</span>
装饰器的 <span class="ne-text">type</span>
参数,可以灵活地定义数据库表的结构,以满足不同的业务需求。
增删改查操作
1. 新增(Create)
- 单条插入**:**
const userRepository = connection.getRepository(User);
const user = userRepository.create({
username: 'john',
age: 25,
});
await userRepository.save(user);
- 批量插入**:**
const users = userRepository.create([
{ username: 'user1', age: 21 },
{ username: 'user2', age: 22 },
{ username: 'user3', age: 23 },
]);
await userRepository.save(users);
2. 查询(Read)
- 查询所有**:**
const allUsers = await userRepository.find();
- 条件查询**:**
const youngUsers = await userRepository.find({
where: { age: LessThan(30) },
});
- 多条件查询**:**
const users = await userRepository.find({
where: { age: MoreThan(20), username: Like('%john%') },
});
- 精确匹配**:**
const john = await userRepository.findOne({
where: { username: 'john' },
});
- 模糊查询**:**
const searchUsers = await userRepository.find({
where: { username: Like('%search%') },
});
- 分页查询**:**
const [users, total] = await userRepository.findAndCount({
skip: 0,
take: 10,
order: { age: 'DESC' },
});
- 关联查询**:**
const userWithPosts = await userRepository.find({
relations: { posts: true },
});
- 复杂条件查询**:**
const users = await userRepository
.createQueryBuilder('user')
.where('user.age > :age', { age: 20 })
.andWhere('user.username LIKE :name', { name: '%john%' })
.orderBy('user.age', 'DESC')
.skip(0)
.take(10)
.getMany();
- 统计数量**:**
const count = await userRepository.count({
where: { age: MoreThan(20) },
});
在 TypeORM 中,进行关联查询和多表查询通常涉及使用实体之间的关系(如一对一、一对多、多对多)以及查询构建器(QueryBuilder)来构建复杂的查询。以下是一些常见的方法和示例:
1. 使用关系进行关联查询
TypeORM 支持在实体之间定义关系,如 <span class="ne-text">@OneToOne</span>
、<span class="ne-text">@OneToMany</span>
、<span class="ne-text">@ManyToOne</span>
和 <span class="ne-text">@ManyToMany</span>
。一旦定义了这些关系,就可以使用它们来执行关联查询。
示例:一对多关系查询
假设有两个实体:<span class="ne-text">User</span>
和 <span class="ne-text">Post</span>
,其中 <span class="ne-text">User</span>
可以有多个 <span class="ne-text">Post</span>
。
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne } from '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;
}
要查询用户及其所有帖子,可以使用以下代码:
const userRepository = dataSource.getRepository(User);
const usersWithPosts = await userRepository.find({
relations: { posts: true },
});
console.log(usersWithPosts);
2. 使用 QueryBuilder 进行多表查询
对于更复杂的查询,可以使用 <span class="ne-text">QueryBuilder</span>
。<span class="ne-text">QueryBuilder</span>
提供了更灵活的方式来构建 SQL 查询。
示例:使用 QueryBuilder 进行多表查询
假设你想查询所有用户及其帖子,但只包括标题包含特定关键词的帖子。
const userRepository = dataSource.getRepository(User);
const usersWithFilteredPosts = await userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('post.title LIKE :title', { title: '%keyword%' })
.getMany();
console.log(usersWithFilteredPosts);
3. 多表查询与条件
你可以在 <span class="ne-text">QueryBuilder</span>
中添加多个条件、排序、分组等。
示例:多条件查询
const postRepository = dataSource.getRepository(Post);
const posts = await postRepository
.createQueryBuilder('post')
.innerJoinAndSelect('post.author', 'user')
.where('post.title LIKE :title', { title: '%TypeORM%' })
.andWhere('user.name = :name', { name: 'John' })
.orderBy('post.id', 'DESC')
.getMany();
console.log(posts);
4. 嵌套关系查询
如果实体之间存在嵌套关系(如用户有帖子,帖子有评论),可以递归地加载这些关系。
示例:嵌套关系
假设 <span class="ne-text">Post</span>
实体还有一个 <span class="ne-text">comments</span>
关系:
@Entity()
export class Comment {
@PrimaryGeneratedColumn()
id: number;
@Column()
content: string;
@ManyToOne(() => Post, (post) => post.comments)
post: Post;
}
@Entity()
export class Post {
// ... 其他代码
@OneToMany(() => Comment, (comment) => comment.post)
comments: Comment[];
}
查询用户、帖子和评论:
const usersWithPostsAndComments = await userRepository.find({
relations: { posts: { comments: true } },
});
console.log(usersWithPostsAndComments);
5. 原始 SQL 查询
在某些情况下,你可能需要使用原始 SQL 查询。TypeORM 提供了执行原始 SQL 查询的方法。
const rawResult = await dataSource.query(`
SELECT u.name AS userName, p.title AS postTitle
FROM user u
INNER JOIN post p ON u.id = p.author_id
WHERE p.title LIKE '%TypeORM%'
`);
console.log(rawResult);
总结
- 关系查询**:利用实体之间的关系定义,通过
<span class="ne-text">find</span>
方法的<span class="ne-text">relations</span>
选项加载关联数据。** - QueryBuilder**:用于构建复杂查询,支持多表连接、条件过滤、排序等。**
- 嵌套关系**:递归地加载实体之间的嵌套关系。**
- 原始 SQL**:在需要时执行原始 SQL 查询。**
通过结合使用这些方法,你可以在 TypeORM 中高效地执行关联查询和多表查询。
3. 修改(Update)
- 更新单条数据**:**
await userRepository.update({ id: 1 }, { age: 26 });
- 更新多条数据**:**
await userRepository.update({ age: LessThan(20) }, { age: 20 });
- 使用
<strong><span class="ne-text">save</span></strong>
更新**:**
const user = await userRepository.findOne({ where: { id: 1 } });
user.age = 27;
await userRepository.save(user);
- 使用
<strong><span class="ne-text">QueryBuilder</span></strong>
更新**:**
await userRepository
.createQueryBuilder()
.update(User)
.set({ age: 20 })
.where('age < :age', { age: 20 })
.execute();
4. 删除(Delete)
- 删除单条数据**:**
const user = await userRepository.findOne({ where: { id: 1 } });
await userRepository.remove(user);
- 批量删除**:**
await userRepository.delete({ age: LessThan(30) });
- 使用
<strong><span class="ne-text">QueryBuilder</span></strong>
删除**:**
await userRepository
.createQueryBuilder()
.delete()
.from(User)
.where('age < :age', { age: 30 })
.execute();