ThinkPHP8学习篇(五):数据库(一)
在请求流程中,数据库是数据存储与交互的核心载体,所有业务数据的持久化、读取与变更都依赖于数据库操作。而数据库连接是开展一切数据库操作的前提,查询构造器则为基础的增删改查提供了便捷、安全的实现方式。所以本篇作为数据库系列文章的第一篇,学习的核心内容将集中在数据库连接配置、查询构造器的基本使用以及基础的新增、查询、更新、删除操作上。本篇文章将记录 ThinkPHP 数据库连接与查询构造器基础增删改查的学习过程。
一、连接数据库
1、配置文件
2、切换连接
3、模型类定义
4、配置参数参考
5、关闭连接
二、查询构造器
1、查询数据
1.1、查询单个数据
1.2、查询数据集
1.3、值和列查询
1.4、游标查询
2、添加数据
2.1、添加一条数据
2.2、添加多条数据
3、更新数据
4、删除数据
一、连接数据库
数据库和模型操作采用独立的 ThinkORM 库,默认安装应用的时候会自动安装。
如果应用需要使用数据库,必须配置数据库连接信息。
1、配置文件
在配置目录(config)下面的 database.php 中(后面统称为数据库配置文件)配置数据库参数:
return [// 默认使用的数据库连接配置'default' => env('DB_DRIVER', 'mysql'),// 自定义时间查询规则'time_query_rule' => [],// 自动写入时间戳字段// true为自动识别类型 false关闭// 字符串则明确指定时间字段类型 支持 int timestamp datetime date'auto_timestamp' => true,// 时间字段取出后的默认时间格式'datetime_format' => 'Y-m-d H:i:s',// 时间字段配置 配置格式:create_time,update_time'datetime_field' => '',// 数据库连接配置信息'connections' => ['mysql' => [// 数据库类型'type' => env('DB_TYPE', 'mysql'),// 服务器地址'hostname' => env('DB_HOST', '127.0.0.1'),// 数据库名'database' => env('DB_NAME', ''),// 用户名'username' => env('DB_USER', 'root'),// 密码'password' => env('DB_PASS', ''),// 端口'hostport' => env('DB_PORT', '3306'),// 数据库连接参数'params' => [],// 数据库编码'charset' => env('DB_CHARSET', 'utf8mb4'),// 数据库表前缀'prefix' => env('DB_PREFIX', ''),// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)'deploy' => 0,// 数据库读写是否分离 主从式有效'rw_separate' => false,// 读写分离后 主服务器数量'master_num' => 1,// 指定从服务器序号'slave_no' => '',// 是否严格检查字段是否存在'fields_strict' => true,// 是否需要断线重连'break_reconnect' => false,// 监听SQL'trigger_sql' => env('APP_DEBUG', true),// 开启字段缓存'fields_cache' => false,],// 更多的数据库配置信息],
];
采用多类型的方式配置,方便切换数据库。
default 配置用于设置默认使用的数据库连接配置(默认使用的连接配置为 mysql)。
connections 配置具体的数据库连接信息,default 配置参数定义的连接配置必须要存在。
connections 配置连接信息中的 type 参数用于指定数据库类型,可以指定的数据库类型如下表所示:
type | 数据库 |
mysql | MySQL |
sqlite | SqLite |
pgsql | PostgreSQL |
sqlsrv | SqlServer |
mongo | MongoDb |
oracle | Oracle |
2、切换连接
我们可以在数据库配置文件中定义多个连接信息:
return ['default' => 'mysql','connections' => ['mysql' => [// 数据库类型'type' => 'mysql',// 服务器地址'hostname' => '127.0.0.1',// 数据库名'database' => 'thinkphp',// 数据库用户名'username' => 'root',// 数据库密码'password' => '',// 数据库连接端口'hostport' => '',// 数据库连接参数'params' => [],// 数据库编码默认采用utf8'charset' => 'utf8',// 数据库表前缀'prefix' => 'think_',],'demo' => [// 数据库类型'type' => 'mysql',// 服务器地址'hostname' => '127.0.0.1',// 数据库名'database' => 'demo',// 数据库用户名'username' => 'root',// 数据库密码'password' => '',// 数据库连接端口'hostport' => '',// 数据库连接参数'params' => [],// 数据库编码默认采用utf8'charset' => 'utf8',// 数据库表前缀'prefix' => 'think_',],],
];
可以调用 Db::connect(连接名称) 方法动态配置数据库连接信息,例如:
\think\facade\Db::connect('demo')->table('user')->where('id', 10)->find();
connect 方法必须在查询的最开始调用,而且必须紧跟着调用查询方法,否则可能会导致部分查询失效或者依然使用默认的数据库连接。
动态连接数据库的 connect 方法仅对当次查询有效。
3、模型类定义
如果某个模型类里面定义了 connection 属性的话,则该模型操作的时候会自动按照给定的数据库配置进行连接,而不是配置文件中设置的默认连接信息,例如:
<?php
namespace app\index\model;use think\Model;class User extends Model
{protected $connection = 'demo'; // 使用的数据库连接是 demo 的配置
}
需要注意的是,ThinkPHP 的数据库连接是惰性的,所以并不是在实例化的时候就连接数据库,而是在有实际的数据操作的时候才会去连接数据库。
4、配置参数参考
参数名 | 描述 | 默认值 |
type | 数据库类型 | 无 |
hostname | 数据库地址 | 127.0.0.1 |
database | 数据库名称 | 无 |
username | 数据库用户名 | 无 |
password | 数据库密码 | 无 |
hostport | 数据库端口号 | 无 |
dsn | 数据库连接dsn信息 | 无 |
params | 数据库连接参数 | 空 |
charset | 数据库编码 | utf8 |
prefix | 数据库的表前缀 | 无 |
deploy | 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) | 0 |
rw_separate | 数据库读写是否分离 主从式有效 | false |
master_num | 读写分离后 主服务器数量 | 1 |
slave_no | 指定从服务器序号 | 无 |
fields_strict | 是否严格检查字段是否存在 | true |
fields_cache | 是否开启字段缓存 | false |
trigger_sql | 是否开启SQL监听 | true |
auto_timestamp | 自动写入时间戳字段 | false |
query | 指定查询对象 | think\db\Query |
5、关闭连接
在常驻内存的服务模式下使用 ThinkORM,我们可能需要手动关闭连接。close() 方法用于关闭数据库连接。
Db::connect()->close();
二、查询构造器
查询构造器是ORM中最核心的部分,ThinkORM 查询构造器为数据库查询提供了统一、安全、灵活并且优雅的查询机制,内置支持PDO参数绑定,可用于执行大多数数据库的CURD操作,并封装了包括聚合查询、分页查询、时间查询等常用查询方法,还可以配合原生查询语句完成复杂的查询。
在使用查询构造器的时候,需要使用门面调用:
use think\facade\Db;
1、查询数据
在使用查询构造器的时候,确保已经引入了门面类(后续的代码中不再做引入的说明,但是不要忘记引入):
use think\facade\Db;
1.1、查询单个数据
find 方法用于查询单个数据。查询结果不存在,返回 null,否则返回结果数组。
// table 方法必须指定完整的数据表名
// 查询 user 表中 id=1 的数据
Db::table('user')->where('id', 1)->find();
// 生成的SQL语句是:SELECT * FROM `user` WHERE `id` = 1 LIMIT 1
如果希望查询数据不存在的时候返回空数组而不是 null,可以使用 findOrEmpty 方法:
Db::table('user')->where('id', 1)->findOrEmpty();
注意:如果没有任何的查询条件并且也没有调用 order 方法的话 ,find 查询不会返回任何结果。
1.2、查询数据集
select 方法用于查询多个数据(数据集)。
// 查询 user 表中 status=1 的数据
Db::table('user')->where('status', 1)->select();
select 方法查询结果是一个数据集对象(think\Collection),如果需要转换为数组可以使用 toArray 方法:
Db::table('user')->where('status', 1)->select()->toArray();
/* 返回的数据格式为:[['id'=>1, 'name'='zhangsan', age=18, status=1, ...],['id'=>2, 'name'='lisi', age=25, status=1, ...],]
*/
如果希望在没有查找到数据后抛出异常可以使用 selectOrFail 方法:
Db::table('user')->where('status', 1)->selectOrFail();
没有查找到数据,会抛出一个 think\db\exception\DataNotFoundException 异常。
在 find 和 select 方法之前可以使用所有的链式操作(在后续的文章中记录)方法。
1.3、值和列查询
value 方法用于查询某个字段的值。查询结果不存在,返回 null。
// 返回某个字段的值
Db::table('user')->where('status', 1)->value("name");
column 方法用于查询某一列的值。查询结果不存在,返回空数组。
// 返回数组
Db::table('user')->where('status', 1)->column("name");
// 指定 id 字段的值作为索引,此时返回数组的索引为 user 表中 id 字段的值
Db::table('user')->where('status', 1)->column("name", "id");
如果要返回所有列数据,并且添加一个索引值的话,可以在 column 方法中传入 "*":
// // 指定 id 字段的值作为索引 返回所有数据
Db::table('user')->where('status', 1)->column("*", "id");
值查询和列查询的区别:
- 值查询(value):始终返回单个值(不是数组),且仅返回查询结果中第一条记录的指定字段值。即使查询条件能匹配多条记录,也只会取第一条的对应字段值。
- 列查询(column):返回一个数组,数组中包含查询结果里所有记录的指定字段值(可能是一个或多个值)。如果查询结果为空,会返回空数组;如果只有一条记录,会返回包含一个元素的数组。
1.4、游标查询
如果需要处理大量的数据,可以使用新版提供的游标查询功能,该查询方式利用了PHP的生成器特性,可以大幅减少大量数据查询的内存开销问题。
cursor 方法用于游标查询,返回一个生成器对象。
$cursor = Db::table('user')->where('status', 1)->cursor();
foreach($cursor as $user)
{echo $user['id'] . ': ' . $user['name'] . '<br>';
}
2、添加数据
2.1、添加一条数据
save 方法用于统一写入数据,自动判断是新增还是更新数据(以写入数据中是否存在主键数据为判断依据)。
$data = ['name' => 'wangwu', 'age' => 17, 'status' => 1]; // 保存的数据
Db::table('user')->save($data);
也可以使用 insert 方法向数据库写入数据。写入数据成功返回写入成功的条数,通常情况返回 1。
$data = ['name' => 'wangwu', 'age' => 17, 'status' => 1];
Db::table('user')->insert($data);
添加数据后如果需要返回新增数据的自增主键,可以使用 insertGetId 方法新增数据并返回主键值:
$data = ['name' => 'wangwu', 'age' => 17, 'status' => 1];
$userId = Db::table('user')->insertGetId($data);
2.2、添加多条数据
insertAll 方法用于向数据库写入多条数据,传入需要写入的数据(通常是二维数组)即可。写入数据成功返回写入成功的条数。
$data = [['name' => 'wangwu', 'age' => 17, 'status' => 1],['name' => 'wangwu1', 'age' => 18, 'status' => 1],['name' => 'wangwu2', 'age' => 19, 'status' => 1],
];
Db::table('user')->insertAll($data);
如果批量插入的数据比较多,可以指定分批插入,使用 limit(数量) 方法指定每次插入的数量限制。
$data = [['name' => 'wangwu', 'age' => 17, 'status' => 1],['name' => 'wangwu1', 'age' => 18, 'status' => 1],['name' => 'wangwu2', 'age' => 19, 'status' => 1],...
];
// 分批写入 每次最多100条数据
Db::table('user')->limit(100)->insertAll($data);
3、更新数据
可以使用 save 方法更新数据。
// 更新的数据中需要包含更新数据的主键id
$data = ['id' => 1, 'name' => '张三'];
Db::table('user')->save($data);
也可以使用 update 方法更新数据。update 方法返回影响数据的条数,没修改任何数据返回 0。
Db::table('user')->where('id', 1)->update(['name' => 'zhangsan']); // 以关联数组的形式传入更改的数据
如果更新的数据中包含主键,可以直接在 update 方法中使用:
Db::table('user')->update(['name' => 'zhangsan', 'id' => 1]); // 和上面示例的效果是一样的
如果要更新的数据需要使用SQL函数或者其它字段,可以使用 exp 方法:
Db::table('user')->where('id', 1) // 设置条件->exp('name', 'UPPER(name)') // 设置更新字段与数据,可以使用SQL函数或者其它字段->update(); // 调用更新方法
支持使用 raw 方法进行数据更新,它允许在查询中直接使用原生SQL片段。
Db::table('user')->where('id', 1)->update(['name' => Db::raw('UPPER(name)'), // SQL片段:name 字段的值更改为大写'age' => Db::raw('age + 2') // SQL片段:age 字段的值在原有基础上加2]);
注意:使用 Db::raw 时,需要特别注意安全性问题,尤其是SQL注入的风险。因为 Db::raw 允许直接插入原生SQL片段,如果这些片段来自于用户输入或其他不可信的源,那么它们可能会包含恶意SQL代码,从而导致SQL注入攻击。
4、删除数据
delete 方法用于删除数据。可以直接向该方法传入删除数据的主键,也可以根据条件进行数据删除。delete 方法返回影响数据的条数,没有删除返回 0。
// 根据主键删除
Db::table('user')->delete(1); // 根据一个主键进行删除
Db::table('user')->delete([1, 2, 3]); // 如果删除的数据为多条,那么传入的为主键数组// 条件删除
Db::table('user')->where('id', 1)->delete(); // 删除 id=1 的数据
Db::table('user')->where('id', '<', 10)->delete(); // 删除 id<10 的数据
如果确定需要删除数据表中所有数据,可以向 delete 方法中传入 true 参数。
Db::table('user')->delete(true);
注意:如果不带任何条件调用 delete 方法会提示错误。
一般情况下,业务数据是不建议真实删除的,ThinkPHP 提供了软删除机制。useSoftDelete 方法用于软删除。
// 软删除数据 使用 status 字段标记删除,status=1 为删除数据
Db::table('user')->where('id', 1)->useSoftDelete('status', 1)->delete();