跳到主要内容

操作数据库

本章内容将介绍如何在单应用项目中使用数据库查询。

你需要掌握 MySQL 基本知识,至少需要一个已经安装好的 MySQL 服务,并可以正常使用。

安装 MySQL 模块

yarn add @zenweb/mysql

配置 MySQL 模块

修改 src/index.ts 配置 MySQL 模块

src/index.ts
import { create } from "zenweb";
import modMySQL from "@zenweb/mysql";

create()

.setup(modMySQL({
// 配置数据库连接池
pools: {
// MySQL 模块支持一个应用中同时使用多个实例
MASTER: { // 实例名称
host: '127.0.0.1', // 数据库地址
port: 3306, // 数据库端口
user: 'root', // 数据库用户名
password: '', // 数据库密码
database: 'test', // 数据库名称
charset: 'utf8mb4', // 数据库默认字符集
timezone: '+08:00', // 数据库时区
connectionLimit: 100, // 单应用数据库连接数上限
},
}
}))

.start();
关于实例名称

在上述配置项中 MASTER 为数据库实例名称,他只是作为使用多个实例时区分不同实例而定义,如果只是单纯使用 SQL 查询本身并没有什么特殊含义。 但是如果需要 ORM 的自动读写分离支持则需要指定名称为主库 MASTER 从库为 SLAVE* * 星号代表自编号或者其他名称,但是必须为 SLAVE 开头。

连接 MySQL 创建表

CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL COMMENT '姓名',
`birthday` date DEFAULT NULL COMMENT '生日',
`unreads` int(11) NOT NULL DEFAULT 0 COMMENT '未读消息数',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`from_user_id` int(11) NOT NULL,
`to_user_id` int(11) NOT NULL,
`content` varchar(255) DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

使用 SQL 查询

创建文件 src/controller/database.ts

src/controller/database.ts
import { mapping, Context, QueryHelper } from "zenweb";

export class DatabaseController {
/**
* 读区数据
*/
@mapping({ path: '/db/get' })
async get(ctx: Context) {
const result = await ctx.mysql.query('SELECT * FROM `user`');
return { result };
}

/**
* 插入数据
*/
@mapping({ path: '/db/create' })
async create(ctx: Context, qh: QueryHelper) {
const data = qh.get({
name: "!trim1",
birthday: "!date",
});
const result = await ctx.mysql.query('INSERT INTO `user` (`name`, `birthday`) VALUES (?, ?)', [
data.name, data.birthday
]);
return { result };
}
}

访问 http://127.0.0.1:7001/create?name=Bob&birthday=1987-9-20 插入数据后再访问 http://127.0.0.1:7001/get 查看结果

使用事务

对于简单的增删改查使用 query() 方法即可完成,借助 nodejs 的异步特性,你可以简单的去除 await 来进行后台查询。

然而我们在业务中会经常用到数据库的事务功能,比如给用户发消息的同时需要更新未读消息数。

自动事务

src/controller/database.ts
export class DatabaseController {
/**
* 自动事务
*/
@mapping({ path: '/db/tx1' })
async autoTransaction(ctx: Context, qh: QueryHelper) {
const data = qh.get({
from_user_id: "!int",
to_user_id: "!int",
content: "!trim1",
});
// 开启数据库事务
await ctx.mysql.transaction(async tx => {
// 插入消息
await tx.query('INSERT INTO `message` (`from_user_id`, `to_user_id`, `content`) VALUES (?, ?, ?)', [
data.from_user_id, data.to_user_id, data.content
]);
// 更新未读消息数
await tx.query('UPDATE `user` SET `unreads` = `unreads` + 1 WHERE `id` = ?', [
data.to_user_id
]);
});
return 'ok';
}
}

使用 transaction() 回调函数即可开启一个自动事务,事物开启成功后会回调业务代码并传入参数 tx 作为本次事务的数据库线程。 在回调函数中使用 tx 数据库线程处理需要被事务化的查询,在没有问题的情况下事务会自动提交,如果需要回滚直接在回调函数中抛出异常即可。

手动事务

对于一些特殊情况也可以使用手动事务来控制事务的提交与回滚。如下代码:

src/controller/database.ts
export class DatabaseController {
/**
* 手动事务
*/
@mapping({ path: '/db/tx2' })
async manualTransaction(ctx: Context, qh: QueryHelper) {
const data = qh.get({
from_user_id: "!int",
to_user_id: "!int",
content: "!trim1",
});
// 使用自动连接池取得一个数据库连接线程
await ctx.mysql.auto(async conn => {
// 开启当前线程事务
await conn.beginTransaction();
try {
// 插入消息
await conn.query('INSERT INTO `message` (`from_user_id`, `to_user_id`, `content`) VALUES (?, ?, ?)', [
data.from_user_id, data.to_user_id, data.content
]);
// 更新未读消息数
await conn.query('UPDATE `user` SET `unreads` = `unreads` + 1 WHERE `id` = ?', [
data.to_user_id
]);
// 提交事务
await conn.commit();
} catch {
// 发生异常 - 回滚事务
await conn.rollback();
}
});
return 'ok';
}
}