这个插件既可以访问普通的 MySQL 数据库,也可以访问基于 MySQL 协议的在线数据库服务。

项目配置
  1. 安装mysql

安装问题:MySQL error 1042: Unable to connect to any of the specified MySQL hosts

解决办法:win+r打开运行,输入services.msc打开服务,找到安装MySQL时添加的服务名,一般8.0版本的MySQL默认是MySQL80,双击打开,登录选项卡下将选择的此账户改为选择本地系统账户。

  1. 安装对应的插件 egg-mysql :
$ npm i --save egg-mysql复制代码
  1. 开启插件
// config/plugin.jsexports.mysql = {  enable: true,  package: 'egg-mysql',
};复制代码
  1. 在 config/config.${env}.js 配置各个环境的数据库连接信息
 config.mysql = {client: {      host: '127.0.0.1',      port: '3306',      user: 'root',      password: '12345678',      database: 'exam',
    },// 是否加载到 app 上,默认开启app: true,// 是否加载到 agent 上,默认关闭agent: false,
  };复制代码
启动项目碰到的问题

问题1. Client does not support authentication protocol requested by server; consider upgrading MySQL client

> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '12345678';

> flush privileges;复制代码

问题2. Access denied for user'root'@'localhost' (using password: NO)

因为在config.mysql配置中没有设密码,设置就对了

对数据库的操作

get 查询

  • 查询单条
const user = await this.app.mysql.get('data_person',{title: 'Hello World'});复制代码
  • 查询全表
const user = await this.app.mysql.get('data_person');复制代码

测试得到的也是匹配到的第一条数据

select 有条件的查询多条数据,他的条件只支持=和in,所以用query更方便

 const user = await this.app.mysql.select('data_person', { // 搜索 post 表
  where: { title: 'Hello World',  age: [10, 11]  }, // WHERE 条件
  columns: ['title'], // 要查询的表字段
  orders: [['person_id','desc']], // 排序方式
  limit: 10, // 返回数据量
  offset: 0, // 数据偏移量});  

=> SELECT `author`, `title` FROM `posts`
  WHERE `title` = 'Hello World' AND `age` IN(10,11)
  ORDER BY `person_id` DESC,  LIMIT 0, 10;复制代码

insert插入数据

await this.app.mysql.insert('data_person', { title: 'Hello World' });复制代码

update 更新数据

修改数据,将会根据主键查找,并更新。否则会报错

报错:Can not auto detect update condition, please set options.where, or make sure exists

// 修改数据,将会根据主键 ID 查找,并更新const row = {  id: 6,  title: '不是主键',  create_time: this.app.mysql.literals.now,
};const result = await this.app.mysql.update('posts', row); 

=> UPDATE `data_person` SET `title` = '不是主键', `create_time` = NOW() WHERE id = 6 ;复制代码
const row = {  title: '更新主键',  create_time: this.app.mysql.literals.now, 
};// 如果主键是自定义的 ID 名称,如 person_id,则需要在 `where` 里面配置const options = {  where: {person_id: '6'
  }
};const result = await this.app.mysql.update('data_person', row, options); 

=> UPDATE `data_person` SET `title` = '更新主键', `create_time` = NOW() WHERE person_id = '6' ;// 判断更新成功const updateSuccess = result.affectedRows === 1;复制代码

delete 删除数据

const result = await this.app.mysql.delete('data_person', {  person_id: '5'});

=> DELETE FROM `data_person` WHERE `person_id` = '5';//把所有person_id为'5'的都删除复制代码

query 直接执行sql语句 ,为了防止sql注入,采用这种每个?匹配一个元素的方式

const personId = '6';const results = await this.app.mysql.query('update data_person set age = (age + ?) where person_id = ?', [5, personId]);

=> update data_person set age = (age + 5) where person_id = '6';复制代码

transaction

MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等。这时候使用事务处理可以方便管理这一组操作。 个事务将一组连续的数据库操作,放在一个单一的工作单元来执行。该组内的每个单独的操作是成功,事务才能成功。如果事务中的任何操作失败,则整个事务将失败。

支持事务条件:在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表。

一般来说,事务是必须满足4个条件(ACID): Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(可靠性)

  • 原子性:确保事务内的所有操作都成功完成,否则事务将被中止在故障点,以前的操作将回滚到以前的状态。
  • 一致性:对于数据库的修改是一致的。
  • 隔离性:事务是彼此独立的,不互相影响
  • 持久性:确保提交事务后,事务产生的结果可以永久存在。

因此,对于一个事务来讲,一定伴随着 beginTransaction、commit 或 rollback,分别代表事务的开始,成功和失败回滚。

egg-mysql 提供了两种类型的事务:

  1. 手动控制

优点:beginTransaction, commit 或 rollback 都由开发者来完全控制,可以做到非常细粒度的控制。

缺点:手写代码比较多,不是每个人都能写好。忘记了捕获异常和 cleanup 都会导致严重 bug。

const conn = await app.mysql.beginTransaction(); // 初始化事务try {  await conn.insert(table, row1);  // 第一步操作
  await conn.update(table, row2);  // 第二步操作
  await conn.commit(); // 提交事务} catch (err) {  // error, rollback
  await conn.rollback(); // 一定记得捕获异常后回滚事务!!
  throw err;
}复制代码
  1. 自动控制:Transaction with scope
API:beginTransactionScope(scope, ctx)复制代码

scope: 一个 generatorFunction,在这个函数里面执行这次事务的所有 sql 语句。

ctx: 当前请求的上下文对象,传入 ctx 可以保证即便在出现事务嵌套的情况下,一次请求中同时只有一个激活状态的事务。

优点:使用简单,不容易犯错,就感觉事务不存在的样子。

缺点:整个事务要么成功,要么失败,无法做细粒度控制。

const result = await app.mysql.beginTransactionScope(async conn => {  // don't commit or rollback by yourself
  await conn.insert(table, row1);  await conn.update(table, row2);  return { success: true };
}, ctx); // ctx 是当前请求的上下文,如果是在 service 文件中,可以从 `this.ctx` 获取到// if error throw on scope, will auto rollback复制代码

内置表达式(Literal)和自定义表达式(Literal)

如果需要调用 MySQL 内置的函数(或表达式),可以使用 Literal。

  • 内置

NOW():数据库当前系统时间,通过 app.mysql.literals.now 获取。

 await this.app.mysql.insert('data_person', {person_id: '8',create_time: this.app.mysql.literals.now,
  });

=> INSERT INTO `data_person`(`person_id`,`create_time`) VALUES('8',NOW())复制代码
  • 自定义

下例展示如何调用 MySQL 内置的 CONCAT(s1, …sn) 函数,做字符串拼接。

const Literal = this.app.mysql.literals.Literal;const first = "he";const last = "hannie";await this.app.mysql.insert('data_person', {  person_id: '7',  title: new Literal(`CONCAT("${first}", "${last}")`),  //注意语法,别少括号等,测的时候找了半天才debug到});

=> INSERT INTO `data_person`(`person_id`, `title`) VALUES(7, CONCAT("he", "hannie"))复制代码

总结

执行insert、update、delete、slect等都必须有对应的字段