Yii2 数据操作指引
数据库访问对象(Database Access Objects)Yii 包含了一个建立在 PHP PDO 之上的数据访问层 (DAO)DAO为不同的数据库提供了一套统一的APIActiveRecord 提供了数据库与模型(MVC 中的 M,Model) 的交互QueryBuilder 用于创建动态的查询语句使用 Yii DAO 时,需要处理纯 SQL 语句和 PHP 数组代价 因不...
数据库访问对象(Database Access Objects)
- Yii 包含了一个建立在 PHP PDO 之上的数据访问层 (DAO)
- DAO为不同的数据库提供了一套统一的API
ActiveRecord
提供了数据库与模型(MVC 中的 M,Model) 的交互QueryBuilder
用于创建动态的查询语句- 使用 Yii DAO 时,需要处理纯 SQL 语句和 PHP 数组
- 代价 因不同数据库之间的 SQL 语法往往是不同,使用 Yii DAO 也意味着必须花些额外的工夫来创建一个”数据库无关“的应用
- Yii DAO 支持
MySQL MariaDB SQLite PostgreSQL CUBRID Oracle MSSQL
- 创建数据库连接
- 通常作法 以应用组件的方式来配置数据库连接
- 应使用 dsn 属性来指明它的数据源名称 (DSN),不同数据的DSN格式不同
- 若通过 ODBC 来连接数据库,你应该配置
yii\db\Connection::$driverName
属性 - 需求
- 建立起数据库连接时立即执行一些语句来初始化一些环境变量,比如设置时区或者字符集
- 可为数据库连接的 afterOpen 事件注册一个事件处理器
- 执行 SQL 查询
- DB Connection 实例执行sql查询流程
- 使用纯SQL查询来创建出
yii\db\Command
createCommand
- 绑定参数(可选)
bindValue bindValues bindParam()
- 调用
yii\db\Command
里 SQL 执行方法中的一个queryOne
- bindParam() 支持通过引用来绑定参数
- 执行非查询语句
yii\db\Command::execute()
方法, 返回执行 SQL 所影响到的行数insert(),update(),delete(),batchInsert()
Upsert
原子操作
- 引用表和列名称
- 使用引用语法原因 不同的数据库有不同的名称引用规则
[[column name]]
使用两对方括号来将列名括起来{{table name}}
使用两对大括号来将表名括起来
- Yii DAO 将自动地根据数据库的具体语法来将这些结构转化为对应的 被引用的列或者表名称
- 使用引用语法原因 不同的数据库有不同的名称引用规则
- 使用表前缀
yii\db\Connection::$tablePrefix
属性来指定- 使用语法
{{%table_name}}
, 百分号将被自动地替换为你在配置 DB 组件时指定的表前缀
- 执行事务
transaction()
beginTransaction()
commit()
rollBack()
- 指定隔离级别
- Yii 也支持为事务设置隔离级别
yii\db\Transaction::READ_UNCOMMITTED
最弱的隔离级别,脏读、不可重复读以及幻读都可能发生yii\db\Transaction::READ_COMMITTED
避免了脏读yii\db\Transaction::REPEATABLE_READ
避免了脏读和不可重复读yii\db\Transaction::SERIALIZABLE
最强的隔离级别, 避免了上述所有的问题。
- 嵌套事务
- 复制和读写分离
- 主写从读,默认情况下,事务使用主库连接
Connection
组件支持从库间的负载均衡和失效备援yii\db\Connection::serverRetryInterval
内将不再试着连接该服务器
- 操纵数据库模式(Working with Database Schema)
- Yii DAO 提供了一套完整的方法来操纵数据库模式,即更改表结构
- 可通过
DB Connection
的getTableSchema()
方法来检索某张表的定义信息
查询构建器
-
查询构建器建立在
Database Access Objects
基础之上- 可创建程序化,DBMS无关的SQL语句
- 可读性更强,安全性更高的SQL相关的代码
-
使用流程
- 创建一个
yii\db\Query
对象来代表一条 SELECT SQL 语句的不同子句,如 SELECT, FROM - 执行
yii\db\Query
的一个查询方法
- 创建一个
-
yii\db\Query
隐式调用yii\db\QueryBuilder
yii\db\QueryBuilder
主要负责将 DBMS 不相关的yii\db\Query
对象转换成 DBMS 相关的 SQL 语句
-
创建查询
- select() 方法用来指定 SQL 语句当中的 SELECT 子句
- 被选取的字段可以包含表前缀,以及/或者字段别名
- 若使用数组格式来指定字段,你可以使用数组的键值来表示字段的别名
- 可以选择数据库的表达式,使用到包含逗号的数据库表达式的时候,须使用数组的格式,以避免自动的错误的引号添加
- Yii2.0.1+ 可以使用子查询
distinct()
方法来去除重复行- 调用
addSelect()
方法来选取附加字段
- from() 方法指定了 SQL 语句当中的 FROM 子句
- 可以通过字符串或者数组的形式来定义被查询的表名称
- 可以用数组的键值来定义表别名
- 除了表名以外,你还可以从子查询中再次查询
- from 还可以应用默认的 tablePrefix 前缀
- where() 方法定义了 SQL 语句当中的 WHERE 子句
- 字符串格式 如
'status=1'
- 哈希格式 如
['status' => 1, 'type' => 2]
- Yii 在内部对相应的值进行参数绑定
- 操作符格式 如
['like', 'name', 'test']
- 操作符格式允许你指定类程序风格的任意条件语句
- 每个操作数可以是字符串格式、哈希格式或者嵌套的操作符格式
and, or, not, between, not between, in, not in, like, or like, not like, or not like, exists, not exists, >, <=
- 对象格式 如
new LikeCondition('name', 'LIKE', 'test')
- yii2.0.14+, 定义条件的最强大和最复杂的方法
- 条件构建器是一个包含转换逻辑的类,将存储条件数据转换为SQL表达式
- 操作符格式与对象格式的对应关系是在 QueryBuilder::conditionClasses 属性中定义
- 常用映射关系如下
AND, OR -> yii\db\conditions\ConjunctionCondition
NOT -> yii\db\conditions\NotCondition
IN, NOT IN -> yii\db\conditions\InCondition
BETWEEN, NOT BETWEEN -> yii\db\conditions\BetweenCondition
…
- 字符串格式 如
- 附加条件
andwhere orWhere
在原有条件的基础上 附加额外的条件
- 过滤条件
- filterWhere() 方法将忽略在条件当中的hash format的空值
- 当一个值为 null、空数组、空字符串或者一个只包含空格的字符串时,那么它将被判定为空值
- andFilterWhere() 和 orFilterWhere() 方法 来追加额外的过滤条件
orderBy(),groupBy(),having(),limit(),offset(),join(),union()
- select() 方法用来指定 SQL 语句当中的 SELECT 子句
-
查询方法
yii\db\Query
提供了一整套的用于不同查询目的的方法all(),one(),column(),scalar(),exists(),count()
- 集合查询方法:包括
sum($q), average($q), max($q), min($q)
等$q
是一个必选参数,既可以是一个字段名称,又可以是一个 DB 表达式
- 所有的这些查询方法都有一个可选的参数 $db, 该参数指代的是 DB connection
- 若缺省,则db application component 将会被用作 默认的 DB 连接
- 调用
yii\db\Query
当中的一个查询方法内部运作机制- 在当前
yii\db\Query
的构造基础之上,调用yii\db\QueryBuilder
来生成一条 SQL 语句 - 利用生成的 SQL 语句创建一个
yii\db\Command
对象 - 调用
yii\db\Command
的查询方法
- 在当前
- 测试
$command = ...->createCommand()
$command->sql
打印 SQL 语句print_r($command->params)
打印被绑定的参数$command->queryAll()
返回查询结果的所有行
-
索引查询结果
indexBy('id')
- 如需使用表达式的值做为索引,那么只需要传递一个匿名函数给 indexBy() 方法
-
批处理查询
- 思路 服务端先保存查询结果,然后客户端使用游标(cursor)每次迭代出一批固定的结果集回来
$query->batch()
或$query->each()
其返回值是一个迭代器- 实现了
Iterator
接口yii\db\BatchQueryResult
的对象
- 实现了
-
MySQL中批量查询的局限
- MySQL 是通过 PDO 驱动库实现批量查询,默认情况下,mysql 查询带缓存
-
添加自定义查询条件和表达式
- 目的 复用查询条件
- Yii 的
ConditionInterface
接口类,必须用它来标识这是一个表示查询条件的类- 需要实现
fromArrayDefinition()
方法,用来从数组格式创建查询条件 - 创建构建器必须实现
yii\db\ExpressionBuilderInterface
接口和 build() 方法 - 新查询条件对象 – 添加一个映射到
expressionBuilders
数组
- 需要实现
-
表达式对象和条件对象
- Expression 对象
- 表达式对象,是数据集的数据转换对象(DTO)
- 表达式对象实现了
yii\db\ExpressionInterface
接口, - 还依赖于一个表达式构建器(
Expression Builder
)来执行构建逻辑 - 可以被编译为一些特定 SQL 语句 (操作符、字符串、数组、JSON等等)
- 条件对象
- 条件对象也需要构建器
- 条件对象,是表达式对象超集
- 实现了
yii\db\condition\ConditionInterface
接口,继承了ExpressionInterface
接口 - 可以聚合多个表达式对象(或标量值),后编译成一条 SQL 查询条件
- 目的 隐藏复杂的 SQL 语句拼装过程
- Expression 对象
活动记录(Active Record)
-
AR 提供了一个面向对象的接口, 用以访问和操作数据库中的数据。
- 声明该类继承
yii\db\ActiveRecord
{{%TableName}}
表前缀,SQL查询引用处理tableName()
方法默认返回的表名称是通过类名转换而来yii\db\ActiveRecord
继承了模型yii\base\Model
,具有属性,验证,数据序列化等等模型特性
- 声明该类继承
-
库连接
- 活动记录
Active Record
默认使用 db 组件 作为连接器 - 不同数据库连接,可以重写AR类的
getDb()
方法
- 活动记录
-
Active Query
查询数据流程- 通过
yii\db\ActiveRecord::find()
方法创建一个新的查询生成器对象- 该方法返回一个
yii\db\ActiveQuery
对象
- 该方法返回一个
- 使用查询生成器的构建方法来构建查询
- 调用查询生成器的查询方法来取出数据到
Active Record
实例中
- 通过
-
yii\db\ActiveQuery
查询方法yii\db\ActiveQuery
继承yii\db\Query
findOne(),findAll()
返回一个Active Record
实例的数据,填充于查询结果数据- 传参格式
- 标量值:这个值会当作主键去查询
- 标量值的数组:这数组里的值都当作要查询的主键的值
- 关联数组:键值是表的列名,元素值是相应的要查询的条件值
- 推荐 注入一个数组条件来匹配任意列的值,多行数据应加上
limit(1)
- 传参格式
yii\db\ActiveRecord::findBySql()
书写原生的 SQL 语句查询数据
-
在查询方法前调用
asArray()
方法, 以数组形式获取数据 -
批处理查询获取数据
Customer::find()->batch(10)
每次返回最多拥有 10 条数据的数组Customer::find()->each(10)
返回Customer
对象Customer::find()->with('orders')->each()
- 贪婪加载模式的批处理查询,
Customer
对象,并附带关联的'orders'
- 贪婪加载模式的批处理查询,
-
save 方法保存数据(Saving Data)
- 检查
Active Record
实例的isNewRecord
属性值来区分这两个状态(插入或更新数据) - 另支持
insert(), update()
方法
- 检查
-
数据验证
- 来自Model验证 重写
rules()
方法声明验证规则并执行, 通过调用validate()
方法进行数据验证 - 调用
save()
时,默认情况下会自动调用validate()
,若可信可以save(false)
来跳过验证过程
- 来自Model验证 重写
-
块赋值,仅支持安全属性
-
更新一个或多个计数列
updateCounters()
-
脏属性 Dirty Attributes
- 自动维护脏属性列表
- 原理 保存所有属性的旧值, 并其与最新的属性值进行比较
yii\db\ActiveRecord::getDirtyAttributes()
获取当前的脏属性- 调用
yii\db\ActiveRecord::markAttributeDirty()
将属性显式标记为脏 - 属性新旧值的比较是用 === 操作符,因此类型不一致也会被视为脏
-
默认属性值
loadDefaultValues()
来填充 DB 定义的默认值 -
属性类型转换
- 浮点值不被转换,并且将被表示为字符串
- 整型值的转换取决于使用的操作系统的整数容量
-
更新多个数据行
updateAll()
-
删除数据
delete(),deleteAll()
-
Active Record
的生命周期- 创建 new 操作符 实例化
- 类的构造函数被调用
- init() 触发 EVENT_INIT 事件
- 查询数据生命周期
3. 前2同上,afterFind()
:触发EVENT_AFTER_FIND
事件 - 保存数据生命周期(save()插入或更新时)
beforeValidate()
触发EVENT_BEFORE_VALIDATE
事件,返回false则后续步骤跳过- 标识
yii\base\ModelEvent::$isValid
值
- 标识
- 执行数据验证,验证失败,则步骤3被跳过
afterValidate()
触发EVENT_AFTER_VALIDATE
事件beforeSave()
触发EVENT_BEFORE_INSERT
或者EVENT_BEFORE_UPDATE
事件,返回false同1- 执行真正的数据插入或者更新
afterSave()
触发EVENT_AFTER_INSERT
或者EVENT_AFTER_UPDATE
事件
- 删除数据生命周期
beforeDelete()
触发EVENT_BEFORE_DELETE
事件- 执行真正的数据删除
afterDelete()
触发EVENT_AFTER_DELETE
事件
- 刷新生命周期
- 通过refresh() 刷新 Active Record 实例时,若刷新成功方法返回 true,EVENT_AFTER_REFRESH 事件将被触发
- 创建 new 操作符 实例化
以下非基于 Active Record 模型的方法调用,不会启动上述任何的生命周期
yii\db\ActiveRecord::updateAll()
yii\db\ActiveRecord::deleteAll()
yii\db\ActiveRecord::updateCounters()
yii\db\ActiveRecord::updateCounters()
- 事务操作
- 在事务块中显式地包含
Active Record
的各个方法调用 - 在
yii\db\ActiveRecord::transactions()
方法中列出需要事务支持的 DB 操作- 该方法应当返回以 场景 为键、 以需要放到事务中的 DB 操作为值的数组
- DB 操作常量
OP_INSERT,OP_UPDATE,OP_DELETE
,OP_ALL
,使用 | 运算符连接上述常量来表明多个操作 - 事务方法原理 相应的事务在调用
beforeSave()
方法时开启, 在调用afterSave()
方法时被提交
- 在事务块中显式地包含
- 乐观锁
- 支持更新
yii\db\ActiveRecord::update()
或者删除yii\db\ActiveRecord::delete()
已经存在的单条数据行 - 使用流程
- 在与
Active Record
类相关联的DB
表中创建一个列,以存储每行的版本号 - 重写
yii\db\ActiveRecord::optimisticLock()
方法返回这个列的命名 - 在
Model
类里实现OptimisticLockBehavior
行为,从请求参数里自动解析这个列的值- 然后从验证规则中删除
version
属性,因为OptimisticLockBehavior
已经处理它了.
- 然后从验证规则中删除
- 在用于用户填写的 Web 表单中,添加一个隐藏字段(hidden field)来存储正在更新的行的当前版本号
- 在使用
Active Record
更新数据的控制器动作中,要捕获(try/catch)yii\db\StaleObjectException
异常
- 在与
- 支持更新
关联数据
- 关联关系声明
- 先在
Active Record
类中定义关联关系,为每个需要定义关联关系声明一个关联方法 - 关联的对应关系 通过调用
hasMany()
或者hasOne()
指定 - 相关联
Active Record
类名, 用来指定为hasMany()
或者hasOne()
方法的第一个参数 - 指定数据关联列
- 数组的值填的是主数据的列(当前要声明关联的 Active Record 类为主数据)
- 数组的键要填的是相关数据的列
- 先附表的主键,后主表的主键(客随主便)
- 示例
$this->hasOne(Customer::className(), ['id' => 'customer_id'])
id
为Customer
模型列,customer_id
为当前模型列
- 示例
- 先在
- 访问关联数据
- 访问关联属性 vs 关联方法
- 前者返回 活动记录实例,后者返回 ActiveQuery 查询生成器实例
- 示例
$customer->orders;
获得Order
对象的数组$customer->getOrders()
返回 ActiveQuery 类的实例
- 首次访问关联属性时,将执行 SQL 语句获取数据,后续再次访问相同的属性,将不会重新执行SQL语句,即关联属性会缓存数据
- 关联方法返回的是一个
yii\db\ActiveQuery
活动 查询生成器的实例- 仅在访问关联属性时,才返回
yii\db\ActiveRecord Active Record
实例
- 仅在访问关联属性时,才返回
- 访问关联属性 vs 关联方法
- 动态关联查询
- 与访问关联属性不同,每次通过关联方法执行动态关联查询时, 都会执行 SQL 语
- 中间关联表
- 多对多对应关系存在中间表
- 声明关系 调用
via()
(两个关联方法时使用) 或viaTable()
(声明一个关联方法) 方法指定中间表 - 使用
via()
方法,可以通过多个表来定义关联声明
- 延时与即时加载
- 正常访问关联属性,sql仅在第一次访问时执行,即延迟加载
- 调用
yii\db\ActiveQuery::with()
方法,支持多关联关系及嵌套关联关系即时加载- 示例
Customer::find()->with(['orders.items','country'])->all()
- 示例
- 加载一个关联,可以通过匿名函数自定义相应的关联查询,匿名函数接受一个
$query
参数- 该参数用于 自定义的关联执行关联查询的
yii\db\ActiveQuery
对象
- 该参数用于 自定义的关联执行关联查询的
在即时加载的关联中调用 select() 方法时,要确保 在关联声明中引用的列(即外键,主键列)必须被 select!!!
- 关联关系的 JOIN 查询
- 调用
yii\db\ActiveQuery::joinWith()
来利用已存在的关联声明 - 默认的,
joinWith()
会使用LEFT JOIN
去连接主表和关联表, 会默认 即时加载 相应的关联数据
- 调用
- 关联表别名
$query->joinWith(['orders o'])
与下述代码等价
$query->joinWith([
'orders' => function ($q) {
$q->from(['o' => Order::tableName()]);
},
])
- 反向关联
- 通过在关联关系方法上调用
inverseOf()
方法声明 反向关联 - 反向关联不能用在有 连接表 关联声明,即若关联关系通过
via()
或viaTable()
反向关联不能使用 - 存在的理由 两个 Active Record 类之间的关联声明往往是相互关联的
- 通过在关联关系方法上调用
- 保存关联数据
link()
方法需要指定关联名 和要建立关联的目标Active Record
实例- 反向操作
unlink()
, 可断掉两个active record
实例间已经存在的关联关系- 通常会指定关联的外键值
- 跨数据库关联
- Active Record 允许在不同数据库驱动的
Active Record
类之间声明关联关系
- Active Record 允许在不同数据库驱动的
- 自定义查询类
- 重写
yii\db\ActiveRecord::find()
方法并返回一个自定义查询类的实例 - 自定义查询类通常需要继承
yii\db\ActiveQuery
类,用纳容纳大多数与查询相关的代码,使ctive Record 类保持简洁
- 重写
- 选择额外的字段
更多推荐
所有评论(0)