Yii2学习之DAO和Query
简单介绍
上节介绍了AR类,是以面向对象的方式操作数据库,yii2还提供了一套统一的数据库访问层API(Data Access Objects),可以以SQL语句方式操作数据库表,能完成AR类的任何操作,但是相对与AR使用DAO需要写大量的sql语句,如果你的sql语句很复杂,会在后续工作中很难维护,所以一般建议还是使用AR类。但是不管DAO还是AR他们都是通过Query基类创建sql语句(AR类通过创建AQ对象操作),最后都是通过Command类的queryInternal方法的PDO操作返回查询的数据。
源码解析
基本的DAO操作
$query = Yii::$app->db->createCommand(“select * from oc_user where id= :id", [":id" => $id])->queryOne();
Query查询返回数组
$query = (new Query())
->select("*")
->from("oc_user")
->where("id = :id", [":id" => $id])
->one();
上面两种格式返回oc_user中id为指定
public function createCommand($sql = null, $params = [])
{
$command = new Command([
"db" => $this,
"sql" => $sql,
]);
return $command->bindValues($params);
}
createCommand只是创建了个Command类,将db连接实例对象和sql查询语句保存到对应的属性中,返回创建的Command类对象。
public $db;
private $_sql;
然后调用Command类的queryOne方法调用到queryInternal方法返回查询语句对应的数组数据(查询成功)
protected function queryInternal($method, $fetchMode = null)
{
$rawSql = $this->getRawSql();
Yii::info($rawSql, "yiidbCommand::query");
if ($method !== "") {
$info = $this->db->getQueryCacheInfo($this->queryCacheDuration, $this->queryCacheDependency);
if (is_array($info)) {
/* @var $cache yiicachingCache */
$cache = $info[0];
$cacheKey = [
__CLASS__,
$method,
$fetchMode,
$this->db->dsn,
$this->db->username,
$rawSql,
];
$result = $cache->get($cacheKey);
if (is_array($result) && isset($result[0])) {
Yii::trace("Query result served from cache", "yiidbCommand::query");
return $result[0];
}
}
}
$this->prepare(true);//该方法中执行$pdo->prepare($sql);
$token = $rawSql;
try {
Yii::beginProfile($token, "yiidbCommand::query");
$this->pdoStatement->execute();
if ($method === "") {
$result = new DataReader($this);
} else {
if ($fetchMode === null) {
$fetchMode = $this->fetchMode;
}
$result = call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode);
$this->pdoStatement->closeCursor();
}
Yii::endProfile($token, "yiidbCommand::query");
} catch (Exception $e) {
Yii::endProfile($token, "yiidbCommand::query");
throw $this->db->getSchema()->convertException($e, $rawSql);
}
if (isset($cache, $cacheKey, $info)) {
$cache->set($cacheKey, [$result], $info[1], $info[2]);
Yii::trace("Saved query result in cache", "yiidbCommand::query");
}
return $result;
}
真正执行查询语句是在$this->pdoStatement->execute();
后,queryOne方法的$method是fetch,固在call_user_func_array中调用PDO.php的fetch方法采集查询的数据,最后以数组形式返回。
Query的查询与createCommand类似,只不过中间多了步QueryBuilder的解析,解析每一个方法中字段的值组合成sql语句,最后还是调用Command对象的queryOne方法去查询。
public function select($columns, $option = null)
{
if (!is_array($columns)) {
$columns = preg_split("/s*,s*/", trim($columns), -1, PREG_SPLIT_NO_EMPTY);
}
$this->select = $columns;
$this->selectOption = $option;
return $this;
}
public function where($condition, $params = [])
{
$this->where = $condition;
$this->addParams($params);
return $this;
}
public function one($db = null)
{
return $this->createCommand($db)->queryOne();
}
public function createCommand($db = null)
{
if ($db === null) {
$db = Yii::$app->getDb();
}
list ($sql, $params) = $db->getQueryBuilder()->build($this);
return $db->createCommand($sql, $params);
}
Query中的每个操作方法都是返回$this对象本身(select,from,where等等),在one方法时创建了QueryBuiler类通过build方法解析Query对象的数据,然后执行db连接对象的createCommand将sql语句和db对象复制给Command对象,然后调用Command对象的queryOne执行查询sql语句返回结果,后面流程同上。
常用操作
//返回id为1对应的行
$user = Yii::$app->db->createCommand("SELECT * FROM oc_user WHERE id=1")->queryOne();
//返回oc_user表的所有数据
$user = Yii::$app->db->createCommand("SELECT * FROM oc_user")->queryAll();
//返回oc_user表的name列
$user = Yii::$app->db->createCommand("SELECT name FROM oc_user")->queryColumn();
//返回id为1对应行的第一列的值
$user = Yii::$app->db->createCommand("SELECT * FROM oc_user WHERE id=1")->queryScalar();
//删除id为1的一行1
Yii::$app->db->createCommand()->delete("oc_user", "id = 1")->execute();
//引用变量时不要直接写到sql语句中,安全性
$user = Yii::$app->db->createCommand("SELECT * FROM oc_user WHERE id=$id")->queryOne();
不安全,如果别人攻击你服务器,$id传入一个delete sql语句很有可能会把你的数据表删除掉
$user = Yii::$app->db->createCommand("SELECT * FROM oc_user WHERE id=:id", [":id" => $id])->queryOne();
改成这种格式,createCommand会以bindValues绑定参数,在调用PDO操作时是会从_pendingParams读取相应的值以bindValue操作查询。(这个要看PDO.php的bindValue这个处理原理,应该会去做检测,有时间可以研究下)
public function bindValues($values)
{
if (empty($values)) {
return $this;
}
$schema = $this->db->getSchema();
foreach ($values as $name => $value) {
if (is_array($value)) {
$this->_pendingParams[$name] = $value;
$this->params[$name] = $value[0];
} else {
$type = $schema->getPdoType($value);
$this->_pendingParams[$name] = [$value, $type];
$this->params[$name] = $value;
}
}
return $this;
}
//事物操作,执行多个数据操作时,封装到一个事物中可以保证数据的一致性,如果中间操作失败可以回滚到操作之前的位置,防止数据出现一个保存,一个失败等问题。
$transaction = $connection->beginTransaction();
try {
$connection->createCommand($sql1)->execute();
$connection->createCommand($sql2)->execute();
// ... 执行其他 SQL 语句 ...
$transaction->commit();
} catch(Exception $e) {
$transaction->rollBack();
}
Query高级查询
query的where方法支持多种格式(1、纯字符串 2、哈希格式 3、操作符格式)
//字符串
->Where("state = 1")
//哈希格式
->Where(["state" => 1])
//操作符格式
->Where(["in", "state", [1,2,3]])
//常用操作符格式
->where(["between", "state", 1, 10])
->where(["and", "id=1", "type=1"])
->filterWhere(["like", "name", "tester"])
->andWhere([">", "age", 10])
//order by id ASC
->orderBy("id ASC")
//group by 分组查询
->groupBy(["id", "status"]);
//过滤
->having(["status" => 1]);
//从第20条开始读取10条数据
->limit(10)->offset(20);
//从第10条开始读取20条数据
->limit("10, 20")
//联合查找
->leftJoin("post", "post.user_id = user.id");
//union组合
$query1 = (new yiidbQuery())
->select("id, category_id AS type, name")
->from("post")
->limit(10);
$query2 = (new yiidbQuery())
->select("id, type, name")
->from("user")
->limit(10);
$query1->union($query2);
最后调用->one(); ->all();
执行查询操作返回需要的数据。
//批量处理,一次获取一条查询
foreach ($query->each() as $user)
例子解析:
$subQuery = (new Query())
->select("*")
->from("oc_task_state_log")
->where(["in", "from_state", [OcButlerTask::BUTLER_TASK_ASSIGN, OcButlerTask::BUTLER_TASK_CONTACT, OcButlerTask::BUTLER_TASK_UNSUCCESS_POTENTIAL, OcButlerTask::BUTLER_TASK_CHANGE_TIME]])
->andWhere(["in", "to_state", [OcButlerTask::BUTLER_TASK_EARNEST_MONEY, OcButlerTask::BUTLER_TASK_DIRECT]])
->andWhere(["between", "create_at", $start_time, $end_time]);
$query = (new Query())
->select("a.create_at, a.task_id, oc_butler.nick_name as nick_name")
->from(["a" => $subQuery])
->leftJoin("oc_butler_task", "a.task_id = oc_butler_task.id")
->leftJoin("oc_earnest_money", "oc_earnest_money.id = oc_butler_task.earnest_money_id")
->leftJoin("oc_butler", "oc_butler.id = oc_butler_task.butler_id");
$query->andWhere("oc_butler.nick_name = :nick_name", [":nick_name" => $params["nick_name"]]);
$query->orderBy("create_at DESC");
foreach($query->each(10) as $task_log){
//...
}