Skip to content

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

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

先来看下 mysql2:

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

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

然后安装 mysql2

npm install --save mysql2 

添加这样一个 index.js:

javascript
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)); 
  }
);

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

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

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

node 执行下:

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

查询也可以指定占位符:

javascript
connection.query(
    'SELECT * FROM customers WHERE name LIKE ?',
    ['李%'],
    function(err, results, fields) {
        console.log(results);
        console.log(fields.map(item => item.name)); 
    }
);

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

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

比如我们插入一条数据:

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

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

再来试下修改。

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

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

node 跑一下:

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

再试试删除:

javascript
connection.execute('DELETE  FROM customers where name=?',
    ['guang'],
    (err) => {
        console.log(err);
    });

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

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

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

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

这样写:

sql
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)); 

})();

结果一样:

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

但这样性能并不高。

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

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

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

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

这样用:

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

(async function() {
    const pool = mysql.createPool({
        host: 'localhost',
        user: 'root',
        password: 'guang',
        database: 'practice',
        waitForConnections: true,
        connectionLimit: 10,
        maxIdle: 10, 
        idleTimeout: 60000,
        queueLimit: 0,
        enableKeepAlive: true,
        keepAliveInitialDelay: 0
      });

    const [results] = await pool.query('select * from customers');
    console.log(results);
})();

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

或者你也可以手动取:

javascript
const connection = await pool.getConnection();

const [results] = await connection.query('select * from orders');
console.log(results);

回过头来再看看这些 option:

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

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

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

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

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

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

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

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

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

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

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

返回的不就是 js 对象么。

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

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

我们来试一下:

新建一个项目:

npx typeorm@latest init --name typeorm-mysql-test --database mysql

通过 typeorm 的 init 命令创建一个项目, --name 指定项目名,--database 指定连接的数据库。

修改下 data-source.ts

typescript
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',
    }
})

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

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

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

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

npm install --save mysql2

然后执行 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 案例代码mysql2 案例代码 都在小册仓库。

总结

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

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

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

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