为什么选择 mysql
市面上流行的数据库很多,在稳定性、性能、拓展性等方面有各自的优势,按照数据组织方式区分与两类
- 关系型数据库:由二维表及其之间的联系组织数据
- mysql/mariadb
- Oracle
- SQL Server
- DB2
- PostgreSQL
- NoSQL:利用文档、键值对等对数据进行组织
- mongoDB
- Hypertable
- Hbase
- redis/memcache
- CouchDB
- Neo4j
两者在不同领域都有广泛的应用,并且在互相学习,但关系型数据是理解数据库的基础,因为先发优势在各企业应用中普及度比 NoSQL 高很多,mysql 免费、稳定、高性能的特性让其成为最流行的关系型数据库
mysql 安装
mysql 程序可以在官网下载安装,Mac 用户可以使用 homebrew 安装
brew install mysql
安装完成后可以在命令行工具启动、操作 mysql 数据库,基础操作可以参考 MySQL入门——看这一篇就够了,但命令行操作比较复杂,可以使用图形化工具管理 mysql 数据库,对于简单的管理 mysql 官方提供的 workbench 和 phpMyAdmin 等选择很多
环境准备
- 使用 root 登录,创建一个普通用户
sunluyong
,赋予用户操作数据库的权限(一般不会在程序中使用 root 账户) - 使用普通账户登录,创建 schema(数据库)
demo
,后面的示例需要连接此数据库 - 在 demo 数据库中创建 Table
user
user 表的结构非常简单CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT ''用户 ID,主键'',
`name` varchar(50) NOT NULL COMMENT ''用户名称'',
`config` tinytext COMMENT ''用户配置'',
`deleted` int unsigned NOT NULL DEFAULT ''0'' COMMENT ''记录是否被删除'',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ''创建时间'',
`updated_at` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT ''最后修改时间'',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
id | 自增长数字,用户 ID,做为表的主键 |
---|---|
name | 用户名 |
config | 用户的一些配置信息,可以为空 |
deleted | 该条记录是否被软删除,0: 未删除 1: 已删除 |
created_at | 本条数据的创建时间,数据库自动插入 CURRENT_TIMESTAMP |
updated_at | 最后一次修改时间,每次该条数据 update 时候数据库自动更新 |
环境准备好后就可以在 egg.js 中连接数据库,进行数据的 CRUD 操作了
配置插件
在 egg.js 中操作 mysql 需要使用 egg-mysql 插件,配置方式和模板引擎插件的方式一致
1. 安装插件
npm i --save egg-mysql
2. 开启插件
修改 config/plugin.js
exports.mysql = {
enable: true,
package: 'egg-mysql',
};
3. 数据库连接配置
修改 config/config.default.js
config.mysql = {
client: {
host: 'localhost',
port: '3306',
user: 'sunluyong',
password: '123456',
database: 'demo',
},
// 在 this.app 上挂载 mysql 对象
app: true,
};
在代码中写明文密码是极度危险的处理方式,直接配置是为了 demo 方便,线上需要使用加密手段
多环境配置
完成上面的配置就可以在本地测试了,但很多时候本地测试数据库和线上数据库并不一致,egg.js 通过 /config 目录下不同文件名的方式提供了多环境配置的支持
config
|- config.default.js
|- config.prod.js
|- config.unittest.js
|- config.local.js
config.default.js
为默认的配置文件,所有环境都会加载这个配置文件,一般也会作为开发环境的默认配置文件
当指定 env 时会同时加载对应的配置文件,并覆盖默认配置文件的同名配置。如 prod 环境会加载 config.prod.js
和 config.default.js 文件,config.prod.js 会覆盖 config.default.js 的同名配置
因此可以创建 /config/config.prod.js 对线上环境进行特殊配置,在 egg.js 启动时候可以通过 EGG_SERVER_ENV
自定义环境
CRUD
CRUD 是 Create、Read、Update、Delete 的缩写,是数据库最常用的四种操作,egg-mysql 对应的操作也非常简单
- this.app.mysql.insert(表名, 数据对象)
- this.app.mysql.get(表名, where 条件 })
- await this.app.mysql.query(sql 语句, [sql 语句中的变量])
- this.app.mysql.update(表名, 更新字段对象, where 条件)
- this.app.mysql.delete(表名, where 条件)
看一下官网示例
// 插入
await this.app.mysql.insert('posts', { title: 'Hello World' });
=> INSERT INTO `posts` (`title`) VALUES('Hello World');
// 查询
await this.app.mysql.get('posts', { id: 12 });
=> SELECT * FROM `posts` WHERE `id` = 12;
// 自定义 SQL 语句
await this.app.mysql.query('update posts set hits = (hits + ?) where id = ?', [1, postId]);
=> update posts set hits = (hits + 1) where id = 1;
// 删除
await this.app.mysql.delete('posts', { author: 'fengmk2'});
=> DELETE FROM `posts` WHERE `author` = 'fengmk2';
// 更新
const row = {
name: 'fengmk2',
otherField: 'other field value', // any other fields u want to update
};
const options = {
where: {
id: 456
}
};
await this.app.mysql.update('posts', row, options);
=> UPDATE `posts`
SET `name` = 'fengmk2', `otherField` = 'other field value'
WHERE id = 456 ;
// 因为大部分数据库的主键 key 都是 id,所以更新有个快捷的写法
// 只要第一个参数里面包含 `id` 并且不提供第二个参数,就会默认把 id 当做 where 的条件
const row = {
id: 123,
name: 'fengmk2',
otherField: 'other field value', // any other fields u want to update
};
await this.app.mysql.update('posts', row);
示例
接下来写一个用户管理的示例展示
- 接口使用 RESTful 风格,egg.js 提供了内置的 RESRful API 实现方式,为了完整演示,示例自定义实现
非 get 请求需要处理 csrf token,防止 csrf 攻击,细节可以参考 egg.js 安全
添加路由
在示例中会使用到 user 相关的 5 个接口和 3 个页面,在 app/router.js 文件中添加路由
// user api
app.get('/api/user/list', 'api.user.list');
app.get('/api/user/:id', 'api.user.get');
app.post('/api/user', 'api.user.create');
app.put('/api/user/:id', 'api.user.edit');
app.delete('/api/user/:id', 'api.user.delete');
// user page
app.get('/user/create', 'user.create');
app.get('/user/edit/:id', 'user.edit');
app.get('/user/list', 'user.list');
https://github.com/Samaritan89/egg-demo/blob/v2/app/router.js#L7
创建 controller、service、view 文件
根据路由创建对应的文件
- app/controller/api/user.js
- app/controller/user.js
- app/service/user.js
- app/view/user/create.hbs
- app/view/user/edit.hbs
- app/view/user/list.hbs
创建用户功能实现
1. 创建用户视图
app/view/user/create.hbs 核心代码<form method="POST" action="/api/user?_csrf={{csrf}}" enctype="application/x-www-form-urlencoded">
<div class="form-row">
<label>用户名:</label>
<input type="text" name="name" />
</div>
<div class="form-row">
<label>用户配置:</label>
<textarea name="config"></textarea>
</div>
<div class="form-row">
<input type="submit" value="创建用户" />
</div>
</form>
需要在 form action url 参数添加 _csrf 参数
完整代码:https://github.com/Samaritan89/egg-demo/blob/v2/app/view/user/create.hbs
2. 页面 controller
app/controller/user.js 添加 create
方法响应视图
async create() {
await this.ctx.render('user/create', {
csrf: this.ctx.csrf,
});
}
完整代码:https://github.com/Samaritan89/egg-demo/blob/v2/app/controller/user.js#L6
3. 创建用户 api controller
app/controller/api/user.js 添加方法 create
async create() {
const { name, config } = this.ctx.request.body;
const result = await this.service.user.insert({
name,
config,
});
this.ctx.body = {
userId: result.insertId,
};
}
完整代码:https://github.com/Samaritan89/egg-demo/blob/v2/app/controller/api/user.js#L26
4. 创建用户 service
app/service/user.js 添加方法 insert
async insert(user) {
const result = await this.app.mysql.insert('user', user);
return result;
}
完整代码:https://github.com/Samaritan89/egg-demo/blob/v2/app/service/user.js#L33
这样就可以添加用户了,其它集中操作非常类似,不再重复介绍,感兴趣可以看看获取用户列表的 service,稍微复杂一些
async list(pageSize, pageNo, orderBy = 'id', order = 'ASC') {
let sql = 'select id, name, config from user ';
sql += 'where deleted = 0 ';
sql += 'order by ? ? ';
sql += 'limit ?,?;';
const offset = pageSize * (pageNo - 1);
const users = await this.app.mysql.query(sql, [ orderBy, order, offset, pageSize ]);
const totalNumRow = await this.app.mysql.query('select count(id) as totalNum from user where deleted = 0;');
return {
users,
pages: {
pageNo,
pageSize,
total: totalNumRow[0].totalNum,
},
};
}
完整示例:https://github.com/Samaritan89/egg-demo/blob/v2/app/service/user.js#L11
事务
很多时候做一个业务操作需要对数据库中多个表做处理,但其中一个的操作出现错误就需要把所有操作返回,保证数据的准确性,这就是数据库中【事务】的概念
对于一个事务来讲,主要有 beginTransaction、commit 或 rollback,分别代表事务的开始,成功和失败回滚,
手工操作
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;
}
自动控制
const result = await app.mysql.beginTransactionScope(async conn => {
// 不要自定 commit 或者回滚,出现错误时候回自动处理
await conn.insert(table, row1);
await conn.update(table, row2);
return { success: true };
}, ctx); // ctx 是当前请求的上下文,如果是在 service 文件中,可以从 `this.ctx` 获取到
附
- MySQL 使用 138 张图带你 MySQL 入门