在NodeJS 的Express 中集成 MySQL 和 Knex 的完整指南(一)中的增删改查案例里,有在创建users表时没有添加的status字段,本文记录Knex怎么在已有表添加新字段以及添加多个新表的操作:
步骤 1:创建迁移文件
npx knex migrate:make add_status_to_users # 添加 status 字段到 users
npx knex migrate:make create_tables # 创建新表 orders/products/categories
步骤 2:更新 users 表(添加 status 字段)
文件:migrations/YYYYMMDDHHMMSS_add_status_to_users.js
exports.up = function (knex) {
return knex.schema.alterTable('users', (table) => {
table.enu('status', ['active', 'inactive']) // MySQL 使用 enu 方法
.notNullable()
.defaultTo('active');
});
};
exports.down = function (knex) {
return knex.schema.alterTable('users', (table) => {
table.dropColumn('status');
});
};
步骤 3:创建新表(无外键约束)
文件:migrations/YYYYMMDDHHMMSS_create_tables.js
exports.up = function (knex) {
return knex.schema
// 创建 categories 表
.createTable('categories', (table) => {
table.increments('id').primary();
table.string('name', 255).notNullable().unique();
table.text('description');
table.timestamp('created_at').defaultTo(knex.fn.now());
table.timestamp('updated_at').defaultTo(knex.fn.now());
})
// 创建 products 表(逻辑关联 categories)
.createTable('products', (table) => {
table.increments('id').primary();
table.string('name', 255).notNullable();
table.text('description');
table.decimal('price', 10, 2).notNullable(); // 十进制类型
table.integer('stock').unsigned().defaultTo(0);
table.integer('category_id').unsigned(); // 仅字段关联,无外键约束
table.timestamp('created_at').defaultTo(knex.fn.now());
table.timestamp('updated_at').defaultTo(knex.fn.now());
})
// 创建 orders 表(逻辑关联 users)
.createTable('orders', (table) => {
table.increments('id').primary();
table.integer('user_id').unsigned(); // 仅字段关联,无外键约束
table.decimal('total_amount', 10, 2).notNullable();
table.enu('status', ['pending', 'paid', 'shipped', 'cancelled'])
.defaultTo('pending');
table.timestamp('created_at').defaultTo(knex.fn.now());
table.timestamp('updated_at').defaultTo(knex.fn.now());
});
};
exports.down = function (knex) {
return knex.schema
.dropTable('orders')
.dropTable('products')
.dropTable('categories');
};
Knex 提供的字段类型函数如下:
- integer():整数
- float():浮点数
- decimal():十进制数
- boolean():布尔值
- date():日期
- dateTime():日期时间
- time():时间
- timestamp():时间戳
- string():字符串
- text():文本
- json():JSON
- binary():二进制数
- uuid():UUID
步骤 4:执行迁移
npx knex migrate:latest
最终表结构说明
1. users 表
| 字段名 | 类型 | 约束 |
|---|---|---|
| id | int | 主键、自增 |
| name | varchar(255) | 非空 |
| varchar(255) | 非空、唯一 | |
| status | enum | 允许 'active'/'inactive',默认 'active' |
| created_at | timestamp | 默认当前时间 |
| updated_at | timestamp | 默认当前时间 |
2. categories 表
| 字段名 | 类型 | 约束 |
|---|---|---|
| id | int | 主键、自增 |
| name | varchar(255) | 非空、唯一 |
| description | text | 可选 |
| created_at | timestamp | 默认当前时间 |
| updated_at | timestamp | 默认当前时间 |
3. products 表
| 字段名 | 类型 | 约束 |
|---|---|---|
| id | int | 主键、自增 |
| name | varchar(255) | 非空 |
| description | text | 可选 |
| price | decimal(10,2) | 非空 |
| stock | int unsigned | 非负、默认 0 |
| category_id | int unsigned | 逻辑关联分类 |
| created_at | timestamp | 默认当前时间 |
| updated_at | timestamp | 默认当前时间 |
4. orders 表
| 字段名 | 类型 | 约束 |
|---|---|---|
| id | int | 主键、自增 |
| user_id | int unsigned | 逻辑关联用户 |
| total_amount | decimal(10,2) | 非空 |
| status | enum | 允许 'pending'/'paid'/'shipped'/'cancelled',默认 'pending' |
| created_at | timestamp | 默认当前时间 |
| updated_at | timestamp | 默认当前时间 |
关键说明
-
没有外键约束
所有关联字段(如user_id、category_id)仅为普通整数字段,无references约束。 -
MySQL 适配
- 使用
enu()替代enum()定义枚举字段。 - 显式定义
timestamp字段并设置默认值knex.fn.now()。
- 使用
-
字段逻辑关联
products.category_id与categories.id通过代码逻辑关联。orders.user_id与users.id通过代码逻辑关联。
操作验证
-
插入测试数据:
// 示例:插入一个分类和关联商品 const categoryId = await knex('categories').insert({ name: '电子产品', description: '手机、电脑等' }); await knex('products').insert({ name: '智能手机', price: 2999.99, category_id: categoryId[0] // 手动关联分类 }); -
查询订单及关联用户:
const orders = await knex('orders') .select('orders.*', 'users.name as user_name') .leftJoin('users', 'orders.user_id', 'users.id'); // 手动关联查询
常见问题
Q: 如何确保关联数据的完整性?
A: 需在业务代码中校验关联数据是否存在(如插入订单前检查 user_id 是否有效)。
Q: 时间戳字段是否需要手动更新?
A: 在更新数据时需手动更新 updated_at:
await knex('products')
.where({ id: 1 })
.update({
price: 2599.99,
updated_at: knex.fn.now() // 手动更新时间戳
});
Q: 事物的提交和回滚
await db.transaction(async (trx) => {
try {
// 扣减库存
await trx('products')
.where('id', 1)
.decrement('stock', 1);
// 创建订单
const [orderId] = await trx('orders')
.insert({
user_id: 1,
total_amount: 99.99
});
await trx.commit() //提交事务
} catch (err) {
await trx.rollback() //回滚事务
}
});
本文使用到的npx,在 npx 的详细说明 中进行了介绍