这个插件既可以访问普通的 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.js
exports.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 obj.id 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等都必须有对应的字段