## 数据库字符集的选择
MySQL在5.5.3之后增加了这个utf8mb4的编码,mb4就是most bytes 4的意思,专门用来兼容四字节的unicode。好在utf8mb4是utf8的超集,除了将编码改为utf8mb4外不需要做其他转换。当然,为了节省空间,一般情况下使用utf8也就够了。
本来3字节的utf8已经可以覆盖这个世界上的所有语言,直到emoji等符号的出现,让字符集大大扩展,导致utf8开始出现一些四字节的符号。
所以我们建议使用数据库的编码为`utf8mb4`,排序方式为:`utf8mb4_unicode_ci`。
## `utf8mb4_unicode_ci`与`utf8mb4_general_ci`如何选择
字符除了需要存储,还需要排序或比较大小,涉及到与编码字符集对应的 排序字符集(collation)。ut8mb4对应的排序字符集常用的有`utf8mb4_unicode_ci`、`utf8mb4_general_ci`。
主要从排序准确性和性能两方面看:
* 准确性
`utf8mb4_unicode_ci`是基于标准的Unicode来排序和比较,能够在各种语言之间精确排序。
`utf8mb4_general_ci`没有实现Unicode排序规则,在遇到某些特殊语言或字符是,排序结果可能不是所期望的。
* 性能
`utf8mb4_general_ci`在比较和排序的时候更快。
`utf8mb4_unicode_ci`在特殊情况下,Unicode排序规则为了能够处理特殊字符的情况,实现了略微复杂的排序算法。
我个人推荐是`utf8mb4_unicode_ci`,将来 8.0 里也极有可能使用变为默认的规则。相比选择哪一种collation,使用者应该更关心字符集与排序规则在db里要统一就好。
## 转换到utf8mb4
如果表定义和连接字符集都是utf8,那么直接在你的表上执行:
~~~
ALTER TABLE tbl_name CONVERT TO CHARACTER SET utf8mb4;
~~~
## 数据库配置
直接在`database.php`数据库配置文件中按如下方式定义即可。
~~~
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'hostname' => '127.0.0.1',
// 数据库名
'database' => 'thinkphp',
// 数据库用户名
'username' => 'root',
// 数据库密码
'password' => '',
// 数据库连接端口
'hostport' => '',
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => 'utf8mb4',
// 数据库表前缀
'prefix' => 'my_',
],
],
];
~~~
也可在本地再配置一份数据库,用于开发阶段使用。见`thinkphp6`根目录下面的`.env`文件。
~~~
APP_DEBUG = true
[APP]
DEFAULT_TIMEZONE = Asia/Shanghai
[DATABASE]
TYPE = mysql
HOSTNAME = 127.0.0.1
DATABASE = project
USERNAME = root
PASSWORD = 123456
HOSTPORT = 3306
CHARSET = utf8mb4
DEBUG = true
[LANG]
default_lang = zh-cn
~~~
## 查询数据
所有的查询都采用静态方法,可以使用`Db`类或者模型类完成查询构造器操作,也就是`think\facade\Db`,通过门面对象`think\facade\Db`进行静态方法调用。以下列举常用的方法。
| 查询方法 | 作用描述 |
| --- | --- |
| table | 指定查询数据表 |
| field | 指定查询字段 |
| where | 指定查询条件 |
| order | 指定结果排序 |
| limit | 指定查询结果数 |
| find | 查询一条记录 |
| select | 查询数据集 |
| insert | 写入数据 |
| update | 更新数据 |
| delete | 删除数据 |
## 数据库架构设计
使用框架开发应用,一般不需要直接操作数据库,而是通过框架封装好的数据库中间层对数据库进行操作。这样的好处主要有两个:一是简化数据库操作,二是做到跨数据库的一致性。这种设计的中间层通常称之为数据库访问抽象层,简称数据访问层(`DAL`),`ThinkPHP6`的数据访问层是基于PHP内置的`PDO`对象实现。一般抽象层本身并不直接操作数据库,而是通过驱动来实现具体的数据库操作。
`ThinkPHP6`的数据库设计相比之前版本更加合理,数据访问层划分的更细化,把数据访问对象分成了连接器、查询器、生成器等多个对象,并通过数据库访问入口类统一调用,分工更明确,各司其职。
### 查询单行
查询单行数据使用`find`方法:
~~~
// table方法必须指定完整的数据表名
Db::table('think_user')->where('id', 1)->find();
// 如果设置了数据表前缀(prefix)参数的话 也可以使用
Db::name('user')->where('id', 1)->find();
~~~
> 即使满足条件的数据有多个,`find`查询也只会返回一条数据。你可以使用`order`排序来决定返回哪一条数据。
最终生成的SQL语句如下:
~~~
SELECT * FROM `think_user` WHERE `id` = 1 LIMIT 1
~~~
> `find`方法查询结果不存在,返回`null`,否则返回结果数组。
> 如果希望查询数据不存在的时候返回空数组,可以使用
~~~
// table方法必须指定完整的数据表名
Db::table('think_user')->where('id', 1)->findOrEmpty();
~~~
如果希望在没有找到数据后抛出异常可以使用
~~~
Db::table('think_user')->where('id', 1)->findOrFail();
~~~
如果没有查找到数据,则会抛出一个`think\db\exception\DataNotFoundException`异常。
### 查询多行数据
查询多行数据使用`select`方法:
~~~
$list = Db::table('think_user')->where('status', 1)->select();
foreach ($list as $user) {
echo $user['name'];
}
~~~
最终生成的SQL语句是:
~~~
SELECT * FROM `think_user` WHERE `status` = 1
~~~
`select`方法查询结果是一个数据集对象(`think\Collection`),如果需要转换为纯粹的二维数组,可以使用toArray()方法。
~~~
$list = Db::table('think_user')->where('status', 1)->select()->toArray();
foreach ($list as $user) {
echo $user['name'];
}
~~~
如果希望查询数据不存在的时候返回空数组,可以使用`select`。`select`方法查询结果不存在,返回返回空数组(`[]`)。
> 如果你的数据表没有设置表前缀的话,那么`name`和`table`方法是一样的。
> **注意!如果要判断数据集是否为空,不能使用`empty`判断**,必须使用数据集对象的`isEmpty`方法判断,如下。
~~~
$users = Db::name('user')->select();
if($users->isEmpty()){
echo '数据集为空';
}
~~~
### 查询单元格的值
~~~
// 返回某个单元格的值
Db::table('think_user')->where('id', 1)->value('name');
~~~
> `value`方法查询结果不存在,返回`null`。
### 查询若干列的值
~~~
// 返回一列,类型为数组
Db::table('think_user')->where('status',1)->column('name');
// 指定id字段的值作为索引
Db::table('think_user')->where('status',1)->column('name', 'id');
~~~
如果要返回完整数据,并且添加一个索引值的话,可以使用
~~~
// 指定id字段的值作为索引 返回所有数据
Db::table('think_user')->where('status',1)->column('*','id');
~~~
> `column`方法查询结果不存在,返回空数组(`[]`)。
## 新增数据
可以使用`insert`方法向数据库明确新增一条数据
~~~
$data = ['foo' => 'bar', 'bar' => 'foo'];
Db::name('user')->insert($data);
~~~
> `insert`方法添加数据成功返回添加成功的条数,通常情况返回 1。
如果你的数据表里面没有`foo`或者`bar`字段,那么就会抛出异常。如果不希望抛出异常,可以通过下面的方法关闭严格模式,丢弃不存在的字段:
~~~
$data = ['foo' => 'bar', 'bar' => 'foo'];
Db::name('user')->strict(false)->insert($data);
~~~
如果你的数据表采用了自增主键,并且添加数据后如果需要返回新增数据的自增主键,可以使用`insertGetId`方法新增数据并返回主键值:
~~~
$userId = Db::name('user')->insertGetId($data);
~~~
## 添加多条数据
添加多条数据直接使用`insertAll`方法传入需要添加的数据(通常是二维数组)即可。
~~~
$data = [
['foo' => 'bar', 'bar' => 'foo'],
['foo' => 'bar1', 'bar' => 'foo1'],
['foo' => 'bar2', 'bar' => 'foo2']
];
Db::name('user')->insertAll($data);
~~~
> `insertAll`方法添加数据成功返回添加成功的条数
### 更新数据
使用`update`方法更新数据,数据最好明确包含主键数据。
~~~
Db::name('user')->update(['id' => 1, 'name' => 'thinkphp']);
~~~
生成的SQL语句:
~~~
UPDATE `think_user` SET `name`='thinkphp' WHERE `id` = 1
~~~
更新的数据如果不包含主键,则必须指定更新条件,例如:
~~~
Db::name('user')
->where('id' ,1)
->update(['name' => 'thinkphp']);
~~~
> `update`方法返回影响数据的条数,没修改任何数据则返回 0。
如果要更新的数据需要使用`SQL`函数,可以使用下面的方式:
~~~
Db::name('user')
->where('id', 1)
->update([
'name' => Db::raw('UPPER(name)'),
'score' => Db::raw('score-3'),
'read_time' => Db::raw('read_time+1')
]);
~~~
## 删除数据
~~~
// 根据主键删除
Db::table('think_user')->delete(1);
Db::table('think_user')->delete([1, 2, 3]);
// 条件删除
Db::table('think_user')->where('id', 1)->delete();
Db::table('think_user')->where('id', '<', 10)->delete();
~~~
最终生成的SQL语句是:
~~~
DELETE FROM `think_user` WHERE `id` = 1
DELETE FROM `think_user` WHERE `id` IN (1,2,3)
DELETE FROM `think_user` WHERE `id` = 1
DELETE FROM `think_user` WHERE `id` < 10
~~~
> `delete`方法返回影响数据的条数,没有删除任何数据返回 0。
出于安全考虑,如果不带任何条件调用`delete`方法会提示错误,如果你确实需要删除所有数据,可以使用:
~~~
// 无条件删除所有数据
Db::name('user')->delete(true);
~~~
## 使用原生SQL语句
### `query`方法
`query`方法用于执行`SQL`查询操作,和`select`方法一样返回查询结果数据集(数组)。
~~~
Db::query('select * from think_user where status=1');
~~~
### Db::raw 方法
~~~
$conditions = [['hit', '>', 0], ['', 'exp', Db::raw('CHAR_LENGTH(id)=3')]];
$data = Db::name('sys_words')->where($conditions)->limit(2)->select();
位运算的使用方法:
~~~
$map = \[\['', 'exp', Db::raw('role & (1<< 3 )')\]\];
~~~
注意 `CONCAT_WS `与 `CONCAT`对 `NULL`的区别:
~~~
$map\[\] = \['', 'exp', Db::raw('CONCAT\_WS(",",`GCDM`,`XMDM`) LIKE "%' . $param\['code'\] . '%"')\];
~~~
// view 方法比较 特殊
$map = [Db::raw('CHAR_LENGTH(id)=3')]; //必须是数组
$data = Db::view('sys_words', '*')->where('hit', '>', 0)->where($map)->limit(2)->select();
~~~
### `execute`方法
`execute`用于更新和写入数据的sql操作,如果数据非法或者查询错误则返回`false`,否则返回影响的记录数。
~~~
Db::execute("update think_user set name='thinkphp' where status=1");
~~~
### `query`方法
~~~
Db::query('select * from users where id=1');
~~~
> 对数据表的CURD操作,除了`select`和存储过程调用使用`query`方法之外,其它的操作都使用`execute`方法。
### 参数绑定
支持在原生查询的时候使用参数绑定,包括问号占位符或者命名占位符,例如:
~~~
Db::query("select * from think_user where id=? AND status=?", [8, 1]);
// 命名绑定
Db::execute("update think_user set name=:name where status=:status", ['name' => 'thinkphp', 'status' => 1]);
~~~
> 注意不支持对表名使用参数绑定
### SQL调试
~~~
Db::name('users')->getLastSql();
~~~
在模型操作中 ,为了更好的查明错误,经常需要查看下最近使用的SQL语句,可以用`getLastsql`方法来输出上次执行的sql语句。例如:
~~~
User::get(1);
echo User::getLastSql();
~~~
输出结果是`SELECT * FROM 'think_user' WHERE 'id' = 1`
> `getLastSql`方法只能获取最后执行的`SQL`记录。
也可以使用`fetchSql`方法直接返回当前的查询SQL而不执行,例如:
~~~
echo User::fetchSql(true)->find(1);
~~~
输出的结果是一样的。
- 搭建ThinkPHP6的开发环境
- 配置ThinkPHP6
- 必要的基础知识(basic)
- MVC开发模式
- 控制器(controller)
- 数据库(database)
- 模型(model)
- 模型关联(relation)
- 视图(view)
- Session
- Cookie
- 缓存(cache)
- 上传(upload)
- 验证器(validate)
- 验证码(captcha)
- 命令行(command)
- 服务器部署(deploy)
- 数据备份(backup)
- 数据同步(synchronization)
- 订阅服务(subscribe)
- PHP 易混淆知识点
- 助手函数
- MySQL规范
- Redis 规范
- office插件 phpoffice
- 拼音插件 pinyin
- 日期插件 datetime
- 消息插件 amqp
- 产品部署环境的搭建
- PDF 等杂项处理
- 文件上传
- 常用扩展
- flc/dysms
- 使用示例 ①
- 使用示例 ②
- qiniu/php-sdk
- 简介
- 使用示例
- 使用示例 2 ②
- liliuwei/thinkphp-jump
- 扩展介绍
- 下载扩展
- 使用方法
- topthink/think-captcha
- 安装扩展
- 验证码显示
- 更换验证码
- 验证码校验
- 验证码配置
- 自定义验证码
- phpoffice/phpspreadsheet
- 数据写入表格
- 读取表格数据
- topthink/think-queue
- 安装
- 自定义函数
- 任务类
- 带有日志的任务类