TypeORM 简单使用
TypeORM
是一个 ORM (Object Relation Mapping) 框架。
支持 Active Record, 和 Data Mapper 模式。
例子
// models
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}
// 逻辑操作
const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.age = 25;
await repository.save(user);
const allUsers = await repository.find();
const firstUser = await repository.findOne(1); // find by id
const timber = await repository.findOne({ firstName: "Timber", lastName: "Saw" });
await repository.remove(timber);
// ActiveRecord
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm";
@Entity()
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}
// ActiveRecord 逻辑操作
const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.age = 25;
await user.save();
const allUsers = await User.find();
const firstUser = await User.findOne(1);
const timber = await User.findOne({ firstName: "Timber", lastName: "Saw" });
await timber.remove();
安装
$ npm install typeorm --save
$ npm install reflect-metadata --save
# 还可以安装 @types/node 增强类型提示
$ npm install @types/node --save
# 根据喜好安装数据库
# MySQL 或 MariaDB
$ npm install mysql --save # 也可以安装 mysql2
# PostgreSQL
$ npm install pg --save
# SQLite
$ npm install sqlite3 --save
# Microsoft SQL Server
$ npm install mssql --save
# sql.js
$ npm install sql.js --save
# Oracle
$ npm install oracledb --save
# 试验性的 MongoDB
$ npm install mogodb --save
// 同时还需要全局导入 reflect-metadata
// e.g. app.ts
import "reflect-metadata"
{
// 需要保证再 tsconfig.json 开启配置 并启用 es6
...
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
...
}
快速开始
仅限于 NodeJS 环境,可以使用 CLI 生成项目。
npm install typrorm -g # 全局安装 CLI
转到项目目录。
# ProjectName 为项目名称
# database 为 使用的数据库 可以是 mysql, mariadb, postgres, sqlite, mssql, oracle, mongodb, cordova, react-native, expo, nativescript.
$ typeorm init --name ProjectName --database mysql
会生成以下目录。
# 省略了一些文件
# 如果在现有 NodeJS 项目运行 可能会覆盖已有文件
ProjectName
|- src
| |- entity -- 实体(数据库模式)目录
| | |- User.ts -- 示例实体
| |- migration -- 存储迁移目录
| |- index.ts -- 入口文件
|- ormconfig.json -- ORM 和数据库连接配置
编辑数据库连接配置。
{
"type": "mysql", // 使用的数据库
"host": "localhost", // 数据库地址
"port": 3306, // 数据库端口
"username": "test", // 数据库用户名
"password": "test", // 数据库用户密码
"database": "test", // 使用的数据库名称
"synchronize": true, // 每次运行时同步
"logging": false, // 日志
"entities": ["src/entity/**/*.ts"], // 实体目录
"migrations": ["src/migration/**/*.ts"], // 存储迁移目录
"subscribers": ["src/subscriber/**/*.ts"] // 订阅器目录
}
使用
import {
Entity, // 实体装饰器
Column, // 列装饰器
PrimaryColumn, // 主键装饰器
PrimaryGeneratedColumn // 自增主键装饰器
} from "typeorm";
// User 模型
@Entity() // 声明当前类作为一个实体
export class User {
// 每个实体必须有一个主键
// @PrimaryColumn()
// 使用 PrimaryGeneratedColumn 创建一个自增主键
@PrimaryGeneratedColumn()
id: number
// 使用 Column 声明一个字段列
// 列类型根据声明类型自动推断
// 也可以手动传入类型
// 需要注意!列类型是特定于数据库的
@Column({
length: 100
})
name: string
@Column("text")
description: string
}
连接数据库
import "reflect-metadata";
import { createConnection } from "typeorm";
import { User } from "./entity/User";
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "test",
entities: [User],
synchronize: true,
logging: false
})
.then(connection => {
// 这里可以写实体操作相关的代码
})
.catch(error => console.log(error));
添加和插入
import { createConnection } from "typeorm";
import { User } from "./entity/User";
createConnection({/** ... */})
// 也可以使用 async/await 语法
.then(connection => {
const user = new User()
user.name = 'n'
user.description = 'd'
return connection
.manager
.save(user)
.then(user => {
console.log(`userid is ${user.id}`)
})
})
实体管理
import { createConnection } from "typeorm";
import { User } from "./entity/User";
createConnection({/** ... */})
.then(async connection => {
// 使用 EntityManager 可以操作当前应用任何实体
let users = await connection.manager.find(User);
console.log("All User from the db: ", users);
})
.catch(error => console.log(error));
存储库
每个实体都有自己的存储库,可以处理实体的所有操作。
当经常处理实体时, 存储库会比实体管理更方便。
import { createConnection } from "typeorm";
import { User } from "./entity/User";
createConnection({/** ... */})
.then(async connection => {
const u = new User()
u.name = 'n'
u.description = 'd'
const repository = connection.getRepository(User)
await repository.save(u)
const allUser = await repository.find()
})
查询操作
// ...
const repository = connection.getRepository(User)
repository.find() // find all
repository.findOne(1) // find first
repository.findOne({name: 'n'}) // find first name eq n
repository.find({name: 'n'}) // find all name eq n
// find all user and count user quantity
const [allUser, userCount] = repository.findAndCount()
// ...
更新操作
// ...
const first = repository.findOne(1)
first.name = 'name'
await repository.save(first)
// ...
删除操作
// ...
const first = repository.findOne(1)
await repository.remove(first)
// ...
关系
一对一
import {
Entity, // 实体装饰器
Column, // 列装饰器
PrimaryColumn, // 主键装饰器
PrimaryGeneratedColumn, // 自增主键装饰器
OneToOne, // 一对一关系装饰器
JoinColumn // 关系外键装饰器
} from "typeorm";
// 一个用户有一个对应的行为记录
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({
length: 100
})
name: string
@Column("text")
description: string
}
// 一个行为记录对应一位用户
@Entity()
export class UserRecord {
@PrimaryGeneratedColumn()
id: number
@Column()
time: Date
@Column()
exec: number
// type 不含内容仅为了可读
// OneToOne 建立了 UserRecord 和 User 之间的一对一关系
@OneToOne(type => User)
// JoinColumn() 表明了实体键的对应关系 关系可单双向,但只有一方为拥有者
// 在此关系的所有者方面需要此装饰器
@JoinColumn()
user: User
}
保存一对一
// ...
const u = User()
u.name = 'n'
u.description = 'd'
const r = UserRecord()
r.time = new Date().toString()
r.exec = 2
// 连接两者
r.user = u
// 获取实体存储库
const uR = connection.getRepository(User)
const uRR = connection.getRepository(UserRecord)
// 首先保存 User
await uR.save(u)
await uRR.save(r)
// ...
反向关系
关系是可单双向的。
目前,UserRecord 可以单方向访问 User,而 User 则不能。因此转换为双向关系。
// user.entity.ts
// ...
export class User {
@OneToOne(
type => UserRecord,
// 用于指定反向关系名称
userRecord => userRecord.user
)
record: UserRecord
}
// user-record.entity.ts
// ...
export class UserRecord {
@OneToOne(
type => User,
user => user.record
)
@JoinColumn()
user: User
}
取出关系对象数据
在一个查询中同时获取 User 和 UserRecord 的数据有两个方法。
find*
和 QueryBuilder
。
// ...
const r = connection.getRepository(User)
// u 为来自数据库的 User 数据列表
// 每项元素都会带有各自的 UserRecord
const u = await r.find({relations: 'record'})
// ...
使用更复杂的查询时,应该使用 QueryBuilder
。
// ...
connection
// QueryBuilder 允许创建和执行几乎任何复杂性的 SQL
// user 和 record 分别为所选的 user 别名
// 可使用别名访问数据列和属性
.getRepository(User)
.createQueryBuilder('user')
.innerJoinAndSelect('user.record', 'record')
.getMany()
// ...
自动保存
在关系设置中设置对应选项,即可在保存其他对象的同时自动保存相关对象。
export class User {
// ...
@OneToOne(
type => UserRecord,
record => record.user,
{
// 开启自动保存
cascade: true
}
)
record: UserRecord
}
多对一
import { Entity, Column, PrimaryGeneratedColumn, OneToMany, ManyToOne, JoinColumn } from "typeorm";
// 拥有者
// 一个用户可以发布多篇文章
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
// 一对多
@OneToMany(
type => Article,
article => article.author
)
articles: Article[]
}
// 被拥有者
// 一篇文章只能有一个作者
@Entity()
export class Article {
@PrimaryGeneratedColumn()
id: number;
// 多对一
@ManyToOne(
type => User,
user => user.articles
)
author: User
}
多对多
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from "typeorm";
// 一篇文章可以有多个标签
@Entity()
export class Article {
@PrimaryGeneratedColumn()
id: number;
@ManyToMany(
type => Tag,
tag => tag.articles
)
@JoinTable() // 指定此处为关系的所有者
tag: Tag[]
}
// 一个标签可以被打给多个文章
@Entity()
export class Tag {
@PrimaryGeneratedColumn()
id: number;
@ManyToMany(
type => Article,
article => article.tag
)
articles: Article[]
}
TypeORM 简单使用
http://localhost:8080/archives/d07681b2-28a8-44fa-aede-77d110a15eaf