Laravel Eloquent使用小记

文章保存地址:https://github.com/wanguowan/notetext-blog/blob/master/Eloquent.md

Laravel Eloquent使用小记

今天因为开发数据库业务中间层需要,开始研究Laravel Eloquent,因为刚开始使用laravel框架的时候,都是使用query,查询构建器来写sql类似于

DB::connection(‘mydb‘)->table(‘mylove‘)
                        ->where( ‘name‘, ‘guowan‘ )
                        ->get();

复杂一点的sql使用db::raw

DB::connection(‘mydb‘)->table(‘mylove‘)->select( DB::RAW( ‘count("name") as mylovecount‘ ) )
                        ->where( ‘name‘, ‘guowan‘ )
                        ->get();

本着在工作中学习的态度开始研究Eloquent,对着laravel中文文档,开始设计Eloquent Model。这里给出表大概字段(因兼容老系统要求,表字段设计与当前业务不相符,这里不与讨论~)

表结构

CREATE TABLE `user_ext` (
  `user_id`     int(10)             NOT NULL,
  `realname`    varchar(255)        DEFAULT NULL,
  `gender`      int(11)             NOT NULL DEFAULT ‘0‘,
  `birthday`    datetime            DEFAULT NULL,
  `comefrom`    varchar(255)        DEFAULT NULL,
  `qq`          varchar(255)        DEFAULT NULL,
  `weibo`       varchar(255)        DEFAULT NULL,
  `blog`        varchar(255)        DEFAULT NULL,
  `mobile`      varchar(255)        DEFAULT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `user` (
  `user_id`     int(10) NOT NULL AUTO_INCREMENT,
  `username`    varchar(100)    DEFAULT NULL,
  `email`       varchar(255)    DEFAULT NULL,
  `user_img`    varchar(255)    DEFAULT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8

创建Eloqueue Model

  • user
<?php

namespace App\Http\Models\Eloquent;

use Illuminate\Database\Eloquent\Model;

class CUser extends Model
{
    /**
     * 与模型关联的数据表。
     *
     * @var string
     */
    protected $table = ‘user‘;

    /*
     * 数据库表主键
     *
     * @var string
     */
    protected $primaryKey = ‘user_id‘;

    /*
     * 取消自动维护create_at,update_at字段
     *
     * @var string
     */
    public $timestamps = false;

    /*
     * 获取与指定用户相关联的扩展信息记录
     */
    public function hasOneExt()
    {
        return $this->hasOne( ‘App\Http\Models\Eloquent\CUserExt‘, ‘user_id‘, ‘user_id‘ );
    }
}
  • user_ext
<?php

namespace App\Http\Models\Eloquent;

use Illuminate\Database\Eloquent\Model;

class CUserExt extends Model
{

    /**
     * 与模型关联的数据表。
     *
     * @var string
     */
    protected $table = ‘ac_user_ext‘;

    /*
     * 数据库表主键
     *
     * @var string
     */
    protected $primaryKey = ‘user_id‘;

    /*
     * 取消自动维护create_at,update_at字段
     *
     * @var string
     */
    public $timestamps = false;

    public function acUser()
    {
        return $this->belongsTo( ‘App\Http\Models\Eloquent\CUser‘ );
    }
}

user与user_ext表为1对1关系。

注意

user model中的hasOneExt方法,之所以使用hasOneExt方法名,是通过方法命名,在调用方法的时候就可以知道和userExt表的关系。

hasOne函数,第一个参数是类路径;第二个参数外键,也就是userExt表的主键;第三个参数才是user表主键。自己使用的时候,没有指定第二个和第三个参数,会出现错误

问题

下面才是今天记录的主要内容,在使用过程中,出现一些问题,以及问题相应的解决方法,可能有些问题还没有解决或者解决的不好,这里记录一下,增加一下印象,也可以和其他同学一块讨论一下

1. 依赖方法hasOneExt

调用下面方法

$oUser = CUser::find( $sUMId )->hasOneExt();

结果竟然返回UserExt表中数据。我的本意本来想做相应的关联查询,查出两个表的数据。然后在网上各种搜索Eloquent两表联查,返回两表字段。

最终解决方案如下:

$oUser = CAcUser::with( ‘hasOneExt‘ )->find( $sUMId );

查询结果:

Array
(
    [user_id] => 1
    [username] => admin
    [email] => [email protected]163.com
    [user_img] => 201303/26132122j2lg.jpg
    [has_one_ext] => Array
        (
            [user_id] => 1
            [realname] => 瞌睡
            [gender] => 1
            [birthday] =>
            [comefrom] => **,不限
            [qq] =>
            [weibo] =>
            [blog] =>
            [mobile] =>
        )

)

这里依赖表数据使用方法名作为key成为返回结果的一部分,这个对于业务接口,需要一维数组的时候还得需要翻译。幸好对于业务层来说,希望屏蔽底层数据层字段细节,本来就需要做一次翻译,所以这里也就不是什么大问题。

这里with语法,是Eloquent中所谓的预加载语法,主要是为了解决ORM(Object Relation Mapping) n+1次查询问题–详细说明。在网上查询过程中,这里虽然是1对1关系,但是如果这样解决,会将一次join查询,变成两次查询,对于将来高并发场景来说,有点不能接受。但是不这样解决又没有找到其他解决方法。

无奈,尝试打印Eloquent执行的sql,查看具体的sql语句(打印laravel执行sql方法比较多,可以参考资料),代码如下:

DB::enableQueryLog();

$oUser = CUser::with( ‘hasOneExt‘ )->find( $sUMId );

print_r(
    DB::getQueryLog()
);

打印结果如下:

Array
(
    [0] => Array
        (
            [query] => select * from `user` where `user`.`user_id` = ? limit 1
            [bindings] => Array
                (
                    [0] => 1
                )

            [time] => 0.56
        )

    [1] => Array
        (
            [query] => select * from `user_ext` where `user_ext`.`user_id` in (?)
            [bindings] => Array
                (
                    [0] => 1
                )

            [time] => 0.32
        )

)

可以看出,sql先根据user_id查询到主标数据,然后在去依赖表中做in查询,这样确实解决了ORM n+1次查询的问题,但是对于直接使用sql,还是多出一次查询。

这里发现一个比较有趣的事情,log里有一个time值,难道这个是sql执行时间,如果这个是执行时间的话,那就可以简单的验证一下sql执行效率问题了,然后开始查询资料,终于在源码中找到了答案,源码如下:详细链接

    /**
     * Run a SQL statement and log its execution context.
     *
     * @param  string   $query
     * @param  array    $bindings
     * @param  Closure  $callback
     * @return mixed
     *
     * @throws QueryException
     */
    protected function run($query, $bindings, Closure $callback)
    {
        $start = microtime(true);
        // To execute the statement, we‘ll simply call the callback, which will actually
        // run the SQL against the PDO connection. Then we can calculate the time it
        // took to execute and log the query SQL, bindings and time in our memory.
        try
        {
            $result = $callback($this, $query, $bindings);
        }
        // If an exception occurs when attempting to run a query, we‘ll format the error
        // message to include the bindings with SQL, which will make this exception a
        // lot more helpful to the developer instead of just the database‘s errors.
        catch (\Exception $e)
        {
            throw new QueryException($query, $bindings, $e);
        }
        // Once we have run the query we will calculate the time that it took to run and
        // then log the query, bindings, and execution time so we will report them on
        // the event that the developer needs them. We‘ll log time in milliseconds.
        $time = $this->getElapsedTime($start);
        $this->logQuery($query, $bindings, $time);
        return $result;
    }

    /**
     * Get the elapsed time since a given starting point.
     *
     * @param  int    $start
     * @return float
     */
    protected function getElapsedTime($start)
    {
        return round((microtime(true) - $start) * 1000, 2);
    }

这里可以看出time就是sql执行时间,而且单位是毫秒.

这里就可以测试单条join和使用eloquent with查询效率对比,代码如下:

DB::enableQueryLog();

DB::table(  ‘user‘ )
    ->leftJoin( ‘user_ext as ext‘, ‘user.user_id‘, ‘=‘, ‘ext.user_id‘ )
    ->where( ‘user.user_id‘, 1 )
    ->get();

$oUser = CUser::with( ‘hasOneExt‘ )->find( $sUMId );

print_r(
    DB::getQueryLog()
);

结果如下:

Array
(
    [0] => Array
        (
            [query] => select * from `user` as `user` left join `user_ext` as `ext` on `user`.`user_id` = `ext`.`user_id` where `user`.`user_id` = ?
            [bindings] => Array
                (
                    [0] => 1
                )

            [time] => 0.65
        )

    [1] => Array
        (
            [query] => select * from `user` where `user`.`user_id` = ? limit 1
            [bindings] => Array
                (
                    [0] => 1
                )

            [time] => 0.35
        )

    [2] => Array
        (
            [query] => select * from `user_ext` where `user_ext`.`user_id` in (?)
            [bindings] => Array
                (
                    [0] => 1
                )

            [time] => 0.35
        )

)

从结果可以看出,执行一条时间相比执行两条时间,差距不是很大,但是客观来说,这说明不了什么问题;首先,测试基于本地数据库,一次请求和两次请求的网络影响及延迟会比线上差距要小很多;其次,本地测试数据库,两个表数据量都在1k,数据量太小,无法反应真实线上数据查询效率。所以这里查询结果仅供参考,后期详细结果,会在本地伪造100w左右数据量进行测试观察,并咨询公司dba,对于大数据量对连表查询效率影响情况。

总结

对于今天解决问题的过程,虽然感觉没有得到完美的答案,但是在查询过程中也学习到不少东西,在这里做一下记录。以备后期温故学习。

这里记录一下几个小细节:

数据库查询过程中,为了节省应用服务器与数据库服务器之间网络流量及数据库服务器的IO,数据库查询原则是只查询返回有用字段,对于无用的大字段,特别是text等,不需要时,尽量不查询。数据库查询尽量不要使用selec *

Eloquent 联合查询指定字段

  • 方法1
$oUser = CUser::with( [ ‘hasOneExt‘ => function( $query ) {
        $query->select( ‘user_id‘, ‘realname‘, ‘gender‘, ‘birthday‘ );
        } ] )->find( $sUMId, [ ‘user_id‘, ‘username‘, ‘email‘, ‘user_img‘ ] );

其中query?>select(′userid′,′realname′,′gender′,′birthday′)为查询扩展表字段;find(sUMId, [ ‘user_id’, ‘username’, ‘email’, ‘user_img’ ] )为查询主表字段

  • 方法2
public function hasOneExt() {
    return $this->hasOne( ‘App\Http\Models\Eloquent\CUserExt‘, ‘user_id‘, ‘user_id‘ )
    ->select( ‘user_id‘, ‘realname‘, ‘gender‘, ‘birthday‘ );
}

$oUser = CUser::with( ‘hasOneExt‘ )->find( $sUMId, [ ‘user_id‘, ‘username‘, ‘email‘, ‘user_img‘ ] );  

执行sql结果:

Array
(
    [0] => Array
        (
            [query] => select `user_id`, `username`, `email`, `user_img` from `user` where `user`.`user_id` = ? limit 1
            [bindings] => Array
                (
                    [0] => 1
                )

            [time] => 0.5
        )

    [1] => Array
        (
            [query] => select `user_id`, `realname`, `gender`, `birthday` from `user_ext` where `user_ext`.`user_id` in (?)
            [bindings] => Array
                (
                    [0] => 1
                )

            [time] => 0.33
        )

)

时间: 2024-12-20 12:35:44

Laravel Eloquent使用小记的相关文章

判断Laravel Eloquent获取数据结果集是否为空

在使用Laravel Eloquent模型时,我们可能要判断取出的结果集是否为空,但我们发现直接使用is_null或empty是无法判段它结果集是否为空的. var_dump之后我们很容易发现,即使取到的空结果集, Eloquent仍然会返回Illuminate\Database\Eloquent\Collection对象实例.其实,Eloquent已经给我们封装几个判断方法. $result = Model::where(...)->get(); //不为空则 if ($result->fi

Laravel Eloquent 判断取出的结果集是否为空

在使用Laravel Eloquent模型时,我们可能要判断取出的结果集是否为空,但我们发现直接使用is_null或empty是无法判段它结果集是否为空的. var_dump之后我们很容易发现,即使取到的空结果集, Eloquent仍然会返回Illuminate\Database\Eloquent\Collection对象实例.其实,Eloquent已经给我们封装几个判断方法. $result = Model::where(...)->get(); //不为空则 if ($result->fi

深入理解 Laravel Eloquent(三)——模型间关系(关联)

Eloquent是什么 Eloquent 是一个 ORM,全称为 Object Relational Mapping,翻译为 "对象关系映射"(如果只把它当成 Database Abstraction Layer 数组库抽象层那就太小看它了).所谓 "对象",就是本文所说的 "模型(Model)":对象关系映射,即为模型间关系.中文文档: http://laravel-china.org/docs/eloquent#relationships 下

laravel Eloquent操作

基本用法 我们先从建立一个 Eloquent 模型开始.模型通常放在 app 目录下,但是您可以将它们放在任何地方,只要能通过 composer.json 自动载入.所有的 Eloquent 模型都继承于Illuminate\Database\Eloquent\Model . 定义一个 Eloquent 模型 class User extends Model {} 你也可以通过 make:model 命令自动生成 Eloquent 模型: php artisan make:model User

laravel Eloquent get获取空的数据问题

在用laravel框架来获取数据,若数据不存在时,以为会返回空,其实不是的,会返回如下: object(Illuminate\Database\Eloquent\Collection)#191 (1) { ["items":protected]=> array(0) { } } $result = User::where('id', 100)->get(); 当 $result 为空时,empty($result) 是不为false的,还是有值的,如上,因此要用 count

Laravel Eloquent ORM--整理

介绍 Laravel 的 Eloquent ORM 提供了漂亮.简洁的 ActiveRecord 实现来和数据库的互动. 每个数据库表会和一个对应的「模型」互动. 在开始之前,记得把 config/database.php 里的数据库连接配置好. 基本用法 我们先从建立一个 Eloquent 模型开始.模型通常放在 app 目录下,但是您可以将它们放在任何地方,只要能通过 composer.json 自动载入.所有的 Eloquent 模型都继承于 Illuminate\Database\Elo

Laravel Eloquent ORM 数据model操作

Laravel ORM 数据model操作 注意:ORM关联操作最后一定要记得带get()方法!否则会获取不到数据,为null 如: posts表 comments表  id id  contents post_id comment post模型内定义一对多关系hasMany 1 //定义关联关系 2 public function comments(){ 3 return $this->hasMany(Comments::class,'post_id','id'); 4 } 1 //容器内调用

Laravel Eloquent—模型间关系(关联)

Eloquent是什么 Eloquent 是一个 ORM,全称为 Object Relational Mapping,翻译为 "对象关系映射"(如果只把它当成 Database Abstraction Layer 数组库抽象层那就太小看它了).所谓 "对象",就是本文所说的 "模型(Model)":对象关系映射,即为模型间关系.中文文档: http://laravel-china.org/docs/eloquent#relationships 下

Laravel Eloquent ORM 一种高级用法

一.需求: <span style="white-space:pre"> </span>在数据搜索时最常见的就是调用同一个方法查询,而查询的字段却可能是其中一个或其中的几个字段一起组合查询,例如:对列表的搜索,基本上都是几个字段随意组合搜索.那么在model里就需要判断有那个字段组合,怎么组合. 网上找了很久,Laravel群里也问了几个,都说没有写过,于是自己写个吧.话不多说,见代码: function findByParam($param = array()