使用 TypeORM

特性

  • 默認(rèn)支持 TypeScript
  • 我們來打算用 Sequelize.js,發(fā)現(xiàn)他 對 TS 支持不夠好
  • 支持關(guān)聯(lián)(Associations)
  • 支持事務(wù)(Transaction)
  • 支持?jǐn)?shù)據(jù)庫遷移(Migration)

啟動數(shù)據(jù)庫 postgresql

新版 docker(額外)

  • 在項目目錄中創(chuàng)建 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 這句話可以查看容器的運(yùn)行狀態(tài)
  • docker logs 容器id 這句話可以查看啟動日志

驗證 pg

進(jìn)入 docker 容器

  • docker exec -it 容器id bash

進(jìn)入 pg 命令行

  • psql -U blog -W
  • 由于上面沒有設(shè)置密碼,所以直接回車即可
  • 如果需要密碼,可在docker run 選項里的 -e POSTGRES_HOST_AUTH_METHOD=trust 替換成 -e POSTGRES_PASSWORD=123456

一些簡單的命令

  • \l 用于 list databases,目前有一個 blog 數(shù)據(jù)庫
  • \c 用于 connect to a database
  • \d 用于 display
  • \dt 用于 display tables,目前還沒有

創(chuàng)建數(shù)據(jù)庫

用 SQL 來創(chuàng)建數(shù)據(jù)庫

  • CREATE DATABASE xxx ENCODING 'UTF8' LC_COLLATE 'en_US.utf8' LC_CTYPE 'en_US.utf8';
  • 因為 Type ORM 沒有提供單純創(chuàng)建數(shù)據(jù)庫的 API
  • 創(chuàng)建三個數(shù)據(jù)庫:開發(fā)、測試、生產(chǎn)
  • 對應(yīng)英文 blog_development、blog_test、 blog_production
  • 得到三個數(shù)據(jù)庫

安裝 TypeORM

  • 打開官網(wǎng),點擊 Getting Started
  • 安裝該安裝的依賴(typeorm reflect-metadata @types/node pg)
  • 不要使用 Quick Start 里面的 typeorm init 命令,因為他們改寫你的現(xiàn)有項目的文件(后面自己改)
  • 在 tsconfig.json 中加入 "emitDecoratorMetadata": true, "experimentalDecorators": true, 并更改成 "module": "commonjs"
  • 創(chuàng)建 ormconfig.json,并加入內(nèi)容(官網(wǎng)上有)

運(yùn)行 TypeORM

吐槽

  • Next.js 默認(rèn)使用 babel 來將 TS 編譯為JS(內(nèi)置功能)
  • TypeORM 推薦使用 ts-node 來編譯(沒有內(nèi)置)
  • babel 和 ts-node 對 TS 的支持并非完全一致
  • 所以我們必須進(jìn)行統(tǒng)一,全部都用 babel

安裝 babel

  • 安裝 @babel/cli
  • 創(chuàng)建 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 對象,連接數(shù)據(jù)庫成功!

此時項目運(yùn)行流程

  • 統(tǒng)一讓 Next.js 和 TypeORM 使用 babel 翻譯 TS
  • 每次修改 src 的 TS 代碼后,翻譯為 dist 里的 JS
  • 使用 node 運(yùn)行 dist 里的 JS,執(zhí)行 TypeORM 任務(wù)
  • 也可使用 Next.js 執(zhí)行 TypeORM 任務(wù)(后面弄)

重要配置:禁用 sync

ormconfig

  • "synchronize": true => false
  • 如果為 true,那么在連接數(shù)據(jù)庫時,typeorm 會自動根據(jù) entity 目錄來修改數(shù)據(jù)表
  • 假設(shè) entity 里面有 User,就會自動創(chuàng)建 User 表

看起來很方便,為什么要禁用

  • 因為 sync 功能可能會在我們修改User 時直接刪除數(shù)據(jù)
  • 假設(shè)你把 user 表中的 name 字段改為了 nickname,他可能會誤解你刪除了 name,并新增了 nickname,此時表中 name 數(shù)據(jù)已經(jīng)丟失
  • 這種行為絕對不能發(fā)生在生產(chǎn)環(huán)境
  • 所以我們要一開始就杜絕 sync 功能

創(chuàng)建表

posts表

"cli": {
  "migrationsDir": "src/migration"
}
  • npx typeorm migration:create -n CreatePosts
  • 在新生成的文件中 up 方法代表升級數(shù)據(jù)庫,down 代表降級數(shù)據(jù)庫
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
  • 運(yùn)行成功
  • 我們就可以看到數(shù)據(jù)庫中已經(jīng)有 posts 表了

每次都要運(yùn)行 babel 不傻嗎?

  • npx babel --help 可以看到有 -w 選項
  • 這樣我們每次更改文件 babel 就會自動編譯
  • 但是此時我們需要開三個窗口來運(yùn)行我們的項目,第一跑 next dev,第二個跑 babel,第三個輸入當(dāng)前命令
  • 所以有沒有辦法讓第一個窗口和第二個窗口合并呢?
  • Linux / Mac 用戶直接使用 & 即可, next dev & babel -w ....
  • 但是 Windows 不支持(&& 的意思是如果前一個命令成功了,就執(zhí)行下一個命令,此時不適用)
  • 通過搜索關(guān)鍵詞 npm run tasks in paraller
  • 發(fā)現(xiàn) concurrently 可以代替 & 操作,安裝根據(jù)文檔操作即可

數(shù)據(jù)映射到實體

背景

  • 剛剛只是在數(shù)據(jù)庫里創(chuàng)建了 posts,代碼如何讀寫 posts 呢?
  • 答案:將數(shù)據(jù)映射到 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
  • 根據(jù) 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: '修改后的標(biāo)題' })
  • await manager.delete(Post, 1)
  • await manager.findOne(Post, 1)

封裝思路

  • 把所有操作都放在 manager 上
  • 把 Post 類、post1 對象和其他參數(shù)傳給 manager

Repository API

舉例

  • const postRepository = getRepository(Post)
  • await postRepository .findOne(1)
  • await postRepository .save(post)

封裝思路

  • 先通過Post 構(gòu)造一個 repo 對象
  • 這個 repo 對象就只操作 posts 表了

特色

  • TreeRepository 和 MongoRepository
  • 目前用不到這兩個功能,所以就先不用Repo API

總結(jié)

migration 數(shù)據(jù)遷移

  • 用來對數(shù)據(jù)庫升級和降級

entity 實體

  • 用類和對象操作數(shù)據(jù)表和數(shù)據(jù)行

connection 連接

  • 一個數(shù)據(jù)庫連接,默認(rèn)最多 10 個連接
  • 這種模式也叫做連接池,可以參考這篇文章

manager / repo

  • 兩種 API 封裝風(fēng)格,用于操作 entity
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。