MidwayJS 全栈开发(四)ORM 技术选型

640 阅读8分钟

image.png

前言

上一篇内容回顾:MidwayJS 全栈开发(三)SQL 技术选型

本篇文章将围绕操作数据库的工具 ORM 进行讲解,简单串一下 ORM 的概念以及技术选型,方便我们有个整体的认识。


ORM 介绍

ORM 全称是:Object Relational Mapping(对象关系映射),主要目的是为了帮助我们更快且更简单的访问和操作数据库表

ORM 工作机制

那么它是如何工作的呢?

顾名思义,就是以面向对象的形式与数据库中的字段定义以及数据建立映射,对于数据库的 CRUD 也会提供对应语法映射的可调用函数方法。

这里我们用代码说话,以一个常见场景来举例,新增一个用户信息到表中(假设表名为 users):

  • 使用标准 SQL 语句来实现:
const sql = `INSERT INTO users (id, username, nickname, password)  
VALUES (null, 'test', '不败花丶', 'myMassword');
`; 

// SQLConnection 表示与数据库的连接实例(此处省略声明)
// `.query()` 表示执行 sql 语句
SQLConnection.query(sql, (error, results, fields) => {
  console.log(results); 
});
  • 使用 ORM 来实现:
// `User` 是我们使用 ORM 语法定义的与`users`表关联的类实例(此处省略声明)
// 其中默认内置了很多基础的 SQL 表操作方法
// `.create(object)` 就表示在表中新增一条记录的方法
const results = await User.create({
  username: 'test',
  nickname: '不败花丶',
  password: 'myMassword',
}); 
console.log(results); 

这里我们可以看到,ORM 其实就是使用对象封装了对数据库的访问

  • 使用对象映射:表示数据库表中的数据
  • 使用函数调用:代替数据库表的 CRUD 操作

对于开发者而言,你可能只需要与数据对象进行交互,而无需关心底层数据库的操作。

流程图.jpg

这里,我们再综合对比下 ORM 相对于传统 SQL 写法的优缺点:

ORM 的优点

  • 提高开发效率:只需声明表定义和操作对象,使使用数据库更更简单高效
  • 面向对象编程:语法语句更贴合自身编程语言,更加清晰易懂,让代码更易维护
  • 降低学习成本:一套通吃,底层抽象封装,抹平语法差异,支持不同主流数据库
  • 强大的扩展能力:除了表定义、映射 CRUD,市面上绝大多数 ORM 工具还支持预处理、事务、字段名修改等自动化功能

ORM 的缺点

  • 小性能代价:ORM 映射实现是以性能为代价的,提高了开发效率,降低了系统性能
  • 不适用复杂SQL查询:对于太复杂的查询,ORM 可能无法表达,要么可能性能极差

小结

ORM 其实就是对象到 SQL 的映射(当然有些 ORM 工具也提供有 SQL 到对象的反向映射)。使用它,可以让数据库操作更加简单。

虽然 ORM 可以提高了我们的开发效率,但是有一定的性能开销。所以一定要合理权衡,普通业务场景可能比较适合,但是对性能要求极高,或者复杂查询场景,可能就得头疼了。

值得一提,ORM 只是一个辅助工具,对于原生 SQL 的学习和使用,作为后端来讲是不可或缺的。比如:使用 ORM 对某个表数据查询耗时较长,你怎么排查、怎么优化呢。


ORM 选型对比

目前 NodeJS 开源社区比较流行的 ORM 大概有这么几个:Sequelize-typescript、TypeORM、Prisma,这里我们逐一进行介绍。

image.png

Sequlize

基本介绍

Sequlize 是目前比较老牌且使用最多的 ORM 工具。目前大多使用 Sequlize-TypeScript 版本,支持类装饰器定义表数据模型,外加类型智能提示,使用更简单。

核心特性

  • 基础能力:模型定义、查询、事务、钩子等
  • 强大的 CLI:帮助快速初始化、sql迁移等
  • 支持 RAW 查询:可以自己编写执行原始的 SQL 语句
  • 连接无感:在初始化 ORM 配置用户名密码即可,运行时执行自动连接
  • 支持 Typescript:但是支持时间上比较落后,被 TypeORM 抢占先机了
  • 支持主流数据库:PostgreSQL、MySQL、SQLite 等
  • 社区强大且成熟:非常古老受欢迎,社区资源也非常多

简单示例

这里我们使用 Sequlize-TypeScript 实现一个简单的场景示例:定义一个极简的用户表,并初始化连接数据库,并往表中插入数据。

  1. 创建与数据库的连接、初始化模型
// sequelize.ts  *******************************

import { Sequelize } from "sequelize-typescript";
import { User } from './user.module'

// 创建连接实例
export const sequelize = new Sequelize(
  dialect: 'postgres',
  username: "username",
  password: "password",
  database: "database_name",
);

// 初始化数据模型
sequelize.addModels([User]);
  1. 定义用户表模型
// user.module.ts  *******************************

import {
  Table,
  Column,
  Model,
  AutoIncrement,
  PrimaryKey,
  AllowNull,
  DataType,
  Unique,
} from "sequelize-typescript";

/**
 * 用户表模型
 */
@Table({ tableName: 'users' })
export class User extends Model<User> {
  @AutoIncrement
  @PrimaryKey
  @Column
  id: number;

  @AllowNull(false)
  @Unique
  @Column(DataType.STRING(32))
  username: string;

  @AllowNull(false)
  @Column(DataType.STRING(128))
  password: string;
}
  1. 往表中插入数据
// demo.ts  *******************************

import { sequelize } from './sequelize'
import { User } from './user.module'

// 进行认证连接数据库(一般在应用入口时调用)
await sequelize.authenticate();

// 请求时,往表中插入数据(在接口请求时被调用)
const results = await User.create({ name: '不败花丶' })
console.log(results)

Typeorm

基本介绍

一个完全基于 TypeScript 的 ORM 框架,对 TS 的高度支持。它的目标是始终支持最新的 JavaScript 特性并提供额外的特性以帮助你开发任何使用数据库的应用。

核心特性

  • Sequelize 该有的基本都有
  • 双模式支持:支持 ActiveRecord(数据模型和CRUD方法在单独类中,需要继承 BaseEntity 基类,写法类同 sequelize 继承 Model 一样)和 DataMapper 模式(数据模型和CRUD方法分开解耦)
  • 支持更多数据库:底层支持非关系数据库 MongoDB,这是 Sequlize 目前没有的

简单示例

这里我们使用 Data Mapper 的方式(Data Mapper 可以帮助你保持软件的可维护性,这在更大的应用程序中将更有效)来写一下示例。

  1. 创建与数据库的连接、初始化模型
// typeorm.ts  *******************************

import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from './user.module'

// 创建连接实例
export const dataSource = new DataSource({
  type: "postgres",
  username: "username",
  password: "password",
  database: "database_name",
  entities: [User],
})
  1. 定义用户表模型
// user.module.ts  *******************************

import { Entity, PrimaryGeneratedColumn, Column, Unique } from "typeorm";
import { dataSource } from './typeorm'

/**
 * 用户表模型
 */
@Entity('users')
@Unique(['username'])
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  username: string;
}

// Data Mapper 模式下,获取实体类的 CRUD 的辅助方法
export const userRepository = dataSource.getRepository(User)
  1. 往表中插入数据
// demo.ts  *******************************

import { dataSource } from './typeorm'
import { User } from './user.module'

// 进行认证连接数据库(一般在应用入口时调用)
await dataSource.initialize()

// 请求时,往表中插入数据(在接口请求时被调用)
const user = new User();
user.username = "不败花丶";

await userRepository.save(user);

Prisma

基本介绍

Prisma 自称是下一代 Node.js 和 TypeScript 的 ORM。通过 PrismaClient 使用的通用数据库抽象取代了传统的 ORM 和自定义数据访问层。用于构建 GraphQL 服务器、 REST API 等。

核心特性

  • 基本都支持
  • 迁移可控:执行 cli 命令进行数据库表变更或迁移,并附带生成sql语句文件及变更日志等信息,方便在生产率和控制力之间自主把握平衡
  • 类型安全:查询到的数据类型都是严格且安全的(不会出现像 TypeORM 联合查询结果字段类型非可选但是使用时却 undefined的情况)
  • 结果干净:数据库查询是返回普通 JavaScript 的对象,不带额外数据方法等
  • Prisma client:支持多客户端连接,能够更好的适应不同技术,包括 NextJS、GraphQL、NestJS、传统 ResAPI、Apollo 等
  • Prisma Studio:现代化的用户界面查看和编辑数据
  • 拥有全职维护团队:对 Issue 支持修复相对更及时

简单示例

和上述两者不同,Prisma 使用自己的一套文件语法(schema.prisma),用来描述数据库相关的配置和表数据模型。 最后,执行 cli 命令就能生成 sql 文件(可以直接在数据库中执行的语句,具备可迁移性),并且自动帮我们创建好表 TABLE

  1. 定义用户表模型 & 数据库连接配置(二合一)

首先,在项目内执行 prisma init 创建如下标准模板,并完成相应数据库连接配置和表模型定义。

// schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = "postgresql://username:password@localhost:3306/database_name"
}

model User {
  id       Int    @id @default(autoincrement())
  username String @unique
}
  1. 执行 CLI 命令

scheme.prisma 定义好之后,可以执行以下命令,完成表的创建以及实体(User) TS 类型文件的生成,方便开发者享受 TS 体验。

prisma migrate dev
  1. 往表中插入数据
import { PrismaClient } from '@prisma/client'

export const prisma = new PrismaClient()

// 请求时,往表中插入数据(在接口请求时被调用)
const user = await prisma.user.create({
  data: { 
    username: '不败花丶', // 有代码提示哦
  }
})

小结

本小节简单介绍了不同 ORM 工具的概念和使用。除了 prisma 数据模型定义有点不一样,使用自己的一套语法之外,基本使用上都大同小异,尤其 Sequlize 和 TypeORM 之间。

  • 对于业务生产使用,我们建议使用 Sequlize 和 TypeORM,尤其 TypeORM 的 Data Mapper 模式值得推荐。
  • 对于 Prisma,非常有可能成为未来的趋势,我打算在本系列课程中尝试使用,感兴趣可以看后面的章节。

全文总结

本篇主要介绍了数据库访问操作的常用工具 ORM,以及社区几种比较流行的 ORM 工具,方便大家选择。

那么,对于 SQL 还不熟悉的初学者而言,它是非常适合快速上手实战的,但不要形成依赖!什么时候使用它也需要合理权衡

  • 业务复杂度:简单 vs 复杂
  • 需求场景:效率 vs 性能

值得一提的是,但是要明确 ORM 只是一个工具,作为全栈,学习标准的 SQL 语法并学习一门实用的关系数据库管理系统,还是非常有必要的