写点什么

Laravel Eloquent Builder 的使用、源码分析总结

  • 2019-09-22
  • 本文字数:4762 字

    阅读完需:约 16 分钟

Laravel Eloquent Builder 的使用、源码分析总结

1. 为何要使用 Query Builder

Query builder 的最大好处就是,对于 SQL 的 select from where join group by order by limit offset having on 等关键字都转换为了类的方法,简化了 SQL 的使用成本,大大简化了代码量,原先一些操作数据库相关的一次性的 servicelogic 相关的函数,可以替换为直接 Builder 操作数据库。


Laravel 中关键字都实现在了下面两个类中:


\Illuminate\Database\Query\Builder


\Illuminate\Database\Query\JoinClause

2. 创建库 & Model

接着上篇文章对评论系统设计的一点总结,总结一下 Laravel Eloquent Builder 的一些用法。


首先用下面的 MySQL 语句创建存储评论的数据库表,并生成 Laravel 对应的 Model,用于检索数据库中的数据。


 1CREATE TABLE `Comment` ( 2  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键,评论id', 3  `replied_id` int(11) NOT NULL DEFAULT '0' COMMENT '被评论id', 4  `replied_root_id` int(11) NOT NULL DEFAULT '0' COMMENT '直接评论id', 5  `content` text COMMENT '评论内容', 6  `status` int(11) DEFAULT NULL '评论状态', 7  `from_user_id` int(11) NOT NULL DEFAULT '0' COMMENT '评论人id', 8  `from_user_name` varchar(20) NOT NULL DEFAULT '' COMMENT '评论人姓名', 9  `to_user_id` int(11) DEFAULT '0' COMMENT '被评论人id',10  `to_user_name` varchar(20) NOT NULL DEFAULT '' COMMENT '被评论人姓名',11  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP '创建时间',12  PRIMARY KEY (`id`)13) ENGINE=InnoDB DEFAULT CHARSET=utf8 1<?php 2/** 3 * Created by PhpStorm. 4 * User: yangzhen 5 * Date: 2018/4/3 6 * Time: 20:26 7 */ 8 9namespace App\Model;101112use Illuminate\Database\Eloquent\Model;1314/**15 * App\Model\Comment16 *17 * @property int $id 主键,评论id18 * @property int $replied_id 被评论id19 * @property int $replied_root_id 直接评论id20 * @property string|null $content 评论内容21 * @property int|null $status22 * @property int $from_user_id 评论人id23 * @property string $from_user_name 评论人姓名24 * @property int|null $to_user_id 被评论人id25 * @property string $to_user_name 被评论人姓名26 * @property string $create_at27 * @method static \Illuminate\Database\Eloquent\Builder|\App\Model\Comment whereContent($value)28 * @method static \Illuminate\Database\Eloquent\Builder|\App\Model\Comment whereCreateAt($value)29 * @method static \Illuminate\Database\Eloquent\Builder|\App\Model\Comment whereFromUserId($value)30 * @method static \Illuminate\Database\Eloquent\Builder|\App\Model\Comment whereFromUserName($value)31 * @method static \Illuminate\Database\Eloquent\Builder|\App\Model\Comment whereId($value)32 * @method static \Illuminate\Database\Eloquent\Builder|\App\Model\Comment whereRepliedId($value)33 * @method static \Illuminate\Database\Eloquent\Builder|\App\Model\Comment whereRepliedRootId($value)34 * @method static \Illuminate\Database\Eloquent\Builder|\App\Model\Comment whereStatus($value)35 * @method static \Illuminate\Database\Eloquent\Builder|\App\Model\Comment whereToUserId($value)36 * @method static \Illuminate\Database\Eloquent\Builder|\App\Model\Comment whereToUserName($value)37 */38class Comment extends Model39{40    protected $table = 'Comment';41    protected $primaryKey = 'id';42    public $timestamps = false;4344}
复制代码

3. 使用案例

好,现在假想下面一个场景:


查询直接评论 id 分别为 10,11,12 的最近 7 天、评论内容含有关键字知识、发表评论用户名为 soull11201 或被评论用户名为 soul11201、按照创建时间倒排后的前 10 条数据,并分别计算每个直接评论下面一共含有多少条数据。


粗暴的构造 sql 如下:。


 1-- 10 2select  *  from Comment  3where  4    content  = '知识'  5    and (from_user_name = 'soul11201' or to_user_name = 'soul11201') 6    and  replied_root_id = 10  7    order by create_at desc 8    limit 10; 910select  count(1) replied_root_id10_total_num  from Comment 11where 12    content  = '知识' 13    and (from_user_name = 'soul11201' or to_user_name = 'soul11201')14    and  replied_root_id = 10 1516-- 1117select  *  from Comment 18where 19    content  = '知识' 20    and (from_user_name = 'soul11201' or to_user_name = 'soul11201')21    and  replied_root_id = 11 22    order by create_at desc23    limit 10;2425select  count(1) replied_root_id10_total_num  from Comment 26where 27    content  = '知识' 28    and (from_user_name = 'soul11201' or to_user_name = 'soul11201')29    and  replied_root_id = 11 3031-- 1232select  *  from Comment 33where 34    content  = '知识' 35    and (from_user_name = 'soul11201' or to_user_name = 'soul11201')36    and  replied_root_id = 1237    order by create_at desc38    limit 10;3940select  count(1) replied_root_id10_total_num  from Comment 41where 42    content  = '知识' 43    and (from_user_name = 'soul11201' or to_user_name = 'soul11201')44    and  replied_root_id = 12 
复制代码


根据上面的 sql 构造,转换成如下的 Eloquent Builder 使用的代码:


 1<?php 2/** 3 * Created by PhpStorm. 4 * User: yangzhen 5 * Date: 2018/4/3 6 * Time: 20:46 7 */ 8 910$replied_root_ids = [10, 11, 12];1112//获取一个 \Illuminate\Database\Eloquent\Builder 实例13$query = \App\Model\Comment::query();1415$query->where('content','知识')16    ->where(function (\Illuminate\Database\Eloquent\Builder $builder){17        //$builder 这是一个新的 builder 作为 $query  一个嵌入的查询 builder ,否则的话orWhere 根本无法实现(因为or的优先级问题),18        $builder->where('from_user_name', 'soul11201');19        $builder->orWhere('to_user_name', 'soul11201');20    });212223$coments = [];24$total_num = [];2526foreach ($replied_root_ids as $replied_root_id) {2728    $new_query = \App\Model\Comment::whereRepliedRootId($replied_root_id)29        ->addNestedWhereQuery($query);3031    //此处先用来查询总条数32    $total_num[$replied_root_id] = $new_query->count();33    //然后用来查询10条信息,顺序反之不可。34    $coments[$replied_root_id] = $new_query->orderBy('create_at', 'desc')35        ->limit(10)36        ->get()37        ->all();38}
复制代码

4. 执行流程分析

\Illuminate\Database\Eloquent\Builder::where()


 1    /** 2     * Add a basic where clause to the query. 3     * 4     * @param  string|array|\Closure  $column 5     * @param  string  $operator 6     * @param  mixed  $value 7     * @param  string  $boolean 8     * @return $this 9     */10    public function where($column, $operator = null, $value = null, $boolean = 'and')11    {12        if ($column instanceof Closure) {13            // 返回一个新的 Eloquent Builder14            $query = $this->model->newQueryWithoutScopes();15            //匿名函数调用,16            //当 where 条件有复杂的条件表达式的时候17            //比如解决上面 表达式中 (from_user_name = 'soul11201' or to_user_name = 'soul11201') or 优先级的问题18            //直接使用 where() 无法解决,只能使用一个新的Builder来嵌入到原先的Builder中19            $column($query);20            //$this->query 是类 \Illuminate\Database\Query\Builder 的实例21            //将新的 Eloquent builder 的 Query\Builder 最为一个整体嵌入到原先Eloquent Builder的 `Query\Builder`的where表达式中,22            //就可以解决上面 or 优先级的问题23            $this->query->addNestedWhereQuery($query->getQuery(), $boolean);24        } else {25            $this->query->where(...func_get_args());26        }2728        return $this;29    }
复制代码


mixin


因为 \Illuminate\Database\Eloquent\Builder mixin 类\Illuminate\Database\Query


\Illuminate\Database\Eloquent\Builder::count()


\Illuminate\Database\Eloquent\Builder::orderby()


\Illuminate\Database\Eloquent\Builder::limit()


都是利用魔术方法__call 间接使用的\Illuminate\Database\Query 的方法


 1    /** 2     * The base query builder instance. 3     * 4     * @var \Illuminate\Database\Query\Builder 5     */ 6    protected $query; 7 8    ... 9     此处省略10    ...1112    public function __call($method, $parameters)13    {14        ...15        此处省略16        ...17        $this->query->{$method}(...$parameters);1819        return $this;20    }
复制代码


\Illuminate\Database\Eloquent\Builder::get()


\Illuminate\Database\Eloquent\Builder 是 \Illuminate\Database\Eloquent\Mdel 子类 与\Illuminate\Database\Query\Builder 沟通的桥梁。其中一个作用就是对\Illuminate\Database\Query\Builder 查询的数组结果(由\Illuminate\Support\Collection 进行包裹)渲染成\Illuminate\Database\Eloquent\Model 子类的对象数组结果(由\Illuminate\Database\Eloquent\Collection 进行包裹)。


 1    /** 2     * Execute the query as a "select" statement. 3     * 4     * @param  array  $columns 5     * @return \Illuminate\Database\Eloquent\Collection|static[] 6     */ 7    public function get($columns = ['*']) 8    { 9        //应用其他注入构造条件10        $builder = $this->applyScopes();1112        // If we actually found models we will also eager load any relationships that13        // have been specified as needing to be eager loaded, which will solve the14        // n+1 query issue for the developers to avoid running a lot of queries.15        if (count($models = $builder->getModels($columns)) > 0) {16            $models = $builder->eagerLoadRelations($models);17        }1819        return $builder->getModel()->newCollection($models);20    }2122     /**23     * Get the hydrated models without eager loading.24     *25     * @param  array  $columns26     * @return \Illuminate\Database\Eloquent\Model[]27     */28    public function getModels($columns = ['*'])29    {30        return $this->model->hydrate(31            $this->query->get($columns)->all()32        )->all();33    }
复制代码


Illuminate\Support\Collection::all()


\Illuminate\Database\Eloquent\Collection 是 Illuminate\Support\Collection 的子类,all()方法指向的是同一个方法,直接返回其所包裹的数组。


作者介绍:


杨振振,技术保障部,17 年 8 月加入链家。先后参与过热链计划、移动审批、圣诞大屏、邮箱代理等项目。


本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。


原文链接:


https://mp.weixin.qq.com/s/3ANed5AO1gf4se8g-rrysw


2019-09-22 23:031148

评论

发布
暂无评论
发现更多内容

天才第一步!Java架构速成笔记必备精品成就年薪百万,掌门一对一java面试流程

Java 程序员 后端

架构实战营 - 毕业总结

雪中亮

架构实战营 #架构实战营

太难了,救救孩子吧,到现在还搞不懂TCP的三次握手四次挥手

Java 程序员 后端

如何设计一个百万级用户的抽奖系统?,三面蚂蚁核心金融部

Java 程序员 后端

太狠了,Spring全家桶笔记,一站式通关全攻略,已入职某厂涨薪18K

Java 程序员 后端

如何在分布式系统中正确的使用缓存?别给你的项目引入定时炸弹!

Java 程序员 后端

如何正确使用Spring Cloud Zookeeper,不懂来学,java教程下载网盘

Java 程序员 后端

天真,居然还有人认为java的参数传递方式是引用传递,互联网java工程师面试突击训练网盘

Java 程序员 后端

如何保证Redis与数据库的双写一致性?,十分钟带你看懂Netty如何实现C-S

Java 程序员 后端

字节跳动Java开放岗面经:14天快速面试,已拿offer,Java全套百度云

Java 程序员 后端

太牛了,Github上标星30K+的SpringBoot实战电商项目mall

Java 程序员 后端

如何保证高可用?搞定三种集群模式,Redis还不是手到擒来

Java 程序员 后端

如何快速成长为技术大牛?阿里资深技术专家的总结亮了!

Java 程序员 后端

如何让自己像打王者荣耀一样发了疯、拼了命、石乐志的学习

Java 程序员 后端

如果当时这16道题能答好,现在应该已经被录取了(记一次面试的亲身经历 2020-9-9

Java 程序员 后端

天真,居然还有人认为java的参数传递方式是引用传递(1)

Java 程序员 后端

太厉害了,终于有人把TCP-IP协议整合成864页学习笔记了

Java 程序员 后端

太强了!阿里大神亲码“SpringCloud核心手册,2021Java常见笔试题

Java 程序员 后端

头条「2020最新」Spring最易学习教程,百度java面试经验

Java 程序员 后端

模块二作业

doublechun

「架构实战营」

如何理解互斥锁、条件锁、读写锁以及自旋锁(1),mysql入门到精通电子书

Java 程序员 后端

太为难我了,阿里面试了7轮(5年经验,java百度图像识别接口

Java 程序员 后端

太全了吧!阿里面试官纯手打:金三银四跳槽必会Java核心知识点笔记整理

Java 程序员 后端

太狠了!阿里技术官熬夜半年肝出来的Spring Boot巅峰之作,爱了

Java 程序员 后端

如何在今年难找工作的大环境下成功入职阿里?Java架构师面试高频300题:集合

Java 程序员 后端

如何让阿三 Windows 10、11 的恢复分区(Recovery Partition

Java 程序员 后端

如何阅读一本书-读书笔记,java二到三年经验面试题

Java 程序员 后端

如果当时这15道题能答好,现在应该已经被录取了(记一次面试的亲身经历 2020-7-20

Java 程序员 后端

太难了,面试官不讲武德!来骗来偷袭,分层架构图案例

Java 程序员 后端

如何使用Spring Cloud Consul的其他配置和发现功能,不会来学

Java 程序员 后端

如何在Spring Boot应用中优雅的使用Date和LocalDateTime

Java 程序员 后端

Laravel Eloquent Builder 的使用、源码分析总结_文化 & 方法_杨振振_InfoQ精选文章