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 中(后面统称为数据库配置文件)配置数据库参数:

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();