4.1 连接配置与查询构造器


4.1 连接配置与查询构造器

本章从最底一级开始:连接怎么配、SQL 怎么用构造器安全地拼出来、结果集怎么处理与分页。这一节看似基础,实则是整章的地基——模型的所有花式语法最终都会落到这里讲的链式调用上。读完本节,你应当能看着一段链式代码直接说出它生成的 SQL 长什么样。

一、连接配置:一个主库与多个分身

数据库连接集中在 config 目录的 database.php。默认连接之外可以配置任意多个命名连接,查询时指名切换:

// config/database.php return [ 'default' => env('DB_DRIVER', 'mysql'), 'connections' => [ // 主库:业务默认走这里 'mysql' => [ 'type' => 'mysql', 'hostname' => env('DB_HOST', '127.0.0.1'), 'database' => env('DB_NAME', 'bookstore'), 'username' => env('DB_USER', 'root'), 'password' => env('DB_PASS', ''), 'charset' => 'utf8mb4', 'prefix' => 'tp_', // 研发期打开,看 ORM 生成的真实 SQL 'trigger_sql' => true, ], // 第二个连接:报表从库,读写分离的最简形态 'report' => [ 'type' => 'mysql', 'hostname' => env('REPORT_DB_HOST', '127.0.0.1'), 'database' => env('REPORT_DB_NAME', 'bookstore_report'), 'username' => env('REPORT_DB_USER', 'reporter'), 'password' => env('REPORT_DB_PASS', ''), 'charset' => 'utf8mb4', ], ], ];

.env 里放凭据,配置文件里只留结构与默认值——这套分工在第 1 章立过规矩,这里是它第一次真正发力。什么时候值得配第二个连接?典型是报表查询拖慢主库、或接入了另一个既有库。只是「为了规范」而拆连接没有收益,多一个连接多一份运维面。

二、链式查询:条件像搭积木一样拼

查询构造器的核心思想:把一条 SQL 的各个部分拆成可 chaining 的方法调用,框架负责按正确顺序拼装并把所有值绑定成参数(预处理),SQL 注入的门从机制上关死(原理第 7 章展开):

use think\facade\Db; // 心里默念对应 SQL: // SELECT id,title,price FROM tp_book // WHERE status=1 AND price BETWEEN 10 AND 50 // ORDER BY price DESC LIMIT 10 OFFSET 0 $rows = Db::name('book') ->field('id,title,price') ->where('status', 1) ->whereBetween('price', [10, 50]) ->order('price', 'desc') ->limit(10) ->select(); // 复杂条件块:闭包内是同一套链式语法 $rows2 = Db::name('book') ->where(function ($query) { $query->where('title', 'like', '%PHP%') ->whereOr('summary', 'like', '%PHP%'); }) ->where('status', 1) ->select(); // 聚合与分组 $stats = Db::name('order') ->field('user_id, COUNT(*) AS cnt, SUM(amount) AS total') ->group('user_id') ->having('total > 100') ->select(); // 原生表达式:构造器没覆盖到的能力出口 $hot = Db::name('book') ->whereExp('stock', '< warning_line') ->orderRaw('RAND()') ->limit(5) ->select();

Db::name() 自动接表前缀;where 条件方法的家族(whereIn、whereNull、whereTime 等)覆盖了绝大多数日常查询。用构造器的心理检验法:写完后尝试默念生成的 SQL,念不出来说明链式写法自己也不理解——回一眼 trigger_sql 打出的日志对照。

三、构造器还是模型:分界线在哪

场景 用什么 理由
一次性统计、后台报表 Db 构造器 不需要业务对象,拿数组就走
跨库、跨连接的即席查询 Db 指定连接 模型绑定单连接,跨连接不便
业务实体的常规读写 模型 获取器、关联、事件都要靠模型承载
团队有统一仓储约定 按约定 约定优先于个人偏好

一句话分界:要「数据」用构造器,要「对象」用模型。对象意味着有行为(获取器、状态流转)、有关系(关联)、有事件钩子;纯读出来的展示型数据不值得付对象化的成本。

四、数据集与分页:结果的正确打开方式

select() 返回数据集对象(Collection 风格),支持链式加工;模型的 select() 返回模型集合。常用加工与分页:

// 数据集加工:过滤与变换,全是内存操作不再发 SQL $list = Db::name('book')->where('status', 1)->select(); $names = $list->column('title'); // 抽列 $cheap = $list->where('price', '<', 20); // 集合内过滤 $grouped = $list->group('status')->toArray(); // 按字段分组转数组 // 分页查询:一行同时完成 limit 与分页条渲染所需信息 $page = Db::name('book') ->where('status', 1) ->order('id', 'desc') ->paginate([ 'list_rows' => 15, 'query' => request()->only(['kw']), // 翻页时保留搜索词 ]); // JSON 接口里输出分页结构 return json([ 'list' => $page->items(), 'total' => $page->total(), 'page' => $page->currentPage(), ]);

分页方法把总数、页码、每页条数一次取齐;注意 paginate 会为取总数额外发一条 count 查询——列表页性能优化时(第 8 章)这里常是第一刀。

⚠️ 常见坑:where('title', 'like', '%' . $kw . '%') 本身是安全的(值会被参数绑定),但百分号拼接在构造器里语义容易错;把 % 放进参数值里之前先确认意图——到底想匹配什么。

💡 关键直觉:构造器是「SQL 的流式写法」。每加一个链式调用,就在心里给 SQL 草稿补一句;这个习惯养成后,慢查询一眼就能看出缺索引的原因。

五、动手练习:把一条烂 SQL 翻译成构造器

背景:老代码里有一段手拼 SQL,字符串拼接搜索词,满是转义与拼接符号。操作:先在数据库里手工跑通原 SQL 确认语义;逐段翻译成 where/whereBetween/order 链式调用;开 trigger_sql 对照生成结果与原 SQL 的差异;再用构造器重写分页版本。结果示例:翻译后代码行数减半,搜索词天然安全,翻页保留查询参数。解读:翻译练习的价值在于逐段对照——你会清楚看到每个链式方法对应 SQL 的哪个子句,这正是「心里有 SQL」的成型过程。变式:给统计接口加一条「近三十天每日成交额」,用 whereTime 加 group 写出来,并默念它的完整 SQL。

本节要点回顾

  • 连接分层:凭据进 .env,主库与报表库分连接配置,connect('report') 指名切换。
  • 链式即 SQL:每个链式方法对应一个 SQL 子句,写完能默念出完整语句才算掌握。
  • 分界线:要数据用构造器,要对象用模型;跨连接即席查询归构造器。
  • 数据集加工:column、where、group 是内存操作,不再发 SQL,用对地方能省很多查询。
  • 分页有代价:paginate 附带 count 查询,列表页优化先看它。

下一级接力:把「数据」升级成「对象」——模型类的完整设计,包括软删除与自动时间戳。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U