夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Node基础

  • 《MySQL》学习笔记

  • Midway

  • Nest

    • 开篇词
    • 学习理由
    • nest概念扫盲
    • 快速掌握 nestcli
    • 5种http数据传输方式
    • IoC 解决了什么痛点问题?
    • 如何调试 Nest 项目
    • Provider注入对象
    • 全局模块和生命周期
    • AOP 架构有什么好处?
    • 一网打尽 Nest 全部装饰器
    • Nest如何自定义装饰器
    • Metadata和Reflector
    • ExecutionContext切换上下文
    • Module和Provider的循环依赖处理
    • 如何创建动态模块
    • Nest和Express,fastify
    • Nest的Middleware
    • RxJS和Interceptor
    • 内置Pipe和自定义Pipe
    • ValidationPipe验证post请求参数
    • 如何自定义 Exception Filter
    • 图解串一串 Nest 核心概念
    • 接口如何实现多版本共存
    • Express如何使用multer实现文件上传
    • Nest使用multer实现文件上传
    • 图书管理系统
    • 大文件分片上传
    • 最完美的 OSS 上传方案
    • Nest里如何打印日志
    • 为什么Node里要用Winston打印日志
    • Nest 集成日志框架 Winston
    • 通过Desktop学Docker也太简单了
    • 你的第一个 Dockerfile
    • Nest 项目如何编写 Dockerfile
    • 提升 Dockerfile 水平的 5 个技巧
    • Docker 是怎么实现的
    • 为什么 Node 应用要用 PM2 来跑?
    • 快速入门 MySQL
    • SQL 查询语句的所有语法和函数
    • 一对一、join 查询、级联方式
    • 一对多、多对多关系的表设计
    • 子查询和 EXISTS
    • SQL 综合练习
    • MySQL 的事务和隔离级别
    • MySQL 的视图、存储过程和函数
    • Node 操作 MySQL 的两种方式
      • 先来看下 mysql2:
        • 连接数据库
        • 我们查询了姓李的顾客有哪些:
        • 比如我们插入一条数据:
        • 把刚才插入的记录改个名字。
        • 再试试删除:
        • 当然,这些 api 也都有 promise 版本。
        • 我们一般都是用连接池来管理:
      • ORM 框架
        • 初始化 typeorm
        • 运行 typeorm
        • 然后数据库里就创建好了表、插入了数据,并且还把它查了出来。
      • 总结
    • 快速掌握 TypeORM
    • TypeORM 一对一的映射和关联 CRUD
    • TypeORM 一对多的映射和关联 CRUD
    • TypeORM 多对多的映射和关联 CRUD
    • 在 Nest 里集成 TypeORM
    • TypeORM保存任意层级的关系
    • 生产环境为什么用TypeORM的migration迁移功能
    • Nest 项目里如何使用 TypeORM 迁移
    • 如何动态读取不同环境的配置?
    • 快速入门 Redis
    • 在 Nest 里操作 Redis
    • 为什么不用 cache-manager 操作 Redis
    • 两种登录状态保存方式:JWT、Session
    • Nest 里实现 Session 和 JWT
    • MySQL + TypeORM + JWT 实现登录注册
    • 基于 ACL 实现权限控制
    • 基于 RBAC 实现权限控制
    • access_token和refresh_token实现无感登录
    • 单token无限续期实现登录无感刷新
    • 使用 passport 做身份认证
    • passport 实现 GitHub 三方账号登录
    • passport 实现 Google 三方账号登录
  • 其他

  • 服务端
  • Nest
神说要有光
2025-03-10
目录

Node 操作 MySQL 的两种方式

前面我们学习了 MySQL 的数据库、表、增删改查等,目的还是在 Node 应用里操作它。

这节我们就来学习下用 mysql2 和 typeorm 两种方式来操作 MysSQL 数据库。

# 先来看下 mysql2:

它是用的最多的连接 mysql 的 npm 包,是 mysql 包的升级版,有更多特性。

我们创建个目录,然后进入这个目录执行 npm init -y 创建 package.json。

然后安装 mysql2

npm install --save mysql2

# 连接数据库

添加这样一个 index.js:

const mysql = require("mysql2");

const connection = mysql.createConnection({
  host: "localhost",
  port: 3306,
  user: "root",
  password: "guang",
  database: "practice",
});

connection.query("SELECT * FROM customers", function (err, results, fields) {
  console.log(results);
  console.log(fields.map((item) => item.name));
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14

连接 mysql server,指定用户名、密码、要操作的数据库(这里要改成你自己的 mysql 连接密码)。

然后通过 query 方法跑一个查询 sql。

results 是结果,fields 是一些元信息,比如字段名这些。

node 执行下:

和我们在 mysql workbench 里执行效果一样。

查询也可以指定占位符:

connection.query(
  "SELECT * FROM customers WHERE name LIKE ?",
  ["李%"],
  function (err, results, fields) {
    console.log(results);
    console.log(fields.map((item) => item.name));
  }
);
1
2
3
4
5
6
7
8

# 我们查询了姓李的顾客有哪些:

当然,增删改也是可以的。

# 比如我们插入一条数据:

connection.execute('INSERT INTO customers (name) VALUES (?)',
    ['光'], (err, results, fields) => {
    console.log(err);
});
1
2
3
4

在 mysql workbench 里可以看到,确实插入了:

再来试下修改。

connection.execute('UPDATE customers SET name="guang" where name="光"', (err) => {
  console.log(err);
});
1
2
3

# 把刚才插入的记录改个名字。

node 跑一下:

点下刷新,可以看到确实修改了:

# 再试试删除:

connection.execute("DELETE  FROM customers where name=?", ["guang"], (err) => {
  console.log(err);
});
1
2
3

执行后数据库中这条记录也删除了:

这就是用 mysql2 做增删改查的方式。

是不是还挺简单的,就和我们在 mysql workbench 里写 sql 差不多。

# 当然,这些 api 也都有 promise 版本。

这样写:

const mysql = require('mysql2/promise');

(async function() {

    const connection = await mysql.createConnection({
        host: 'localhost',
        port: 3306,
        user: 'root',
        password: 'guang',
        database: 'practice'
    });

    const [results, fields] = await connection.query('SELECT * FROM customers');

    console.log(results);
    console.log(fields.map(item => item.name));

})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

结果一样:

这是最基本的使用:需要操作数据库的时候,建立连接,用完之后释放连接。

但这样性能并不高。

因为数据库的连接建立还是很耗时的,而且一个连接也不够用。

# 我们一般都是用连接池来管理:

连接池中放着好几个 mysql 的连接对象,用的时候取出来执行 sql,用完之后放回去,不需要断开连接。

mysql2 自然也封装了连接池的功能。

这样用:

const mysql = require("mysql2/promise");

const pool = mysql.createPool({
  host: "localhost",
  user: "root",
  password: "123456",
  database: "study",
  waitForConnections: true,
  connectionLimit: 10,
  maxIdle: 10,
  idleTimeout: 60000,
  queueLimit: 0,
  enableKeepAlive: true,
  keepAliveInitialDelay: 0,
});

async function query(sql) {
  try {
    const [results] = await pool.promise().query(sql);
    console.dir(results, { depth: null });
  } catch (err) {
    console.error(err);
  }
}

query("SELECT * FROM courses");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

只要把 createConnection 换成 createPool 就好了。query 或者 execute 的时候会自动从 pool 中取 connection 来用,用完会放回去。

或者你也可以手动取:

async function query(sql) {
  pool.getConnection((err, connection) => {
    connection.query(sql, (err, result) => {
      console.log(result);
      //    当不再使用时,归还到连接池中
      connection.release();
    });
  });
}

query("SELECT * FROM courses");
1
2
3
4
5
6
7
8
9
10
11

回过头来再看看这些 option:

connectionLimit 是指定最多有多少个连接,比如 10 个,那就是只能同时用 10 个,再多需要排队等。

maxIdle 是指定最多有多少个空闲的,超过这个数量的空闲连接会被释放。

waitForConnections 是指如果现在没有可用连接了,那就等待,设置为 false 就是直接返回报错。

idleTimeout 是指空闲的连接多久会断开。

queueLimit 是可以排队的请求数量,超过这个数量就直接返回报错说没有连接了。设置为 0 就是排队没有上限。

enableKeepAlive、keepAliveInitialDelay 是保持心跳用的,用默认的就好。

这就是 mysql2 的用法,是不是还会挺简单的?

只要建立个连接或者连接池,就可以在 node 里执行 sql 了。

但是我们一般不会直接这样执行 sql,而是会用 ORM 框架。

# ORM 框架

ORM 是 Object Relational Mapping,对象关系映射。也就是说把关系型数据库的表映射成面向对象的 class,表的字段映射成对象的属性映射,表与表的关联映射成属性的关联。

其实这个想法也很自然,比如我们前面执行的这些 sql:

返回的不就是 js 对象么。

那不如直接操作这个对象,让 ORM 框架自动执行 sql 去同步数据库。

TypeORM 就是一个流行的 ORM 框架。

# 初始化 typeorm

我们来试一下:

npm install typeorm -g
npx typeorm init
1
2

此命令将在MyProject目录中生成一个包含以下文件的新项目:

MyProject
├── src              // TypeScript 代码
│   ├── entity       // 存储实体(数据库模型)的位置
│   │   └── User.ts  // 示例 entity
│   ├── migration    // 存储迁移的目录
│   └── index.ts     // 程序执行主文件
├── .gitignore       // gitignore文件
├── ormconfig.json   // ORM和数据库连接配置
├── package.json     // node module 依赖
├── README.md        // 简单的 readme 文件
└── tsconfig.json    // TypeScript 编译选项
1
2
3
4
5
6
7
8
9
10
11

修改下 data-source.ts

import "reflect-metadata";
import { DataSource } from "typeorm";
import { User } from "./entity/User";

export const AppDataSource = new DataSource({
  type: "mysql",
  host: "localhost",
  port: 3306,
  username: "root",
  password: "guang",
  database: "practice",
  synchronize: true,
  entities: [User],
  migrations: [],
  subscribers: [],
  connectorPackage: "mysql2",
  extra: {
    authPlugin: "sha256_password",
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

用户名密码,要操作的数据库,这些很容易理解。

指定用 mysql2 包来连接,就是我们前面测试的那个。

然后添加一个验证的插件,sha256_password,这个是切换密码的加密方式的,新版本 mysql 改成这种密码加密方式了。

还要手动安装下 mysql2 这个驱动包:

npm install --save mysql2

# 运行 typeorm

然后执行 npm run start。

这时候你会发现 practice 数据库多了个表:

打开看一下:

它还有一条数据。

而且控制台也打印了查询出来的这条数据:

怎么做到的呢?

我们看下代码,有这样一个 entity,通过装饰器声明了主键列和其他的列:

在 index.ts 里创建了一个 User 的对象,调用 save 方法保存这个对象,通过 find 方法查询这个对象:

# 然后数据库里就创建好了表、插入了数据,并且还把它查了出来。

这就是 ORM。

有没有感觉很神奇,它是怎么实现的呢?

我们看下它打印的 sql 就懂了。

加一个 logging 为 true 的选项:

然后去数据库把那个 user 表删掉。

再重新 npm run start:

看到它打印的 CREATE TABLE、INSERT INTO、SELECT 的 sql 语句了么?

这就是 ORM 的实现原理。

它会根据你在 class 的属性上加的装饰器来生成建表 sql。

然后 save 这个 class 的对象,就会执行 insert into 来插入数据。

find 方法会执行 select 来查询数据。

这样,对表的增删改查就变成了对对象的操作。

此外,可以看到每个涉及到修改的 sql 都包了一层事务,这样出了错可以回滚:

typeorm 案例代码 (opens new window) 和 mysql2 案例代码 (opens new window) 都在小册仓库。

# 总结

我们学习了 Node 里操作数据库的两种方式:

一种是直接用 mysql2 连接数据库,发送 sql 来执行。

一种是用 ORM 库,比如 typeorm,它是基于 class 和 class 上的装饰器来声明和表的映射关系的,然后对表的增删改查就变成了对象的操作以及 save、find 等方法的调用。它会自动生成对应的 sql。

主流的方案还是 ORM 的方案,下节我们继续深入学习 typeorm。

编辑 (opens new window)
上次更新: 2025/7/23 18:02:16
MySQL 的视图、存储过程和函数
快速掌握 TypeORM

← MySQL 的视图、存储过程和函数 快速掌握 TypeORM→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式