实体(Entities)
什么是实体?
实体是映射到数据库表(或在使用MongoDB时映射到集合)的类。
您可以通过定义一个新类并使用@Entity()
标记它来创建实体:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
@Column()
isActive: boolean
}
这将创建以下数据库表:
+-------------+--------------+----------------------------+
| user |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| firstName | varchar(255) | |
| lastName | varchar(255) | |
| isActive | boolean | |
+-------------+--------------+----------------------------+
基本实体由列和关系组成。 每个实体必须具有主列(如果使用MongoDB,则是ObjectId列)。
每个实体必须在数据源选项中注册:
import { DataSource } from "typeorm"
import { User } from "./entity/User"
const myDataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
entities: [User],
})
或者您可以指定包含所有实体的整个目录 - 所有实体都将被加载:
import { DataSource } from "typeorm"
const dataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
entities: ["entity/*.js"],
})
如果要为User
实体设置替代表名,可以在@Entity
中指定:@Entity("my_users")
。
如果要为应用程序中的所有数据库表设置基本前缀,可以在数据源选项中指定entityPrefix
。
在使用实体构造函数时,其参数**必须是可选的。由于ORM在从数据库加载时创建实体类的实例,因此不知道您的构造函数参数。
有关参数@Entity
的详细信息,请参阅装饰器参考。
实体列
由于数据库表由列组成,因此您的实体也必须由列组成。
您使用@Column
标记的每个实体类属性都将映射到数据库表列。
主键列
每个实体必须至少有一个主键列。 有几种类型的主键列:
@PrimaryColumn()
创建一个主键列,可以接受任何类型的值。您可以指定列类型。如果不指定列类型,它将从属性类型推断出来。下面的示例将创建一个带有int
类型的id,您在保存之前必须手动分配其值。
import { Entity, PrimaryColumn } from "typeorm"
@Entity()
export class User {
@PrimaryColumn()
id: number
}
@PrimaryGeneratedColumn()
创建一个主键列,其值将自动生成具有自增值的列。它将创建一个带有auto-increment
/serial
/sequence
/identity
(取决于数据库和提供的配置)的int
列。在保存之前,您不必手动分配其值-值将自动生成。
import { Entity, PrimaryGeneratedColumn } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
}
@PrimaryGeneratedColumn("uuid")
创建一个主键列,其值将自动生成具有uuid
的值。Uuid是一个唯一的字符串标识。在保存之前,您不必手动分配其值-值将自动生成。
import { Entity, PrimaryGeneratedColumn } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn("uuid")
id: string
}
您还可以拥有复合主键列:
import { Entity, PrimaryColumn } from "typeorm"
@Entity()
export class User {
@PrimaryColumn()
firstName: string
@PrimaryColumn()
lastName: string
}
使用save
保存实体时,它始终尝试在数据库中使用给定的实体id(或ids)查找实体。
如果找到id/ids,则将更新数据库中的该行。
如果没有具有id/ids的行,将插入新行。
要通过id查找实体,可以使用manager.findOneBy
或repository.findOneBy
。示例:
// 使用单个主键按id查找
const person = await dataSource.manager.findOneBy(Person, { id: 1 })
const person = await dataSource.getRepository(Person).findOneBy({ id: 1 })
// 使用复合主键按id查找
const user = await dataSource.manager.findOneBy(User, {
firstName: "Timber",
lastName: "Saw",
})
const user = await dataSource.getRepository(User).findOneBy({
firstName: "Timber",
lastName: "Saw",
})
特殊列
有几种带有附加功能的特殊列类型:
@CreateDateColumn
是一个特殊列,它会自动设置为实体的插入日期。 您不需要设置该列-它将自动设置。@UpdateDateColumn
是一个特殊列,每次调用实体管理器或存储库的save
时,它都会自动设置为实体的更新时间。 您不需要设置该列-它将自动设置。@DeleteDateColumn
是一个特殊列,每次调用实体管理器或存储库的软删除时,它都会自动设置为实体的删除时间。 您不需要设置该列-它将自动设置。如果设置了@DeleteDateColumn,则默认范围将是“非删除”。@VersionColumn
是一个特殊列,每次调用实体管理器或存储库的save
时,它都会自动设置为实体的版本(递增数)。 您不需要设置该列-它将自动设置。
空间列
MS SQL、MySQL、MariaDB、PostgreSQL 和 CockroachDB 都支持空间列。TypeORM 对每个数据库的支持略有不同,特别是由于列名在不同数据库之间有所变化。
MS SQL 和 MySQL / MariaDB 的 TypeORM 支持将几何图形以 well-known text (WKT) 的形式提供和接受,因此几何列应该标记为 string
类型。
import { Entity, PrimaryColumn, Column } from "typeorm"
@Entity()
export class Thing {
@PrimaryColumn()
id: number
@Column("point")
point: string
@Column("linestring")
linestring: string
}
...
const thing = new Thing()
thing.point = "POINT(1 1)"
thing.linestring = "LINESTRING(0 0,1 1,2 2)"
TypeORM 的 PostgreSQL 和 CockroachDB 支持使用 GeoJSON 作为交换格式,因此几何列应该标记为 object
或 Geometry
(或其子类,如 Point
),在导入 geojson
类型 后或使用 TypeORM 内置的 GeoJSON 类型。
import {
Entity,
PrimaryColumn,
Column,
Point,
LineString,
MultiPoint
} from "typeorm"
@Entity()
export class Thing {
@PrimaryColumn()
id: number
@Column("geometry")
point: Point
@Column("geometry")
linestring: LineString
@Column("geometry", {
spatialFeatureType: "MultiPoint",
srid: 4326,
})
multiPointWithSRID: MultiPoint
}
...
const thing = new Thing()
thing.point = {
type: "Point",
coordinates: [116.443987, 39.920843],
}
thing.linestring = {
type("LineString",
coordinates: [
[-87.623177, 41.881832],
[-90.199402, 38.627003],
[-82.446732, 38.413651],
[-87.623177, 41.881832],
],
}
thing.multiPointWithSRID = {
type: "MultiPoint",
coordinates: [
[100.0, 0.0],
[101.0, 1.0],
],
}
TypeORM试图尽力处理,但有时无法确定插入的值或PostGIS函数的结果应如何处理为几何值。因此,您可能会编写类似以下代码的代码,其中值从GeoJSON转换为PostGIS的geometry
,并从json
转换为GeoJSON:
import { Point } from "typeorm"
const origin: Point = {
type: "Point",
coordinates: [0, 0],
}
await dataSource.manager
.createQueryBuilder(Thing, "thing")
// 将序列化的GeoJSON转换为具有与表规范相匹配的SRID的几何值
.where(
"ST_Distance(geom, ST_SetSRID(ST_GeomFromGeoJSON(:origin), ST_SRID(geom))) > 0",
)
.orderBy({
"ST_Distance(geom, ST_SetSRID(ST_GeomFromGeoJSON(:origin), ST_SRID(geom)))":
{
order: "ASC",
},
})
.setParameters({
// 将GeoJSON转换为字符串
origin: JSON.stringify(origin),
})
.getMany()
await dataSource.manager
.createQueryBuilder(Thing, "thing")
// 将几何值结果转换为GeoJSON,以JSON格式处理(以便TypeORM知道对其进行反序列化)
.select("ST_AsGeoJSON(ST_Buffer(geom, 0.1))::json geom")
.from("thing")
.getMany()
列类型
TypeORM支持大多数常用的数据库支持的列类型。列类型是特定于数据库类型的,这提供了更大的灵活性,使您的数据库模式看起来更加多样化。
您可以将列类型指定为@Column
的第一个参数,或在@Column
的列选项中指定,例如:
@Column("int")
或者
@Column({ type: "int" })
如果要指定其他类型参数,可以通过列选项来实现。例如:
@Column("varchar", { length: 200 })
或者
@Column({ type: "int", width: 200 })
关于
bigint
类型的注意事项:在SQL数据库中使用的bigint
列类型不适合常规的number
类型,而是将属性映射为string
。
mysql
/ mariadb
的列类型
bit
、int
、integer
、tinyint
、smallint
、mediumint
、bigint
、float
、double
、
double precision
、dec
、decimal
、numeric
、fixed
、bool
、boolean
、date
、datetime
、
timestamp
、time
、year
、char
、nchar
、national char
、varchar
、nvarchar
、national varchar
、
text
、tinytext
、mediumtext
、blob
、longtext
、tinyblob
、mediumblob
、longblob
、enum
、set
、
json
、binary
、varbinary
、geometry
、point
、linestring
、polygon
、multipoint
、multilinestring
、multipolygon
、geometrycollection
、uuid
、inet4
、inet6
注意:UUID、INET4和INET6仅适用于mariadb以及使它们可用的相应版本。
postgres
的列类型
int
、int2
、int4
、int8
、smallint
、integer
、bigint
、decimal
、numeric
、real
、
float
、float4
、float8
、double precision
、money
、character varying
、varchar
、character
、
char
、text
、citext
、hstore
、bytea
、bit
、varbit
、bit varying
、timetz
、timestamptz
、
timestamp
、timestamp without time zone
、timestamp with time zone
、date
、time
、
time without time zone
、time with time zone
、interval
、bool
、boolean
、enum
、point
、line
、
lseg
、box
、path
、polygon
、circle
、cidr
、inet
、macaddr
、tsvector
、tsquery
、uuid
、xml
、
json
、jsonb
、int4range
、int8range
、numrange
、tsrange
、tstzrange
、daterange
、geometry
、
geography
、cube
、ltree
cockroachdb
的列类型
array
、bool
、boolean
、bytes
、bytea
、blob
、date
、numeric
、decimal
、dec
、float
、
float4
、float8
、double precision
、real
、inet
、int
、integer
、int2
、int8
、int64
、
smallint
、bigint
、interval
、string
、character varying
、character
、char
、char varying
、
varchar
、text
、time
、time without time zone
、timestamp
、timestamptz
、timestamp without time zone
、
timestamp with time zone
、json
、jsonb
、uuid
注意:CockroachDB将所有数值数据类型都返回为
string
。但是,如果您省略列类型并将属性定义为number
,ORM将把字符串解析为数字。
sqlite
/ cordova
/ react-native
/ expo
的列类型
int
、int2
、int8
、integer
、tinyint
、smallint
、mediumint
、bigint
、decimal
、numeric
、
float
、double
、real
、double precision
、datetime
、varying character
、character
、
native character
、varchar
、nchar
、nvarchar2
、unsigned big int
、boolean
、blob
、text
、clob
、date
mssql
的列类型
int
、bigint
、bit
、decimal
、money
、numeric
、smallint
、smallmoney
、tinyint
、float
、
real
、date
、datetime2
、datetime
、datetimeoffset
、smalldatetime
、time
、char
、varchar
、
text
、nchar
、nvarchar
、ntext
、binary
、image
、varbinary
、hierarchyid
、sql_variant
、
timestamp
、uniqueidentifier
、xml
、geometry
、geography
、rowversion
oracle
的列类型
char
、nchar
、nvarchar2
、varchar2
、long
、raw
、long raw
、number
、numeric
、float
、dec
、
decimal
、integer
、int
、smallint
、real
、double precision
、date
、timestamp
、
timestamp with time zone
、timestamp with local time zone
、interval year to month
、
interval day to second
、bfile
、blob
、clob
、nclob
、rowid
、urowid
spanner
的列类型
bool
、int64
、float64
、numeric
、string
、json
、bytes
、date
、timestamp
、array
enum
列类型
enum
列类型由postgres
和mysql
支持。有多种可能的列定义方式:
使用TypeScript的枚举:
export enum UserRole {
ADMIN = "admin",
EDITOR = "editor",
GHOST = "ghost",
}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({
type: "enum",
enum: UserRole,
default: UserRole.GHOST,
})
role: UserRole
}
注意:支持字符串、数字和混合类型的枚举。
使用包含枚举值的数组:
export type UserRoleType = "admin" | "editor" | "ghost",
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({
type: "enum",
enum: ["admin", "editor", "ghost"],
default: "ghost"
})
role: UserRoleType
}
set
列类型
set
列类型由mariadb
和mysql
支持。有多种可能的列定义方式:
使用TypeScript的枚举:
export enum UserRole {
ADMIN = "admin",
EDITOR = "editor",
GHOST = "ghost",
}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({
type: "set",
enum: UserRole,
default: [UserRole.GHOST, UserRole.EDITOR],
})
roles: UserRole[]
}
使用包含set
值的数组:
export type UserRoleType = "admin" | "editor" | "ghost",
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({
type: "set",
enum: ["admin", "editor", "ghost"],
default: ["ghost", "editor"]
})
roles: UserRoleType[]
}
simple-array
列类型
有一种特殊的列类型称为simple-array
,它可以将基本数组值存储在单个字符串列中。所有值由逗号分隔。例如:
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column("simple-array")
names: string[]
}
const user = new User()
user.names = ["Alexander", "Alex", "Sasha", "Shurik"]
将以Alexander,Alex,Sasha,Shurik
的形式存储在单个数据库列中。当您从数据库加载数据时,姓名将作为姓名数组返回,就像您存储它们一样。
请注意,您在写入的值中不能有任何逗号。
simple-json
列类型
有一种特殊的列类型称为simple-json
,它可以存储可以通过JSON.stringify存储在数据库中的任何值。当您没有数据库中的JSON类型并且希望存储和加载对象而无需麻烦时,这非常有用。例如:
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column("simple-json")
profile: { name: string; nickname: string }
}
const user = new User()
user.profile = { name: "John", nickname: "Malkovich" }
将以{"name":"John","nickname":"Malkovich"}
的形式存储在单个数据库列中。当您从数据库加载数据时,您将通过JSON.parse获得您的对象/数组/基本值。
生成值的列
您可以使用@Generated
装饰器创建具有生成值的列。例如:
@Entity()
export class User {
@PrimaryColumn()
id: number
@Column()
@Generated("uuid")
uuid: string
}
uuid
值将自动生成并存储到数据库中。
除了"uuid"之外,还有"increment"、"identity"(仅适用于Postgres 10+)和"rowid"(仅适用于CockroachDB)生成类型,但某些数据库平台对于这种类型的生成有一些限制(例如,某些数据库只能有一个增量列,或者某些数据库要求增量是主键)。
列选项
列选项为实体列定义了附加选项。您可以在@Column
中指定列选项:
@Column({
type: "varchar",
length: 150,
unique: true,
// ...
})
name: string;
ColumnOptions
中可用的选项列表:
type: ColumnType
- 列类型。可以是上述列类型之一。name: string
- 数据库表中的列名称。默认情况下,列名称是根据属性的名称生成的。您可以通过指定自己的名称来更改它。length: number
- 列类型的长度。例如,如果要创建varchar(150)
类型,可以指定列类型和长度选项。width: number
- 列类型的显示宽度。仅用于MySQL整数类型。onUpdate: string
-ON UPDATE
触发器。仅在MySQL中使用。nullable: boolean
- 在数据库中将列设置为NULL
或NOT NULL
。默认情况下,列是nullable: false
。update: boolean
- 指示列值是否通过“save”操作进行更新。如果为false,您只能在首次插入对象时写入此值。默认值为true
。insert: boolean
- 指示列值是否在插入对象时设置。默认值为true
。select: boolean
- 定义在进行查询时是否默认隐藏此列。当设置为false
时,标准查询将不显示列数据。默认情况下,列是select: true
。default: string
- 添加数据库级别的列DEFAULT
值。primary: boolean
- 将列标记为主键。与@PrimaryColumn
使用相同。unique: boolean
- 将列标记为唯一列(创建唯一约束)。comment: string
- 数据库列的注释。并非所有数据库类型都支持。precision: number
- 十进制(精确数值)列的精度(仅适用于十进制列),即存储的值的最大位数。用于某些列类型。scale: number
- 十进制(精确数值)列的标度(仅适用于十进制列),表示小数点右侧的数字位数,不能大于精度。用于某些列类型。zerofill: boolean
- 在数字列上添加ZEROFILL
属性。仅适用于MySQL。如果为true
,MySQL会自动将UNSIGNED
属性添加到该列。unsigned: boolean
- 在数字列上添加UNSIGNED
属性。仅适用于MySQL。charset: string
- 定义列的字符集。不适用于所有数据库类型。collation: string
- 定义列的排序规则。enum: string[]|AnyEnum
- 在enum
列类型中用于指定允许的枚举值列表。可以指定值数组或指定枚举类。enumName: string
- 定义所使用枚举的名称。asExpression: string
- 生成的列表达式。仅在MySQL中使用。generatedType: "VIRTUAL"|"STORED"
- 生成的列类型。仅在MySQL中使用。hstoreType: "object"|"string"
-HSTORE
列的返回类型。将值返回为字符串或对象。仅在Postgres中使用。array: boolean
- 用于可以是数组的postgres和cockroachdb列类型(例如int[])。transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType }
- 用于将任意类型EntityType
的属性转换为数据库支持的类型DatabaseType
。也支持数组的转换器,写入时将按照自然顺序应用,并在读取时以相反顺序应用。例如[lowercase, encrypt]
将在写入时先将字符串转换为小写,然后加密,而在读取时解密然后不进行任何操作。
注意:这些列选项大多是特定于关系数据库管理系统(RDBMS)的,不适用于MongoDB
。
实体继承
您可以通过使用实体继承来减少代码中的重复。
例如,您有Photo
、Question
、Post
实体:
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
description: string
@Column()
size: string
}
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
description: string
@Column()
answersCount: number
}
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
description: string
@Column()
viewCount: number
}
正如您所见,所有这些实体都有共同的列:id
、title
、description
。为了减少重复并产生更好的抽象,我们可以为它们创建一个名为Content
的基类:
export abstract class Content {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
description: string
}
@Entity()
export class Photo extends Content {
@Column()
size: string
}
@Entity()
export class Question extends Content {
@Column()
answersCount: number
}
@Entity()
export class Post extends Content {
@Column()
viewCount: number
}
所有来自父实体的列(关系、嵌入等)(父实体也可以扩展其他实体)都将被继承并创建在最终实体中。
树状实体
TypeORM支持使用邻接列表和闭包表模式存储树结构。
邻接列表
邻接列表是一种具有自引用关系的简单模型。 这种方法的好处是简单性,缺点是由于联接限制,无法一次加载大型树结构。 示例:
import {
Entity,
Column,
PrimaryGeneratedColumn,
ManyToOne,
OneToMany,
} from "typeorm"
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@Column()
description: string
@ManyToOne((type) => Category, (category) => category.children)
parent: Category
@OneToMany((type) => Category, (category) => category.parent)
children: Category[]
}
闭包表
闭包表在一个单独的表中以特殊的方式存储父子关系。 它在读取和写入时都非常高效。 要了解有关闭包表的更多信息,请查看Bill Karwin的精彩演示文稿。 示例:
import {
Entity,
Tree,
Column,
PrimaryGeneratedColumn,
TreeChildren,
TreeParent,
TreeLevelColumn,
} from "typeorm"
@Entity()
@Tree("closure-table")
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@Column()
description: string
@TreeChildren()
children: Category[]
@TreeParent()
parent: Category
@TreeLevelColumn()
level: number
}