yii2的数据库层设计
Yii2在M层的抽象做的很经典,值得分析理解。
QueryBuilder
不同的数据库引擎(mysql,oracle…)执行相同的SQL,可能语法有一些细微的差异,但是SQL整体语法差别不大,所以会定义一个QueryBuilder基类,它提供一般通用的SQL生成方法,具体每个数据库引擎继承QueryBuilder,如果存在细微差异只需覆盖特定的方法既可。
简单说,QueryBuilder用来构造SQL语句(读SQL和写SQL都可以),但是并不执行它,通过调用它的where,groupBy,orderBy,select,insert等方法传入结构化的语句组成部分,最终由QueryBuilder返回一个字符串的SQL语句。
Query
仅提供查询能力,不支持修改,它也提供where,groupBy,select等方法(注意不支持insert,update这种修改操作),但是它本身没有生成SQL的能力,仅仅是在QueryBulder之上提供一个通用层,最终在调用它的one或者all方法时会将SQL的各个组成部分一次性交给QueryBuilder构造出SQL语句,那么Query对象怎么知道用哪个QueryBuilder呢?
Query对象会通过application->get(“db”)拿到配置的数据库连接connection对象,这个connection对象自然是知道自己连接的是哪种数据库引擎,因此可以通过它拿到对应的queryBuilder对象。具体来说,connection对象创建时会从配置中取到这样的配置:
1 2 3 4 5 6 7 8 9 |
return [ 'components' => [ 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=yii2advanced', 'username' => 'root', 'password' => '', 'charset' => 'utf8', ], |
Connection对象根据dsn知道对应的数据库引擎,从而创建对应的schema对象:
1 2 3 4 5 6 7 8 9 10 11 12 |
public $schemaMap = [ 'pgsql' => 'yii\db\pgsql\Schema', // PostgreSQL 'mysqli' => 'yii\db\mysql\Schema', // MySQL 'mysql' => 'yii\db\mysql\Schema', // MySQL 'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3 'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2 'sqlsrv' => 'yii\db\mssql\Schema', // newer MSSQL driver on MS Windows hosts 'oci' => 'yii\db\oci\Schema', // Oracle driver 'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts 'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts 'cubrid' => 'yii\db\cubrid\Schema', // CUBRID ]; |
而这个Schema对象是每个数据库引擎各自实现的,它们有一个方法可以返回对应的QueryBuilder对象:
1 2 3 4 5 6 7 8 9 10 11 |
/** * @return QueryBuilder the query builder for this connection. */ public function getQueryBuilder() { if ($this->_builder === null) { $this->_builder = $this->createQueryBuilder(); } return $this->_builder; } |
但是需要注意,虽然经过QueryBuilder生成了SQL,但是Query并不是自己直接执行这个SQL,而是将这个任务交给了下面这个类。
Command
这个类需要由connection对象创建的,内部保存了从connection对象取来的pdo数据库连接。command类的定位是直接执行SQL,也提供bind参数的能力,它不再限制SQL是读还是写,这也是为什么Query类不直接使用connection的pdo连接执行读SQL而是把构造来的SQL交给command,因为command类才是负责执行所有SQL的合适角色,职责集中在Command对象是一个合理的设计。
根据这个认识,我们知道Command类可以直接执行裸SQL,读和写都是可以的。
ActiveQuery
之前的Query算一个调度者,它从用户收集查询的组成要素,交给QueryBuilder生成SQL,最后交给Command完成SQL的执行,拿回来的结果都是数组形式的一行一行的数据。
ActiveQuery则是在Query基础上(继承自Query),额外提供了ORM能力,主要包含2点:数据库行映射为对象(就是指ActiveRecord,也是一个model),以及表的关联关系。
我们知道Query的one和all都是返回关联数组形式的结果集,而ActiveRecord则覆盖了这两个方法,为每一行数据生成对应的activeRecord对象并把列值填充到对象里,这样就实现了数据库行和程序对象的自动映射,大概就是这种关系了。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~

嗯