特性
- 默认支持 TypeScript
- 我们来打算用 Sequelize.js,发现他 对 TS 支持不够好
- 支持关联(Associations)
- 支持事务(Transaction)
- 支持数据库迁移(Migration)
启动数据库 postgresql
新版 docker(额外)
- 在项目目录中创建 blog-data 目录
- .gitignore 里添加 /blog-data/
启动 PostgreSQL
- 一句话启动
- 新版:
docker run -v "$PWD/blog-data":/var/lib/postgresql/data -p 5432:5432 -e POSTGRES_USER=blog -e POSTGRES_HOST_AUTH_METHOD=trust -d postgres:12.2
- 旧版:
docker run -v "blog-data":/var/lib/postgresql/data -p 5432:5432 -e POSTGRES_USER=blog -e POSTGRES_HOST_AUTH_METHOD=trust -d postgres:12.2
- docker ps -a 这句话可以查看容器的运行状态
- docker logs 容器id 这句话可以查看启动日志
验证 pg
进入 docker 容器
- docker exec -it 容器id bash
进入 pg 命令行
- psql -U blog -W
- 由于上面没有设置密码,所以直接回车即可
- 如果需要密码,可在docker run 选项里的 -e POSTGRES_HOST_AUTH_METHOD=trust 替换成 -e POSTGRES_PASSWORD=123456
一些简单的命令
- \l 用于 list databases,目前有一个 blog 数据库
- \c 用于 connect to a database
- \d 用于 display
- \dt 用于 display tables,目前还没有
创建数据库
用 SQL 来创建数据库
CREATE DATABASE xxx ENCODING 'UTF8' LC_COLLATE 'en_US.utf8' LC_CTYPE 'en_US.utf8';
- 因为 Type ORM 没有提供单纯创建数据库的 API
- 创建三个数据库:开发、测试、生产
- 对应英文 blog_development、blog_test、 blog_production
- 得到三个数据库
安装 TypeORM
- 打开官网,点击 Getting Started
- 安装该安装的依赖(typeorm reflect-metadata @types/node pg)
- 不要使用 Quick Start 里面的 typeorm init 命令,因为他们改写你的现有项目的文件(后面自己改)
- 在 tsconfig.json 中加入
"emitDecoratorMetadata": true, "experimentalDecorators": true,
并更改成 "module": "commonjs"
- 创建 ormconfig.json,并加入内容(官网上有)
运行 TypeORM
吐槽
- Next.js 默认使用 babel 来将 TS 编译为JS(内置功能)
- TypeORM 推荐使用 ts-node 来编译(没有内置)
- babel 和 ts-node 对 TS 的支持并非完全一致
- 所以我们必须进行统一,全部都用 babel
安装 babel
- 安装 @babel/cli
- 创建 src/index.ts
// index.ts
import "reflect-metadata";
import {createConnection} from 'typeorm';
createConnection().then(async connection => {
console.log(connection)
await connection.close()
}).catch(error => console.log(error));
- npx babel ./src --out-dir dist --extensions ".ts,.tsx"
- node dist/index.js
- 控制台成功打印出 connection 对象,连接数据库成功!
此时项目运行流程
- 统一让 Next.js 和 TypeORM 使用 babel 翻译 TS
- 每次修改 src 的 TS 代码后,翻译为 dist 里的 JS
- 使用 node 运行 dist 里的 JS,执行 TypeORM 任务
- 也可使用 Next.js 执行 TypeORM 任务(后面弄)
重要配置:禁用 sync
ormconfig
- "synchronize": true => false
- 如果为 true,那么在连接数据库时,typeorm 会自动根据 entity 目录来修改数据表
- 假设 entity 里面有 User,就会自动创建 User 表
看起来很方便,为什么要禁用
- 因为 sync 功能可能会在我们修改User 时直接删除数据
- 假设你把 user 表中的 name 字段改为了 nickname,他可能会误解你删除了 name,并新增了 nickname,此时表中 name 数据已经丢失
- 这种行为绝对不能发生在生产环境
- 所以我们要一开始就杜绝 sync 功能
创建表
posts表
- 使用命令行来创建
- 首先在 ormconfig 中加入以下代码,控制文件生成的目录
"cli": {
"migrationsDir": "src/migration"
}
- npx typeorm migration:create -n CreatePosts
- 在新生成的文件中 up 方法代表升级数据库,down 代表降级数据库
import {MigrationInterface, QueryRunner, Table} from 'typeorm';
export class CreatePosts1595341120888 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(new Table({
name: 'posts',
columns: [
{name: 'id', isGenerated: true, type: 'int', isPrimary: true, generationStrategy: "increment"},
{name: 'title', type: 'varchar'},
{name: 'content', type: 'text'},
{name: 'author_id', type:'int'}
]
}))
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('posts')
}
}
- npx babel ./src --out-dir dist --extensions ".ts,.tsx"
- 由于我们使用 babel,与 TypeORM 官方建议用的 ts-node 不一样,所以我们还需要修改 ormconfig.json 文件,把 entities、migrations、subscribers,里面的路径都替换为 dist/xxx/*/.js
"entities": [
"dist/entity/**/*.js"
],
"migrations": [
"dist/migration/**/*.js"
],
"subscribers": [
"dist/subscriber/**/*.js"
],
- npx typeorm migration:run
- 运行成功
- 我们就可以看到数据库中已经有 posts 表了
每次都要运行 babel 不傻吗?
- npx babel --help 可以看到有 -w 选项
- 这样我们每次更改文件 babel 就会自动编译
- 但是此时我们需要开三个窗口来运行我们的项目,第一跑 next dev,第二个跑 babel,第三个输入当前命令
- 所以有没有办法让第一个窗口和第二个窗口合并呢?
- Linux / Mac 用户直接使用 & 即可,
next dev & babel -w ....
- 但是 Windows 不支持(&& 的意思是如果前一个命令成功了,就执行下一个命令,此时不适用)
- 通过搜索关键词 npm run tasks in paraller
- 发现 concurrently 可以代替 & 操作,安装根据文档操作即可
数据映射到实体
背景
- 刚刚只是在数据库里创建了 posts,代码如何读写 posts 呢?
- 答案:将数据映射到 Entity(实体)
- 和 migration 一样首先在 ormconfig 中加入以下代码,控制文件生成的目录
"cli": {
"entitiesDir": "src/entity",
}
- npx typeorm entity:create -n Post
import {Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn} from 'typeorm';
@Entity('posts')
export class Post {
@PrimaryGeneratedColumn('increment')
id: number;
@Column('varchar')
title: string;
@Column('text')
content: string;
@Column('int')
authorId: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
- 编译时遇到一个报错
syntax 'decorators-legacy'
- 搜索以后,安装
yarn add -D @babel/plugin-proposal-decorators
- 根据 Next.js 的要求,新建 .babelrc 文件,并加入上面安装的插件
{
"presets": [
"next/babel"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
知识点
- @PrimaryGeneratedColumn('increment') // 自增主键
- @Column('varchar') // varchar 类型
- @Column('text') // text 类型
如何使用实体
EntityManager API
举例
- await manager.find(Post, { title: '第一篇博客' })
- await manager.create(Post, { title: '.....' })
- await manager.save(post1)
- await manager.save([post1, post2, post3])
- await manager.remove(post1)
- await manager.update(Post, 1, { title: '修改后的标题' })
- await manager.delete(Post, 1)
- await manager.findOne(Post, 1)
封装思路
- 把所有操作都放在 manager 上
- 把 Post 类、post1 对象和其他参数传给 manager
Repository API
举例
- const postRepository = getRepository(Post)
- await postRepository .findOne(1)
- await postRepository .save(post)
封装思路
- 先通过Post 构造一个 repo 对象
- 这个 repo 对象就只操作 posts 表了
特色
总结
migration 数据迁移
entity 实体
connection 连接
- 一个数据库连接,默认最多 10 个连接
- 这种模式也叫做连接池,可以参考这篇文章
manager / repo
网友评论