人人都能看懂的全栈开发教程——主键和外键

人人都能看懂的全栈开发教程——主键和外键

Chris Yue No Comment
Posts

到此我们已经完了我们的任务清单项目的第一个里程碑了。产品对我们的作品很满意,只不过还达不到上线让用户使用的需求。『怎么着也不能多个用户用一个本子来记任务吧?』,然后更多需求被提了出来:

  1. 得有用户登录界面,登录的要素有:用户名,和密码
  2. 刚开始先不开放注册,我们通过命令行直接添加用户
  3. 任务与用户是『n 对 1』的关系,登录用户只能看见自己创建的任务

『假如我们之前的任务列表或者新增任务页面被一个没登录的用户访问了怎么办呢?』

『那就都直接显示登录页面!』

上面的对话是我想象的,但实际在工作中,这种对话经常出现在『需求讨论会』上,后面我都会适当引入一些这样的对话,让大家对实际开发工作都在跟产品聊什么这个事儿上有点带入点『画面感』。

我们依然需要先给出开发计划:

  1. 数据库里得有用户表了,先就给用户名和密码两个字段吧,都是字符串类型的,但是产品没说用户名和密码限制多长,这得再跟产品确认
  2. 另外之前网易和 CSDN 都被人偷走了数据库的数据,他们的用户密码都被暴光了,万一这些用户在其他网站也是用得同样的密码,那多危险啊。我们应该给密码『加密』,即使我们的数据库被偷走了,也不知道用户都怎么设置密码。至于加密方式,就用 PHP 自己提供的 password_hash 函数,并且选择默认的加密算法。
  3. 如果用户输入帐号和密码校验(包括是否输入了账户/密码,以及密码是否匹配)失败,应当如何给用户展示还没说,但如果现阶段需求没要求,就先用 HTM5 加 die 大法展示吧。

『用户名就别太长了,3 到 10 个字符吧;密码先定 6 到 18 位吧』

『完全同意密码要加密,显示出我们的专业性』

『至于账户密码校验出错的问题,怎么方便怎么来吧……』

得到了产品『较为明确』的回复之后,便可以开始开发新需求了。

添加新的数据迁移文件 migrations/user.sql,输入下面代码:

# 先进入我们的 minetodo 库
USE minetodo;
# 再创建我们的用户表,用户名最长 10 位,密码按 PHP 文档建议设置成 255
# 另外我们需要添加主键(primary key)id,原因后面说
CREATE TABLE user (id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(10) NOT NULL, password VARCHAR(255) NOT NULL);

我们的需求说『task 和 user 是 n 对 1』的关系,『 n 对 1』或者『多对 1』的意思是,n 个 task 记录,都属于某一个 user 记录,或者说一个 user 记录有多个 task 记录。因此我们需要在 task 表上,添加 user_id 字段,表示属于哪一个 user。现在知道关系型数据库的『关系』是啥意思了吧?

# 因为我们之前测试的原因,可能表里已经有一些 task 数据了。
# 如果我们直接添加 user_id,以前已经存在的 task 数据就变成了『孤儿数据』,即没有任何用户能看到它们
# 所以在新增字段前记得运行 delete from task; 把表清空后再添加 user_id 字段
ALTER TABLE task ADD COLUMN user_id INT NOT NULL;

这里我们来解释一下,我们刚才添加的『键』是什么意思。

主键

就类似于 HTML 里用 id 属性用来标识文档里面某一个元素,每个元素可以有独一无二的 id 值,在数据库里,我们也可以用『主键』来标识一张表里的某一行数据。

外键

A 表有了主键之后,就可以在 B 表里添加『外键』来表示 B 表中的若干记录和 A 表里的某个记录的关系,就好像我们在 task 表里做的一样,如果有一条 task 的记录,它的 user_id 是 1,那么我们就知道这条记录跟 id 为 1 的用户有关系,或者说它属于 id 为 1 的用户。

我们使用了自增(AUTO_INCREMENT)的整数(INT)来作为主键的值。实际上我们也可以拿用户名来作主键,但我们没这么做的原因是,很有可能以后会有需求说用户名是可以修改的,如果用户名被修改,那么跟这个用户有关的所有 task 记录里的 user_id 也得跟着改才能保持这种关联。而使用一个毫无意义的数字 id,就没有这个问题。

事情到此其实我们可以写代码了。但这里我要再多说一个知识点。

外键约束

MySQL 以及其他很多数据库,不光只是会记录数据,当设置了外键的表,遇到被关联的数据的主键被修改或者整行被删除,是可以配置处理方式的。

MySQL 支持 4 种方式。以我们自己的例子举例:如果某个用户被删除,可以有以下几种设置供选择

  1. 自动删除属于这个用户的所有任务
  2. 相关任务的 user_id 变成 null,从而变成『孤儿数据』
  3. 直接报错,并告知因有与其关联的数据,不能被删除
  4. 设置 user_id 为默认值,如果有默认值的话

因为需求并没有提用户可以被删除,我们可以先选择最保险的方式 3。

可能有人会问,既然需求都没说要怎么处理,那为啥还非得要加呢?

是可以不加,但我建议还是加上。如果不加,假如代码有问题,把用户 id 处理错了,比如所有的用户获取到的用户 id 全取错成 0 了,导致新增的 task 的 user_id 实际上根本不存在,虽然逻辑上出错了,但数据库是不报错的,那代码也不会报错,可能这个错误就这么隐藏下去了。如果一开始就设置了外键约束,这种情况数据库会直接报错说找不到对应的用户 ID,保存不了任务数据,立马就能知道代码有问题。

添加外键的 SQL:

ALTER TABLE task ADD FOREIGN KEY (user_id) REFERENCE user (id);

最后说明一下,还记得我们的 show create table 命令吗?你是否发现这个命令返回的结果里有这么一句 ENGINE=InnoDB ?在 MySQL 里我们有很多『引擎』可以选择,不同的引擎提供的功能也不同。只有 InnoDB 才支持外键约束。

本章 user.sql 文件内容见这里

人人都能看懂的全栈开发教程——主键和外键 by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

如果觉得文章还不错,就请扫码鼓励一下作者吧
天使打赏人

发表评论

33 + = 34