在请求流程中,数据库是数据存储与交互的核心载体,所有业务数据的持久化、读取与变更都依赖于数据库操作。而数据库连接是开展一切数据库操作的前提,查询构造器则为基础的增删改查提供了便捷、安全的实现方式。所以本篇作为数据库系列文章的第一篇,学习的核心内容将集中在数据库连接配置、查询构造器的基本使用以及基础的新增、查询、更新、删除操作上。本篇文章将记录 ThinkPHP 数据库连接与查询构造器基础增删改查的学习过程。
一、连接数据库
数据库和模型操作采用独立的 ThinkORM 库,默认安装应用的时候会自动安装。
如果应用需要使用数据库,必须配置数据库连接信息。
1、配置文件
在配置目录(config)下面的 database.php 中(后面统称为数据库配置文件)配置数据库参数:
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、切换连接
我们可以在数据库配置文件中定义多个连接信息:
php
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(连接名称) 方法动态配置数据库连接信息,例如:
php
\think\facade\Db::connect('demo')
->table('user')
->where('id', 10)
->find();
connect 方法必须在查询的最开始调用,而且必须紧跟着调用查询方法,否则可能会导致部分查询失效或者依然使用默认的数据库连接。
动态连接数据库的 connect 方法仅对当次查询有效。
3、模型类定义
如果某个模型类里面定义了 connection 属性的话,则该模型操作的时候会自动按照给定的数据库配置进行连接,而不是配置文件中设置的默认连接信息,例如:
php
<?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() 方法用于关闭数据库连接。
php
Db::connect()->close();
二、查询构造器
查询构造器是ORM中最核心的部分,ThinkORM 查询构造器为数据库查询提供了统一、安全、灵活并且优雅的查询机制,内置支持PDO参数绑定,可用于执行大多数数据库的CURD操作,并封装了包括聚合查询、分页查询、时间查询等常用查询方法,还可以配合原生查询语句完成复杂的查询。
在使用查询构造器的时候,需要使用门面调用:
php
use think\facade\Db;
1、查询数据
在使用查询构造器的时候,确保已经引入了门面类(后续的代码中不再做引入的说明,但是不要忘记引入):
php
use think\facade\Db;
1.1、查询单个数据
find 方法用于查询单个数据。查询结果不存在,返回 null,否则返回结果数组。
php
// table 方法必须指定完整的数据表名
// 查询 user 表中 id=1 的数据
Db::table('user')->where('id', 1)->find();
// 生成的SQL语句是:SELECT * FROM `user` WHERE `id` = 1 LIMIT 1
如果希望查询数据不存在的时候返回空数组而不是 null,可以使用 findOrEmpty 方法:
php
Db::table('user')->where('id', 1)->findOrEmpty();
注意:如果没有任何的查询条件并且也没有调用 order 方法的话 ,find 查询不会返回任何结果。
1.2、查询数据集
select 方法用于查询多个数据(数据集)。
php
// 查询 user 表中 status=1 的数据
Db::table('user')->where('status', 1)->select();
select 方法查询结果是一个数据集对象(think\Collection),如果需要转换为数组可以使用 toArray 方法:
php
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 方法:
php
Db::table('user')->where('status', 1)->selectOrFail();
没有查找到数据,会抛出一个 think\db\exception\DataNotFoundException 异常。
在 find 和 select 方法之前可以使用所有的链式操作(在后续的文章中记录)方法。
1.3、值和列查询
value 方法用于查询某个字段的值。查询结果不存在,返回 null。
php
// 返回某个字段的值
Db::table('user')->where('status', 1)->value("name");
column 方法用于查询某一列的值。查询结果不存在,返回空数组。
php
// 返回数组
Db::table('user')->where('status', 1)->column("name");
// 指定 id 字段的值作为索引,此时返回数组的索引为 user 表中 id 字段的值
Db::table('user')->where('status', 1)->column("name", "id");
如果要返回所有列数据,并且添加一个索引值的话,可以在 column 方法中传入 "*":
php
// // 指定 id 字段的值作为索引 返回所有数据
Db::table('user')->where('status', 1)->column("*", "id");
值查询和列查询的区别:
- 值查询(value) :始终返回单个值 (不是数组),且仅返回查询结果中第一条记录的指定字段值。即使查询条件能匹配多条记录,也只会取第一条的对应字段值。
- 列查询(column) :返回一个数组 ,数组中包含查询结果里所有记录的指定字段值(可能是一个或多个值)。如果查询结果为空,会返回空数组;如果只有一条记录,会返回包含一个元素的数组。
1.4、游标查询
如果需要处理大量的数据,可以使用新版提供的游标查询功能,该查询方式利用了PHP的生成器特性,可以大幅减少大量数据查询的内存开销问题。
cursor 方法用于游标查询,返回一个生成器对象。
php
$cursor = Db::table('user')->where('status', 1)->cursor();
foreach($cursor as $user)
{
echo $user['id'] . ': ' . $user['name'] . '<br>';
}
2、添加数据
2.1、添加一条数据
save 方法用于统一写入数据,自动判断是新增还是更新数据(以写入数据中是否存在主键数据为判断依据)。
php
$data = ['name' => 'wangwu', 'age' => 17, 'status' => 1]; // 保存的数据
Db::table('user')->save($data);
也可以使用 insert 方法向数据库写入数据。写入数据成功返回写入成功的条数,通常情况返回 1。
php
$data = ['name' => 'wangwu', 'age' => 17, 'status' => 1];
Db::table('user')->insert($data);
添加数据后如果需要返回新增数据的自增主键,可以使用 insertGetId 方法新增数据并返回主键值:
php
$data = ['name' => 'wangwu', 'age' => 17, 'status' => 1];
$userId = Db::table('user')->insertGetId($data);
2.2、添加多条数据
insertAll 方法用于向数据库写入多条数据,传入需要写入的数据(通常是二维数组)即可。写入数据成功返回写入成功的条数。
php
$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(数量) 方法指定每次插入的数量限制。
php
$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 方法更新数据。
php
// 更新的数据中需要包含更新数据的主键id
$data = ['id' => 1, 'name' => '张三'];
Db::table('user')->save($data);
也可以使用 update 方法更新数据。update 方法返回影响数据的条数,没修改任何数据返回 0。
php
Db::table('user')
->where('id', 1)
->update(['name' => 'zhangsan']); // 以关联数组的形式传入更改的数据
如果更新的数据中包含主键,可以直接在 update 方法中使用:
php
Db::table('user')
->update(['name' => 'zhangsan', 'id' => 1]); // 和上面示例的效果是一样的
如果要更新的数据需要使用SQL函数或者其它字段,可以使用 exp 方法:
php
Db::table('user')
->where('id', 1) // 设置条件
->exp('name', 'UPPER(name)') // 设置更新字段与数据,可以使用SQL函数或者其它字段
->update(); // 调用更新方法
支持使用 raw 方法进行数据更新,它允许在查询中直接使用原生SQL片段。
php
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。
php
// 根据主键删除
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 参数。
php
Db::table('user')->delete(true);
注意 :如果不带任何条件调用 delete 方法会提示错误。
一般情况下,业务数据是不建议真实删除的,ThinkPHP 提供了软删除机制。useSoftDelete 方法用于软删除。
php
// 软删除数据 使用 status 字段标记删除,status=1 为删除数据
Db::table('user')
->where('id', 1)
->useSoftDelete('status', 1)
->delete();