这个插件既可以访问普通的 MySQL 数据库,也可以访问基于 MySQL 协议的在线数据库服务。
项目配置- 安装mysql
安装问题:MySQL error 1042: Unable to connect to any of the specified MySQL hosts
解决办法:win+r打开运行,输入services.msc打开服务,找到安装MySQL时添加的服务名,一般8.0版本的MySQL默认是MySQL80,双击打开,登录选项卡下将选择的此账户改为选择本地系统账户。
- 安装对应的插件 egg-mysql :
$ npm i --save egg-mysql复制代码
- 开启插件
// config/plugin.jsexports.mysql = { enable: true, package: 'egg-mysql', };复制代码
- 在 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 提供了两种类型的事务:
- 手动控制
优点: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; }复制代码
- 自动控制: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等都必须有对应的字段