数据库环境搭建与十一张表结构解析
纲要
本篇完成两件事:把 reggie 数据库建起来,并把 11 张表 的业务含义与字段设计讲透。
- 建库 :图形界面与命令行两种方式,字符集
utf8mb4 - 建表 :执行
数据模型/db_reggie.sql脚本,图形界面导入与source命令两种方式 source命令的坑 :SQL文件路径不能包含中文,否则解析失败- 表清单 :
employee、category、dish、dish_flavor、setmeal、setmeal_dish、user、address_book、shopping_cart、orders、order_detail - 三类表角色:主数据表、关系表、单据表
- 字段设计约定 :
bigint主键(雪花ID)、is_deleted逻辑删除、create_time/update_time/create_user/update_user公共字段
数据库环境搭建
创建数据库
两种方式任选其一。
图形界面方式 :连接本地 MySQL,右键新建数据库,库名 reggie,字符集指定 utf8mb4。
命令行方式:
sql
-- 登录 MySQL
mysql -uroot -p
-- 查看已有库
SHOW DATABASES;
-- 创建数据库,字符集必须为 utf8mb4(支持 emoji 与四字节字符)
CREATE DATABASE reggie CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
-- 切换到目标库
USE reggie;
-- 查看当前库下的表
SHOW TABLES;
字符集选择 utf8mb4 而不是 utf8:MySQL 的 utf8 实际只支持最多三个字节,遇到 emoji 或部分生僻字会报 Incorrect string value。utf8mb4 才是完整的 UTF-8 实现。
导入表结构
项目资料提供了 项目课程/资料/数据模型/db_reggie.sql,其中是一组 CREATE TABLE 语句,并带有初始化 INSERT 数据。
脚本的建表语句统一采用「先删后建」的写法:
sql
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
...
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='员工信息';
这样重复执行脚本不会产生「表已存在」错误,保证幂等。
图形界面导入 :选中 reggie 库 → 右键 → 运行 SQL 文件 → 选择 db_reggie.sql → 开始执行。
命令行导入:
sql
USE reggie;
SOURCE D:\db_reggie.sql;
SHOW TABLES;
source 命令的中文路径问题
这是一个高频踩坑点。source 命令在打开 SQL 文件时无法正确解析含中文的路径 ,会直接报 error:
text
mysql> source D:\课程资料\数据模型\db_reggie.sql
Failed to open file 'D:\课程资料\数据模型\db_reggie.sql', error: 2
解决办法:把 SQL 文件临时复制到不含中文 的目录(例如磁盘根目录),再执行 source。图形界面导入不受此限制。
十一张表总览
#mermaid-svg-vhj0QRy4TdeBqbf8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vhj0QRy4TdeBqbf8 .error-icon{fill:#552222;}#mermaid-svg-vhj0QRy4TdeBqbf8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vhj0QRy4TdeBqbf8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vhj0QRy4TdeBqbf8 .marker.cross{stroke:#333333;}#mermaid-svg-vhj0QRy4TdeBqbf8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vhj0QRy4TdeBqbf8 p{margin:0;}#mermaid-svg-vhj0QRy4TdeBqbf8 .entityBox{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-vhj0QRy4TdeBqbf8 .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#mermaid-svg-vhj0QRy4TdeBqbf8 .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-vhj0QRy4TdeBqbf8 .labelBkg{background-color:rgba(248.6666666666, 255, 235.9999999999, 0.5);}#mermaid-svg-vhj0QRy4TdeBqbf8 .edgeLabel .label{fill:#9370DB;font-size:14px;}#mermaid-svg-vhj0QRy4TdeBqbf8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edge-pattern-dashed{stroke-dasharray:8,8;}#mermaid-svg-vhj0QRy4TdeBqbf8 .node rect,#mermaid-svg-vhj0QRy4TdeBqbf8 .node circle,#mermaid-svg-vhj0QRy4TdeBqbf8 .node ellipse,#mermaid-svg-vhj0QRy4TdeBqbf8 .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vhj0QRy4TdeBqbf8 .relationshipLine{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-vhj0QRy4TdeBqbf8 .marker{fill:none!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-vhj0QRy4TdeBqbf8 .edgeLabel{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vhj0QRy4TdeBqbf8 .edgeLabel .label rect{fill:rgba(232,232,232, 0.8);}#mermaid-svg-vhj0QRy4TdeBqbf8 .edgeLabel .label text{fill:#333;}#mermaid-svg-vhj0QRy4TdeBqbf8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 维护
分类
分类
拥有口味
包含
被包含
拥有地址
加购
下单
明细
收货地址
EMPLOYEE
CATEGORY
DISH
SETMEAL
DISH_FLAVOR
SETMEAL_DISH
USER
ADDRESS_BOOK
SHOPPING_CART
ORDERS
ORDER_DETAIL
| 表名 | 业务含义 | 归属端 | 表类型 |
|---|---|---|---|
employee |
员工信息(后台登录账号) | 管理端 | 主数据 |
category |
菜品与套餐分类 | 管理端 | 主数据 |
dish |
菜品 | 管理端 | 主数据 |
dish_flavor |
菜品口味(规格) | 管理端 | 关系表 |
setmeal |
套餐 | 管理端 | 主数据 |
setmeal_dish |
套餐与菜品关系 | 管理端 | 关系表 |
user |
C 端用户 |
移动端 | 主数据 |
address_book |
地址簿 | 移动端 | 主数据 |
shopping_cart |
购物车 | 移动端 | 单据表 |
orders |
订单 | 双端 | 单据表 |
order_detail |
订单明细 | 双端 | 单据表 |
按业务域再归类
#mermaid-svg-ztsAQ7seNM9eVIRW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ztsAQ7seNM9eVIRW .error-icon{fill:#552222;}#mermaid-svg-ztsAQ7seNM9eVIRW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ztsAQ7seNM9eVIRW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ztsAQ7seNM9eVIRW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ztsAQ7seNM9eVIRW .marker.cross{stroke:#333333;}#mermaid-svg-ztsAQ7seNM9eVIRW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ztsAQ7seNM9eVIRW p{margin:0;}#mermaid-svg-ztsAQ7seNM9eVIRW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster-label text{fill:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster-label span{color:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster-label span p{background-color:transparent;}#mermaid-svg-ztsAQ7seNM9eVIRW .label text,#mermaid-svg-ztsAQ7seNM9eVIRW span{fill:#333;color:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW .node rect,#mermaid-svg-ztsAQ7seNM9eVIRW .node circle,#mermaid-svg-ztsAQ7seNM9eVIRW .node ellipse,#mermaid-svg-ztsAQ7seNM9eVIRW .node polygon,#mermaid-svg-ztsAQ7seNM9eVIRW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ztsAQ7seNM9eVIRW .rough-node .label text,#mermaid-svg-ztsAQ7seNM9eVIRW .node .label text,#mermaid-svg-ztsAQ7seNM9eVIRW .image-shape .label,#mermaid-svg-ztsAQ7seNM9eVIRW .icon-shape .label{text-anchor:middle;}#mermaid-svg-ztsAQ7seNM9eVIRW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ztsAQ7seNM9eVIRW .rough-node .label,#mermaid-svg-ztsAQ7seNM9eVIRW .node .label,#mermaid-svg-ztsAQ7seNM9eVIRW .image-shape .label,#mermaid-svg-ztsAQ7seNM9eVIRW .icon-shape .label{text-align:center;}#mermaid-svg-ztsAQ7seNM9eVIRW .node.clickable{cursor:pointer;}#mermaid-svg-ztsAQ7seNM9eVIRW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ztsAQ7seNM9eVIRW .arrowheadPath{fill:#333333;}#mermaid-svg-ztsAQ7seNM9eVIRW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ztsAQ7seNM9eVIRW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ztsAQ7seNM9eVIRW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ztsAQ7seNM9eVIRW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ztsAQ7seNM9eVIRW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ztsAQ7seNM9eVIRW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster text{fill:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW .cluster span{color:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ztsAQ7seNM9eVIRW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ztsAQ7seNM9eVIRW rect.text{fill:none;stroke-width:0;}#mermaid-svg-ztsAQ7seNM9eVIRW .icon-shape,#mermaid-svg-ztsAQ7seNM9eVIRW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ztsAQ7seNM9eVIRW .icon-shape p,#mermaid-svg-ztsAQ7seNM9eVIRW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ztsAQ7seNM9eVIRW .icon-shape .label rect,#mermaid-svg-ztsAQ7seNM9eVIRW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ztsAQ7seNM9eVIRW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ztsAQ7seNM9eVIRW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ztsAQ7seNM9eVIRW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 后台管理域
employee
用户域(移动端)
user
address_book
shopping_cart
orders
order_detail
菜品域(管理端)
category
dish
dish_flavor
setmeal
setmeal_dish
表结构逐张解析
employee 员工表
管理端的登录账号表。后台登录页输入的用户名与密码,就来自这张表。
sql
CREATE TABLE `employee` (
`id` bigint(20) NOT NULL COMMENT '主键',
`name` varchar(32) NOT NULL COMMENT '姓名',
`username` varchar(32) NOT NULL COMMENT '用户名',
`password` varchar(64) NOT NULL COMMENT '密码',
`phone` varchar(11) NOT NULL COMMENT '手机号',
`sex` varchar(2) NOT NULL COMMENT '性别',
`id_number` varchar(18) NOT NULL COMMENT '身份证号',
`status` int(11) NOT NULL DEFAULT '1' COMMENT '状态 0:禁用,1:正常',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='员工信息';
设计要点:
username上有唯一索引idx_username,这是新增员工时重复账号报错Duplicate entry的根源,后续由全局异常处理器捕获status字段支撑「启用 / 禁用员工账号」功能:禁用后员工无法登录,但记录不删除id为bigint,对应Java的Long;若实体误用Integer,雪花ID会溢出
初始化数据中包含默认管理员账号(admin),密码经 MD5 加密存储。
category 分类表
菜品分类与套餐分类共用一张表,靠 type 字段区分。
sql
CREATE TABLE `category` (
`id` bigint(20) NOT NULL COMMENT '主键',
`type` int(11) DEFAULT NULL COMMENT '类型 1 菜品分类 2 套餐分类',
`name` varchar(64) NOT NULL COMMENT '分类名称',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '顺序',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_category_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品及套餐分类';
type 值 |
含义 | 示例数据 |
|---|---|---|
1 |
菜品分类 | 湘菜、川菜、粤菜、饮品、主食 |
2 |
套餐分类 | 商务套餐、儿童套餐 |
name 唯一索引意味着分类名不可重复,新增重名分类会触发唯一约束异常。
dish 菜品表
sql
CREATE TABLE `dish` (
`id` bigint(20) NOT NULL COMMENT '主键',
`name` varchar(64) NOT NULL COMMENT '菜品名称',
`category_id` bigint(20) NOT NULL COMMENT '菜品分类id',
`price` decimal(10,2) DEFAULT NULL COMMENT '菜品价格',
`code` varchar(64) NOT NULL COMMENT '商品码',
`image` varchar(200) NOT NULL COMMENT '图片',
`description` varchar(400) DEFAULT NULL COMMENT '描述信息',
`status` int(11) NOT NULL DEFAULT '1' COMMENT '0 停售 1 起售',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '顺序',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_dish_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品管理';
price使用decimal(10,2)而非float/double,避免浮点精度丢失;Java侧对应BigDecimalimage存的是文件名 (如f966a38e-0780-40be-bb52-5699d13cb3d9.jpg),不是完整路径,实际路径由reggie.path配置项拼接status控制起售 / 停售;is_deleted控制逻辑删除。两者独立:停售的菜品仍可被管理端看到,删除的菜品不再出现
dish_flavor 菜品口味表
sql
CREATE TABLE `dish_flavor` (
`id` bigint(20) NOT NULL COMMENT '主键',
`dish_id` bigint(20) NOT NULL COMMENT '菜品',
`name` varchar(64) NOT NULL COMMENT '口味名称',
`value` varchar(500) DEFAULT NULL COMMENT '口味数据list',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品口味关系表';
关键设计:value 字段存的是**JSON 数组字符串**,一行代表一个口味维度,维度下的多个选项以 JSON 数组形式塞进一个字段:
text
name = '辣度', value = '["不辣","微辣","中辣","重辣"]'
name = '忌口', value = '["不要葱","不要蒜","不要香菜","不要辣"]'
name = '温度', value = '["热饮","常温","去冰","少冰","多冰"]'
name = '甜味', value = '["无糖","少糖","半糖","多糖","全糖"]'
Java 侧该字段映射为 List<String>,需要自定义类型处理器或直接使用 String 接收后手工解析。这种设计牺牲了查询能力(无法按单个口味选项检索),换取了写入的简洁性 ------ 一个菜品的所有口味维度只需要 N 行(N = 维度数)。
setmeal 套餐表
sql
CREATE TABLE `setmeal` (
`id` bigint(20) NOT NULL COMMENT '主键',
`category_id` bigint(20) NOT NULL COMMENT '菜品分类id',
`name` varchar(64) NOT NULL COMMENT '套餐名称',
`price` decimal(10,2) NOT NULL COMMENT '套餐价格',
`status` int(11) DEFAULT NULL COMMENT '状态 0:停用 1:启用',
`code` varchar(32) DEFAULT NULL COMMENT '编码',
`description` varchar(512) DEFAULT NULL COMMENT '描述信息',
`image` varchar(255) DEFAULT NULL COMMENT '图片',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_setmeal_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='套餐';
套餐本质是「若干菜品的集合 + 一个独立价格」。套餐价格与其中菜品价格之和没有强制一致约束,由运营自行定价。
setmeal_dish 套餐菜品关系表
sql
CREATE TABLE `setmeal_dish` (
`id` bigint(20) NOT NULL COMMENT '主键',
`setmeal_id` varchar(32) NOT NULL COMMENT '套餐id',
`dish_id` varchar(32) NOT NULL COMMENT '菜品id',
`name` varchar(32) DEFAULT NULL COMMENT '菜品名称(冗余字段)',
`price` decimal(10,2) DEFAULT NULL COMMENT '菜品原价(冗余字段)',
`copies` int(11) NOT NULL COMMENT '份数',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='套餐菜品关系';
name 与 price 是冗余字段 。冗余的收益:展示套餐内菜品列表时无需再 JOIN dish 表;代价:菜品改价后关系表里的历史价格不会同步,展示的是加入套餐时的快照价格 ------ 这在订单类业务中恰恰是期望行为。
copies 表示这个菜品在套餐里有几份。
user 用户表
sql
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT '主键',
`name` varchar(50) DEFAULT NULL COMMENT '姓名',
`phone` varchar(100) NOT NULL COMMENT '手机号',
`sex` varchar(2) DEFAULT NULL COMMENT '性别',
`id_number` varchar(18) DEFAULT NULL COMMENT '身份证号',
`avatar` varchar(500) DEFAULT NULL COMMENT '头像',
`status` int(11) DEFAULT '0' COMMENT '状态 0:禁用,1:正常',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用户信息';
这是 C 端用户表,与 employee 完全独立 ------ 两套账号体系,两套登录逻辑。phone 是移动端登录的唯一凭证,验证码登录后若手机号不存在则自动注册。
注意该表没有 create_time / update_time / is_deleted 字段,是 11 张表里结构最简单的一张。
address_book 地址簿表
sql
CREATE TABLE `address_book` (
`id` bigint(20) NOT NULL COMMENT '主键',
`user_id` bigint(20) NOT NULL COMMENT '用户id',
`consignee` varchar(50) NOT NULL COMMENT '收货人',
`sex` tinyint(4) NOT NULL COMMENT '性别 0 女 1 男',
`phone` varchar(11) NOT NULL COMMENT '手机号',
`province_code` varchar(12) DEFAULT NULL COMMENT '省级区划编号',
`province_name` varchar(32) DEFAULT NULL COMMENT '省级名称',
`city_code` varchar(12) DEFAULT NULL COMMENT '市级区划编号',
`city_name` varchar(32) DEFAULT NULL COMMENT '市级名称',
`district_code` varchar(12) DEFAULT NULL COMMENT '区级区划编号',
`district_name` varchar(32) DEFAULT NULL COMMENT '区级名称',
`detail` varchar(200) DEFAULT NULL COMMENT '详细地址',
`label` varchar(100) DEFAULT NULL COMMENT '标签',
`is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认 0 否 1是',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='地址管理';
- 省市区采用编号 + 名称双存:编号用于后续对接物流系统,名称用于直接展示,避免联查行政区划表
label存「公司」「家」这类标签is_default标记默认地址。设置默认地址的业务规则是:先将该用户所有地址置为非默认,再把目标地址置为默认 ------ 这是一个需要两条SQL的更新操作
shopping_cart 购物车表
sql
CREATE TABLE `shopping_cart` (
`id` bigint(20) NOT NULL COMMENT '主键',
`name` varchar(50) DEFAULT NULL COMMENT '名称',
`image` varchar(100) DEFAULT NULL COMMENT '图片',
`user_id` bigint(20) NOT NULL COMMENT '主键',
`dish_id` bigint(20) DEFAULT NULL COMMENT '菜品id',
`setmeal_id` bigint(20) DEFAULT NULL COMMENT '套餐id',
`dish_flavor` varchar(50) DEFAULT NULL COMMENT '口味',
`number` int(11) NOT NULL DEFAULT '1' COMMENT '数量',
`amount` decimal(10,2) NOT NULL COMMENT '金额',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='购物车';
dish_id 与 setmeal_id 互斥 :加购菜品时 setmeal_id 为 NULL,加购套餐时 dish_id 为 NULL。这个约定决定了「添加购物车」的逻辑必须先判断是菜品还是套餐。
amount 是单项小计(单价 × 数量),不是单价。
dish_flavor 存用户选中的口味字符串,且它参与「同一菜品是否算作同一购物项」的判定 ------ 辣度不同应当视为两条购物车记录。
orders 订单表
sql
CREATE TABLE `orders` (
`id` bigint(20) NOT NULL COMMENT '主键',
`number` varchar(50) DEFAULT NULL COMMENT '订单号',
`status` int(11) NOT NULL DEFAULT '1' COMMENT '订单状态 1待付款,2待派送,3已派送,4已完成,5已取消',
`user_id` bigint(20) NOT NULL COMMENT '下单用户',
`address_book_id` bigint(20) NOT NULL COMMENT '地址id',
`order_time` datetime NOT NULL COMMENT '下单时间',
`checkout_time` datetime NOT NULL COMMENT '结账时间',
`pay_method` int(11) NOT NULL DEFAULT '1' COMMENT '支付方式 1微信,2支付宝',
`amount` decimal(10,2) NOT NULL COMMENT '实收金额',
`remark` varchar(100) DEFAULT NULL COMMENT '备注',
`phone` varchar(255) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
`user_name` varchar(255) DEFAULT NULL,
`consignee` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='订单表';
表名是 orders 而不是 order ------ 因为 order 是 SQL 保留字,用它做表名每次都要加反引号,极易出错。这是数据库命名的通用实践。
phone / address / consignee 属于冗余快照:下单瞬间把地址簿里的信息抄一份到订单里。这样即使后续用户修改了地址簿,历史订单的配送信息也不会变。
订单状态机:
#mermaid-svg-dgYmr32kWA7wOesq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dgYmr32kWA7wOesq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dgYmr32kWA7wOesq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dgYmr32kWA7wOesq .error-icon{fill:#552222;}#mermaid-svg-dgYmr32kWA7wOesq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dgYmr32kWA7wOesq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dgYmr32kWA7wOesq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dgYmr32kWA7wOesq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dgYmr32kWA7wOesq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dgYmr32kWA7wOesq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dgYmr32kWA7wOesq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dgYmr32kWA7wOesq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dgYmr32kWA7wOesq .marker.cross{stroke:#333333;}#mermaid-svg-dgYmr32kWA7wOesq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dgYmr32kWA7wOesq p{margin:0;}#mermaid-svg-dgYmr32kWA7wOesq defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-dgYmr32kWA7wOesq g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-dgYmr32kWA7wOesq g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-dgYmr32kWA7wOesq g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-dgYmr32kWA7wOesq g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-dgYmr32kWA7wOesq g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-dgYmr32kWA7wOesq .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-dgYmr32kWA7wOesq .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-dgYmr32kWA7wOesq .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-dgYmr32kWA7wOesq .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-dgYmr32kWA7wOesq .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-dgYmr32kWA7wOesq .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-dgYmr32kWA7wOesq .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-dgYmr32kWA7wOesq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dgYmr32kWA7wOesq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dgYmr32kWA7wOesq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dgYmr32kWA7wOesq .edgeLabel .label text{fill:#333;}#mermaid-svg-dgYmr32kWA7wOesq .label div .edgeLabel{color:#333;}#mermaid-svg-dgYmr32kWA7wOesq .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-dgYmr32kWA7wOesq .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-dgYmr32kWA7wOesq .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-dgYmr32kWA7wOesq .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-dgYmr32kWA7wOesq .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-dgYmr32kWA7wOesq .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dgYmr32kWA7wOesq .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dgYmr32kWA7wOesq #statediagram-barbEnd{fill:#333333;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dgYmr32kWA7wOesq .cluster-label,#mermaid-svg-dgYmr32kWA7wOesq .nodeLabel{color:#131300;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-dgYmr32kWA7wOesq .note-edge{stroke-dasharray:5;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-note text{fill:black;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram-note .nodeLabel{color:black;}#mermaid-svg-dgYmr32kWA7wOesq .statediagram .edgeLabel{color:red;}#mermaid-svg-dgYmr32kWA7wOesq #dependencyStart,#mermaid-svg-dgYmr32kWA7wOesq #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-dgYmr32kWA7wOesq .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dgYmr32kWA7wOesq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1 用户提交订单
2 支付完成
3 商家发货
4 用户确认
5 超时或主动取消
待付款
待派送
已派送
已完成
已取消
order_detail 订单明细表
sql
CREATE TABLE `order_detail` (
`id` bigint(20) NOT NULL COMMENT '主键',
`name` varchar(50) DEFAULT NULL COMMENT '名字',
`image` varchar(100) DEFAULT NULL COMMENT '图片',
`order_id` bigint(20) NOT NULL COMMENT '订单id',
`dish_id` bigint(20) DEFAULT NULL COMMENT '菜品id',
`setmeal_id` bigint(20) DEFAULT NULL COMMENT '套餐id',
`dish_flavor` varchar(50) DEFAULT NULL COMMENT '口味',
`number` int(11) NOT NULL DEFAULT '1' COMMENT '数量',
`amount` decimal(10,2) NOT NULL COMMENT '金额',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='订单明细';
结构与 shopping_cart 高度相似 ------ 这不是巧合:下单时的数据来源是购物车,明细是购物车项的持久化快照。区别在于多了一个 order_id 外键指向 orders。
一次下单会写入 1 条 orders + N 条 order_detail ,必须包在同一个事务里,这正是启动类上加 @EnableTransactionManagement 的原因。
字段设计中的三条通用约定
主键统一 bigint + 雪花算法
所有表主键为 bigint(20),由 MyBatis-Plus 的 ASSIGN_ID 策略用雪花算法生成。Java 实体必须用 Long 。这条约定有一个直接后果:Long 型 ID 在 JavaScript 中超过 2^53 会精度丢失,因此必须配置消息转换器把 Long 序列化为字符串 ------ 这是后续 JacksonObjectMapper 存在的原因。
is_deleted 逻辑删除
dish、setmeal、category、dish_flavor、setmeal_dish、address_book 六张表带 is_deleted 字段。业务上的「删除」实际是 UPDATE ... SET is_deleted = 1,数据仍在库中,可恢复、可审计。
四个公共字段
create_time / update_time / create_user / update_user 在多张表中重复出现。如果每张表的每次插入更新都要手工 set,会产生大量重复代码 ------ 这直接催生了后续「公共字段自动填充」章节的 MyMetaObjectHandler。
完整可运行代码
建库与导入脚本
sql
-- ============ 步骤一:创建数据库 ============
CREATE DATABASE IF NOT EXISTS reggie
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_general_ci;
USE reggie;
-- ============ 步骤二:执行 db_reggie.sql 导入 11 张表 ============
-- 命令行方式(注意路径不要含中文):
-- SOURCE D:/db_reggie.sql;
-- ============ 步骤三:校验 ============
SHOW TABLES;
-- 预期输出 11 张表:
-- address_book / category / dish / dish_flavor / employee
-- order_detail / orders / setmeal / setmeal_dish / shopping_cart / user
-- ============ 步骤四:确认初始化数据 ============
SELECT id, name, type, sort FROM category ORDER BY type, sort;
SELECT id, username, name, status FROM employee;
与表对应的实体类
以 Employee 与 DishFlavor 为例,展示字段到属性的映射规则。
src/main/java/com/itheima/reggie/entity/Employee.java:
java
package com.itheima.reggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 员工实体,对应 employee 表
* 表名 employee 与类名 Employee 通过驼峰 + 首字母规则映射
* 字段 id_number 与属性 idNumber 通过 map-underscore-to-camel-case 映射
*/
@Data
@TableName("employee")
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private Long id;
private String name;
private String username;
private String password;
private String phone;
private String sex;
private String idNumber;
/** 状态:0 禁用,1 正常 */
private Integer status;
/** 插入时自动填充 */
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/** 插入与更新时均自动填充 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
}
src/main/java/com/itheima/reggie/entity/DishFlavor.java:
java
package com.itheima.reggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 菜品口味实体,对应 dish_flavor 表
* value 字段在库中以 JSON 数组字符串形式存储,如 ["不辣","微辣"]
*/
@Data
@TableName("dish_flavor")
public class DishFlavor implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private Long id;
/** 所属菜品 id */
private Long dishId;
/** 口味维度名称,如 辣度 / 忌口 / 温度 / 甜味 */
private String name;
/** 口味选项,JSON 数组字符串 */
@TableField(value = "`value`")
private String value;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
/** 逻辑删除标记,0 未删除,1 已删除 */
private Integer isDeleted;
}
注意 value 字段用 @TableField(value = "`value`") 显式加了反引号 ------ value 在部分 SQL 方言中是关键字,加反引号可以避免 SQL 拼接失败。
持久层接口
src/main/java/com/itheima/reggie/mapper/EmployeeMapper.java:
java
package com.itheima.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Employee;
import org.apache.ibatis.annotations.Mapper;
/**
* 继承 BaseMapper 后即拥有 insert / deleteById / updateById / selectById / selectList 等基础方法
*/
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}
src/main/java/com/itheima/reggie/mapper/DishFlavorMapper.java:
java
package com.itheima.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.DishFlavor;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DishFlavorMapper extends BaseMapper<DishFlavor> {
}
验证环境可用的最小测试
java
package com.itheima.reggie;
import com.itheima.reggie.entity.Employee;
import com.itheima.reggie.mapper.EmployeeMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class ReggieApplicationTests {
@Autowired
private EmployeeMapper employeeMapper;
/**
* 验证数据库环境:能查到初始化数据即说明建库建表与数据源配置均正确
*/
@Test
public void testDataSource() {
List<Employee> employees = employeeMapper.selectList(null);
employees.forEach(e -> System.out.println(e.getUsername() + " -> " + e.getName()));
}
}
API 速览
API / 注解 |
所属 | 作用 |
|---|---|---|
CREATE DATABASE ... CHARACTER SET utf8mb4 |
MySQL |
创建支持四字节字符的库 |
SOURCE <file> |
MySQL 客户端 |
执行 SQL 脚本文件,路径不可含中文 |
SHOW TABLES |
MySQL |
列出当前库所有表 |
@TableName |
MyBatis-Plus |
建立实体类与表名的映射 |
@TableId |
MyBatis-Plus |
标识主键字段,配合全局 id-type 生成 ID |
@TableField |
MyBatis-Plus |
字段名映射与填充策略声明 |
@TableField(fill = FieldFill.INSERT) |
MyBatis-Plus |
插入时自动填充 |
@TableField(fill = FieldFill.INSERT_UPDATE) |
MyBatis-Plus |
插入与更新时均自动填充 |
BaseMapper<T> |
MyBatis-Plus |
提供单表 CRUD 基础方法 |
@Mapper |
MyBatis |
将接口注册为 MyBatis 映射器 |
@Data |
Lombok |
生成 getter / setter / toString 等 |
@SpringBootTest |
Spring Boot |
加载完整容器用于集成测试 |
官方文档
MySQL 8.0CREATE DATABASE语法:https://dev.mysql.com/doc/refman/8.0/en/create-database.htmlMySQL字符集与排序规则:https://dev.mysql.com/doc/refman/8.0/en/charset.htmlmysql客户端source命令:https://dev.mysql.com/doc/refman/8.0/en/mysql-commands.htmlMyBatis-Plus注解:https://baomidou.com/pages/223848/MyBatis-Plus主键策略:https://baomidou.com/pages/e131bd/
总结
本篇围绕 reggie 数据库的落地,完成了从建库、导表到逐张解析的完整闭环,核心收获可以归纳为四点:
- 环境搭建 :字符集必须选
utf8mb4才能完整支持emoji与生僻字;source命令无法解析含中文的路径,遇到报错先把SQL文件复制到纯英文目录再执行。 - 表结构设计 :11 张表按业务可归为三类 ------ 主数据表(
employee、category、dish、setmeal、user、address_book)、关系表(dish_flavor、setmeal_dish)与单据表(shopping_cart、orders、order_detail)。关系表与单据表大量使用冗余快照字段,用空间换查询性能,这在订单类业务中是刻意为之。 - 通用约定 :主键统一
bigint+ 雪花算法(Java侧必须用Long,且要配置序列化器避免前端精度丢失);is_deleted逻辑删除保证数据可恢复;create_time/update_time/create_user/update_user四个公共字段催生了后续的自动填充处理器。 - 代码落地 :实体类通过
@TableName、@TableId、@TableField与表结构一一对应,BaseMapper直接提供单表CRUD,配合一个最小测试即可验证整条数据链路是否打通。
至此,数据库层已经就绪。下一步建议进入「公共字段自动填充」与「登录鉴权」章节,把 MyMetaObjectHandler 和 JWT 拦截器串起来,让后台管理端真正跑起来。