数据库访问对象(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查询流程
    1. 使用纯SQL查询来创建出 yii\db\Command createCommand
    2. 绑定参数(可选)bindValue bindValues bindParam()
    3. 调用 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 ConnectiongetTableSchema() 方法来检索某张表的定义信息

查询构建器

  • 查询构建器建立在 Database Access Objects 基础之上

    • 可创建程序化,DBMS无关的SQL语句
    • 可读性更强,安全性更高的SQL相关的代码
  • 使用流程

    1. 创建一个 yii\db\Query 对象来代表一条 SELECT SQL 语句的不同子句,如 SELECT, FROM
    2. 执行 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()
  • 查询方法

    • 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 语句拼装过程

活动记录(Active Record)

  • AR 提供了一个面向对象的接口, 用以访问和操作数据库中的数据。

    • 声明该类继承 yii\db\ActiveRecord
    • {{%TableName}} 表前缀,SQL查询引用处理
    • tableName() 方法默认返回的表名称是通过类名转换而来
    • yii\db\ActiveRecord 继承了模型 yii\base\Model,具有属性,验证,数据序列化等等模型特性
  • 库连接

    • 活动记录 Active Record 默认使用 db 组件 作为连接器
    • 不同数据库连接,可以重写AR类的 getDb() 方法
  • Active Query 查询数据流程

    1. 通过 yii\db\ActiveRecord::find() 方法创建一个新的查询生成器对象
      • 该方法返回一个 yii\db\ActiveQuery 对象
    2. 使用查询生成器的构建方法来构建查询
    3. 调用查询生成器的查询方法来取出数据到 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) 来跳过验证过程
  • 块赋值,仅支持安全属性

  • 更新一个或多个计数列 updateCounters()

  • 脏属性 Dirty Attributes

    • 自动维护脏属性列表
    • 原理 保存所有属性的旧值, 并其与最新的属性值进行比较
    • yii\db\ActiveRecord::getDirtyAttributes() 获取当前的脏属性
    • 调用 yii\db\ActiveRecord::markAttributeDirty() 将属性显式标记为脏
    • 属性新旧值的比较是用 === 操作符,因此类型不一致也会被视为脏
  • 默认属性值 loadDefaultValues()来填充 DB 定义的默认值

  • 属性类型转换

    • 浮点值不被转换,并且将被表示为字符串
    • 整型值的转换取决于使用的操作系统的整数容量
  • 更新多个数据行 updateAll()

  • 删除数据 delete(),deleteAll()

  • Active Record 的生命周期

    • 创建 new 操作符 实例化
      1. 类的构造函数被调用
      2. init() 触发 EVENT_INIT 事件
    • 查询数据生命周期
      3. 前2同上,afterFind():触发 EVENT_AFTER_FIND 事件
    • 保存数据生命周期(save()插入或更新时)
      1. beforeValidate() 触发 EVENT_BEFORE_VALIDATE 事件,返回false则后续步骤跳过
        • 标识 yii\base\ModelEvent::$isValid
      2. 执行数据验证,验证失败,则步骤3被跳过
      3. afterValidate() 触发 EVENT_AFTER_VALIDATE 事件
      4. beforeSave() 触发 EVENT_BEFORE_INSERT 或者 EVENT_BEFORE_UPDATE 事件,返回false同1
      5. 执行真正的数据插入或者更新
      6. afterSave() 触发 EVENT_AFTER_INSERT 或者 EVENT_AFTER_UPDATE 事件
    • 删除数据生命周期
      1. beforeDelete() 触发 EVENT_BEFORE_DELETE 事件
      2. 执行真正的数据删除
      3. afterDelete() 触发 EVENT_AFTER_DELETE 事件
    • 刷新生命周期
      • 通过refresh() 刷新 Active Record 实例时,若刷新成功方法返回 true,EVENT_AFTER_REFRESH 事件将被触发

以下非基于 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() 已经存在的单条数据行
    • 使用流程
      1. 在与 Active Record 类相关联的 DB 表中创建一个列,以存储每行的版本号
      2. 重写 yii\db\ActiveRecord::optimisticLock() 方法返回这个列的命名
      3. Model 类里实现 OptimisticLockBehavior 行为,从请求参数里自动解析这个列的值
        • 然后从验证规则中删除 version 属性,因为 OptimisticLockBehavior 已经处理它了.
      4. 在用于用户填写的 Web 表单中,添加一个隐藏字段(hidden field)来存储正在更新的行的当前版本号
      5. 在使用 Active Record 更新数据的控制器动作中,要捕获(try/catch) yii\db\StaleObjectException 异常

关联数据

  • 关联关系声明
    • 先在 Active Record 类中定义关联关系,为每个需要定义关联关系声明一个关联方法
    • 关联的对应关系 通过调用 hasMany() 或者 hasOne() 指定
    • 相关联 Active Record 类名, 用来指定为 hasMany() 或者 hasOne() 方法的第一个参数
    • 指定数据关联列
      • 数组的值填的是主数据的列(当前要声明关联的 Active Record 类为主数据)
      • 数组的键要填的是相关数据的列
      • 先附表的主键,后主表的主键(客随主便)
        • 示例 $this->hasOne(Customer::className(), ['id' => 'customer_id'])
        • idCustomer 模型列,customer_id 为当前模型列
  • 访问关联数据
    • 访问关联属性 vs 关联方法
      • 前者返回 活动记录实例,后者返回 ActiveQuery 查询生成器实例
      • 示例
        • $customer->orders; 获得 Order 对象的数组
        • $customer->getOrders() 返回 ActiveQuery 类的实例
    • 首次访问关联属性时,将执行 SQL 语句获取数据,后续再次访问相同的属性,将不会重新执行SQL语句,即关联属性会缓存数据
    • 关联方法返回的是一个 yii\db\ActiveQuery 活动 查询生成器的实例
      • 仅在访问关联属性时,才返回 yii\db\ActiveRecord Active Record 实例
  • 动态关联查询
    • 与访问关联属性不同,每次通过关联方法执行动态关联查询时, 都会执行 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 类之间声明关联关系
  • 自定义查询类
    • 重写 yii\db\ActiveRecord::find() 方法并返回一个自定义查询类的实例
    • 自定义查询类通常需要继承 yii\db\ActiveQuery 类,用纳容纳大多数与查询相关的代码,使ctive Record 类保持简洁
  • 选择额外的字段
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐