附录A: 常见问题

陆续整理数据库和模型相关的常见问题(保持更新~)

  • 内置支持的数据库有哪些?
  • Db类封装的方法看起来很简单,是如何实现调用的?
  • Db类的内部是什么实现原理?
  • 模型和Db类的区别主要是什么?
  • tablename方法的区别是什么?
  • 模型的getall方法可以支持条件查询么?
  • 模型的getall方法可以支持排序等链式操作么?
  • 闭包查询如何传入变量
  • 在模型里面怎么限制查询字段
  • 5.0还有数据表字段缓存么?
  • 获取器和修改器方法名的规范是什么?
  • 获取器是在什么时候触发?
  • 修改器是在什么时候触发?
  • 为什么定义的修改器会执行两次
  • 如何使用视图模型?
  • 设置主从分离后,如何切换到主库进行查询操作
  • 如何查询一个字段值为NULL或者NOT NULL的数据?
  • 如何直接使用字符串条件进行查询?
  • 如何切换数据库连接
  • 模型中如何使用事务
  • Db类如何使用软删除功能

内置支持的数据库有哪些?

5.0内置支持的数据库包括MysqlSqlitePgsqlSqlServer,另外还提供了OracleMongoDb的扩展驱动。

Db类封装的方法看起来很简单,是如何实现调用的?

Db类只是一个数据库操作的入口类,数据库的查询方法都是由Query类实现的,调用Db类的静态方法会自动实现Query类方法的动态调用。并且Db类是一个工厂类,针对不同的数据库驱动实现了统一的封装。

Db类的内部是什么实现原理?

Db类可以看成是数据库抽象访问层的入口类,抽象访问层本身包含了连接器类(负责连接不同的数据库和执行基础查询)、查询器类(负责各种查询方法的实现)和生成器类(负责把查询方法转换为基础查询语句),各司其职完成了跨数据库的底层操作,我们只需要操作Db类即可完成数据库抽象访问层的操作,系统的数据库抽象访问层实际上是对PDO的一种扩展和增强,主要的优势是CURD的增强和查询事件,PDO本身只有基础查询方法。

模型和Db类的区别主要是什么?

Db类(其实就是数据库抽象访问层)负责数据和查询本身,而模型类侧重于业务逻辑和数据处理。Db类仅仅是单纯的进行操作存取操作,主要使用的是数组类型,而模型操作的数据主要是模型的对象实例,更加面向对象化和直观,并且提供了模型关联操作可以大大简化业务逻辑的相关处理和数据获取,模型本身是依赖数据库抽象访问层的。

tablename方法的区别是什么?

Db类提供了tablename两个方法,table方法用于指定数据表的完整名称(包括前缀,而且不会进行大小写转换处理),name方法仅仅用于指定数据表的标识(会把驼峰表名转换为小写加下划线的方式,并且不包含前缀)。

如果你遵循框架的数据表命名规范,并且不使用表前缀的话,这两个方法是等效的。

模型的getall方法可以支持条件查询么?

可以,如果模型的getall方法如果传入一个索引数组,就表示查询条件,例如:

User::all([
    'name'  =>  'thinkphp',
    'id'    =>  ['>', 10],
]);

模型的getall方法可以支持排序等链式操作么?

模型类的get和all方法之前不支持Db类的链式方法调用,如果你需要使用Db类的链式方法,可以使用闭包方式,例如:

User::all(function($query){
    $query->field('name,email,id')->order('id');
});

等效于

User::field('name,email,id')->order('id')->select();

但区别是可以支持模型的事件操作。

闭包查询如何传入变量

如果要在闭包中传入外部变量,可以使用use语法,例如:

User::get(function($query) use($name){
    $query->where('name',$name);
});

用类似的方法可以支持传入多个变量,下面的用法是错误的:

User::get(function($query,$name) {
    $query->where('name',$name);
});

在模型里面怎么限制查询字段

如果在模型里面调用的是find或者select方法,那么依然可以使用field方法进行字段限制,模型类的getall方法查询的话本身不支持field方法,但可以通过闭包完成(参考上一个问题)。

但并不建议模型查询的时候指定字段,因为可能会影响模型的获取器,尤其存在依赖关系的话。如果仅仅是希望不暴露敏感数据,则可以在输出数据的时候使用hidden或者visible方法进行隐藏和指定显示。

5.0还有数据表字段缓存么?

5.0默认不会生成字段缓存,但提供了数据表字段缓存的命令行指令,你可以在部署上线后执行php think optimize:schema命令生成字段缓存。

获取器和修改器方法名的规范是什么?

获取器和修改器方法的命名规范分别是getFieldNameAttrsetFieldNameAttr,其中的FieldName是数据表字段的驼峰法表示,也就是对应数据表的field_name字段。

获取器是在什么时候触发?

获取器的作用是对模型的数据对象的(原始)数据做出自动处理,定义了获取器之后会在下列情况自动触发:

  • 模型的数据对象取值操作($model->field_name);
  • 模型的序列化输出操作($model->toArray()或者$model->toJson());
  • 显式调用getAttr方法($this->getAttr('field_name'));

修改器是在什么时候触发?

修改器的作用是在写入数据库之前对模型数据进行修改处理,一般在显式赋值(包括单个赋值和批量赋值)的时候会自动触发,不过有一些修改器是在设置了自动完成后被动触发,下面是触发条件。

  • 模型对象赋值;
  • 调用模型的data方法,并且第二个参数传入true;
  • 调用模型的save方法,并且传入数据;
  • 显式调用模型的setAttr方法;
  • 定义了该字段的自动完成;

为什么定义的修改器会执行两次

如果定义的修改器字段同时定义了自动完成,并且你也进行了赋值操作,那么就会导致修改器执行两次。避免的方法是对需要赋值操作的字段不再定义自动完成。典型的场景就是密码字段使用md5加密保存,如果定义了自动完成,然后同时表单又赋值了,那么可能会被加密两次导致出错。

如何使用视图模型?

ThinkPHP提供了多种方式的视图查询的支持,包括:

一、直接在数据库中创建视图

可以直接在数据库中创建视图,然后使用Db类或者创建模型类进行操作,这种方式的优点是方便,缺点是有些数据库不支持创建视图,而且不支持数据写入操作。

二、使用Db类的view方法动态创建视图查询

这种方式可以动态的创建一个视图并进行查询操作,而不依赖数据库,缺点也是不支持数据写入。

三、使用聚合模型

可以把聚合模型看成是view方法的模型封装,而且可以支持写入操作,缺点是不直观和配置麻烦,而且聚合模型和原始模型不能同时使用(最新版本已经不再推荐使用)。

四、使用模型关联

最新版本的模型关联对一对一关联改进了很多,包括关联属性绑定到当前模型,以及关联自动写入功能,这是ThinkPHP5最为推荐的视图操作方式,相比前面几种,优势是操作直观和支持写入。

设置主从分离后,如何切换到主库进行查询操作

一旦开启了数据库的主从分离,默认情况下所有读操作都是在从库,而写操作则是在主库,如果因为某些情况需要(例如对于实时性要求较高的查询,写入后立刻查询从库同步还不及时的情况)在主库进行查询,我们可以使用

Db::name('user')->where('id',10)->update(['name'=>'thinkphp']);
// 连接到主库进行查询操作
Db::name('user)->master(true)->find(10);

如何查询一个字段值为NULL或者NOT NULL的数据?

5.0.5版本开始,可以直接使用快捷查询方法如下:

// 查询name为NULL的数据
Db::name('user')->whereNull('name')->select();
// 查询name为NOT NULL的数据
Db::name('user')->whereNotNull('name')->select();

如果要使用OR查询,可以传入第二个参数为OR

// 查询name为thinkphp或者NULL的数据
Db::name('user')->where('name','thinkphp')->whereNull('name','or')->select();
// 查询name为thinkphp或者NOT NULL的数据
Db::name('user')->where('name','thinkphp')->whereNotNull('name','or')->select();

5.0.5版本之前,可以使用下面的方式

// 查询name为NULL的数据
Db::name('user')->where('name','null')->select();
// 查询name为NOT NULL的数据
Db::name('user')->where('name','not null')->select();

如何直接使用字符串条件进行查询?

可以在where方法的第一个参数直接传入字符串条件,并且可以和其它条件混合使用:

Db::name('user')
    ->where('( name like :name OR name IS NULL ) AND id > :id', ['name' => '%think%', 'id' => 10])
    ->where('email', 'like', '%think')
    ->select();

如何切换数据库连接

无论在Db类还是模型里面,要切换数据库都可以直接调用connect方法,该方法的参数可以是数组或者DNS字符串,以及配置文件中的数据库连接配置参数名,例如:

Db::connect('db_config')->name('user')->find();

必须在connect方法后面调用查询。

模型中如何使用事务

在模型中使用事务和数据库中使用事务一样,

$this->startTrans();
try{
    // 添加实现代码
    // ...
    // 提交事务
    $this->commit();    
} catch (\Exception $e) {
    // 回滚事务
    $this->rollback();
}

但仍然建议直接使用

$this->transaction(function(){
    // 添加实现代码
});

能够实现事务的自动提交及回滚。

Db类如何使用软删除功能

软删除功能是模型的特性,Db类默认不支持,不过5.0.8+版本开始,可以使用useSoftDelete方法来支持软删除操作。

查询数据的时候不包含软删除数据(假设软删除字段是delete_time)。

Db::name('user')->useSoftDelete('delete_time')->select();

上一篇:第九章:性能和安全
下一篇:附录B:使用MongoDb

你可能感兴趣的:(附录A: 常见问题)