从数据库中获取数据

今天大多数应用都在使用数据库。不论是一个小网站,还是一个大型社交网站,至少其中一部分功能是由数据库驱动的。

Yii引入了三种方法来允许你使用数据库。他们是:

  • Active Record
  • Query Builder
  • SQL via DAO

我们将使用这三种方法从filmfilm_actoractor表中获取数据,并将他们展示在一个列表中。同时,我们将会比较它们的执行时间和内存占用情况,来决定这些方法的使用场景。

准备

  1. 按照官方指南http://www.yiiframework.com/doc-2.0/guide-start-installation.html的描述,使用Composer包管理器创建一个新的应用。
  2. http://dev.mysql.com/doc/index-other.html下载Sakila数据库。
  3. 执行下载好的SQLs;首先是schema,然后是数据。
  4. config/main.php中配置数据库连接,使用Sakila数据库。
  5. 使用Gii为actor和film表创建模型。

如何做…

  1. 创建app/controllers/DbController.php
  1. <?php
  2. namespace app\controllers;
  3. use app\models\Actor;
  4. use Yii;
  5. use yii\db\Query;
  6. use yii\helpers\ArrayHelper;
  7. use yii\helpers\Html;
  8. use yii\web\Controller;
  9. /**
  10. * Class DbController
  11. * @package app\controllers
  12. */
  13. class DbController extends Controller
  14. {
  15. /**
  16. * Example of Active Record usage.
  17. *
  18. * @return string
  19. */
  20. public function actionAr()
  21. {
  22. $records = Actor::find()
  23. ->joinWith('films')
  24. ->orderBy('actor.first_name,
  25. actor.last_name, film.title')
  26. ->all();
  27. return $this->renderRecords($records);
  28. }
  29. /**
  30. * Example of Query class usage.
  31. *
  32. * @return string
  33. */
  34. public function actionQuery()
  35. {
  36. $rows = (new Query())
  37. ->from('actor')
  38. ->innerJoin('film_actor',
  39. 'actor.actor_id=film_actor.actor_id')
  40. ->leftJoin('film',
  41. 'film.film_id=film_actor.film_id')
  42. ->orderBy('actor.first_name, actor.last_name,
  43. actor.actor_id, film.title')
  44. ->all();
  45. return $this->renderRows($rows);
  46. }
  47. /**
  48. * Example of SQL execution usage.
  49. *
  50. * @return string
  51. */
  52. public function actionSql()
  53. {
  54. $sql = 'SELECT *
  55. FROM actor a
  56. JOIN film_actor fa ON fa.actor_id = a.actor_id
  57. JOIN film f ON fa.film_id = f.film_id
  58. ORDER BY a.first_name, a.last_name, a.actor_id,
  59. f.title';
  60. $rows = Yii::$app->db->createCommand($sql)->queryAll();
  61. return $this->renderRows($rows);
  62. }
  63. /**
  64. * Render records for Active Record array.
  65. *
  66. * @param array $records
  67. *
  68. * @return string
  69. */
  70. protected function renderRecords(array $records = [])
  71. {
  72. if (!$records) {
  73. return $this->renderContent('Actor list is empty.');
  74. }
  75. $items = [];
  76. foreach ($records as $record) {
  77. $actorFilms = $record->films
  78. ?
  79. Html::ol(ArrayHelper::getColumn($record->films, 'title')): null;
  80. $actorName = $record->first_name.'
  81. '.$record->last_name;
  82. $items[] = $actorName.$actorFilms;
  83. }
  84. return $this->renderContent(Html::ol($items, [
  85. 'encode' => false,
  86. ]));
  87. }
  88. /**
  89. * Render rows for result of query.
  90. *
  91. * @param array $rows
  92. *
  93. * @return string
  94. */
  95. protected function renderRows(array $rows = [])
  96. {
  97. if (!$rows) {
  98. return $this->renderContent('Actor list is empty.');
  99. }
  100. $items = [];
  101. $films = [];
  102. $actorId = null;
  103. $actorName = null;
  104. $actorFilms = null;
  105. $lastActorId = $rows[0]['actor_id'];
  106. foreach ($rows as $row) {
  107. $actorId = $row['actor_id'];
  108. $films[] = $row['title'];
  109. if ($actorId != $lastActorId) {
  110. $actorName = $row['first_name'].'
  111. '.$row['last_name'];
  112. $actorFilms = $films ? Html::ol($films) : null;
  113. $items[] = $actorName.$actorFilms;
  114. $films = [];
  115. $lastActorId = $actorId;
  116. }
  117. }
  118. if ($actorId == $lastActorId) {
  119. $actorFilms = $films ? Html::ol($films) : null;
  120. $items[] = $actorName.$actorFilms;
  121. }
  122. return $this->renderContent(Html::ol($items, [
  123. 'encode' => false,
  124. ]));
  125. }
  126. }
  1. 这里,我们有三个actions分别对应于三种不同的方法。
  2. 运行上面的db/ardb/querydb/sql三个actions之后,你应该得到了一个展示200个演员和他们演过的1000个电影的树,截图如下:

从数据库中获取数据 - 图1

  1. 在页面底部,提供了关于内存使用和执行时间的信息。运行这段代码的绝对时间可能不同,但相对大小应该是一致的:
方法 内存使用(MB) 执行时间(秒)
Active Record 21.4 2.398
Query Builder 28.3 0.477
SQL(DAO) 27.6 0.481

工作原理…

actionAr方法使用Active Record方法获取了模型的实例。我们使用Gii生成的Actor模型来获取所有的演员,并指定joinWith=>'films'来获取对应的电影,它使用一个简单的查询或者通过关系预先加载,这是由Gii从InnoDB表外键为我们创建的。然后迭代所有的演员和电影,打印出他们的名字。

actionQuery函数使用Query Builder。首先我们使用\yii\db\Query为当前数据库连接创建了一个查询。然后依次加入查询部分fromjoinInnerleftJoin。这些方法自动escape值、表和field名称。\yii\db\Query的函数all()返回了原始数据库的行数组。每一行也是一个数组,索引是field名称。我们将结果传给了renderRows,它负责渲染。

actionSql是一样的,不同的是我们直接传递SQL,而不是一个接着一个。值得一提的是,我们应该使用Yii::app()->db->quoteValue手动escape参数值:

renderRows方法渲染了Query Builder。

renderRecords方法渲染了active records。

方法 Active Record Query Builder SQL(DAO)
语法 能为你处理SQL。
Gii会为你创建模型和关系。
使用完全面向对象风格的模型和整洁的API。
生成一个适当嵌套的模型的数组作为结果。
整洁的API,适于一步步创建查询。
生成原始数据数组作为结果。
适用于复杂的SQL。
手动qoute值和关键字。
不太适用于一步步创建查询。
生成原始数据数组作为结果。
性能 相对于SQL和Query Builder,内存占用率高,执行时间长。 Okay Okay
更多特性 自动quote值和名称。
Behaviors. Before/after hook.
校验。Prototyping select.
自动quote值和名称
适用于 为单个模型更新、删除和创建(当使用form时尤为便利) 适用于大量的数据,并能一步步创建查询。 使用纯SQL进行复杂的查询,并有尽可能好的性能。

更多…

欲了解更多有关Yii操作数据库,参考如下资源: