美文网首页
使用 TypeORM

使用 TypeORM

作者: littleyu | 来源:发表于2020-07-22 21:08 被阅读0次

    特性

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

    相关文章

      网友评论

          本文标题:使用 TypeORM

          本文链接:https://www.haomeiwen.com/subject/oznrkktx.html