使用 TypeORM

特性

  • 默认支持 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表

"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 表了

特色

  • TreeRepository 和 MongoRepository
  • 目前用不到这两个功能,所以就先不用Repo API

总结

migration 数据迁移

  • 用来对数据库升级和降级

entity 实体

  • 用类和对象操作数据表和数据行

connection 连接

  • 一个数据库连接,默认最多 10 个连接
  • 这种模式也叫做连接池,可以参考这篇文章

manager / repo

  • 两种 API 封装风格,用于操作 entity
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349