Swoft ORM AR 活动记录

什么是ORM?

ORM(Object Relational Mapping, ORM, O/RM, O/R mapping)对象关系映射用来实现面向对象编程语言中不同类型系统的数据之间的转换。

ORM

ORM是一种为了解决面向对象与关系数据库存在互补匹配现象的技术。

ORM

简单来说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。从本质上来讲,就是将数据从一种形式转换到另一种形式。

ORM

ORM中数据库操作分为两种:

基础的活动记录ActiveRecord用于快速开发和常规查询

高级的数据映射DataMapping用于事务和复杂业务查询

ActiveRecord

每个数据表对应创建一个类,类的每个对象实例对应数据表中的一行记录,通常表的每个字段在类中都有对应的字段。

ActiveRecord同时负责将自己持久化,在ActiveRecord中封装了对数据库的访问即CURD。

ActiveRecord是一种领域模型(Domain Model, DM)封装了部分业务逻辑

DataMapping

DataMapping数据映射是指给定两个数据模型,在模型之间建立起数据元素的对应关系,将此过程称为数据映射。

数据映射

在简单应用中,领域模型(Domain Model)是一种和数据库结构一致的简单模型,对应的每个数据表都有一个领域类。在这种情况下,有必要让每个对象负责数据库的存取过程,这也就是活动记录(ActiveRecord)。领域对象直接与数据表进行交互,这带来了一个问题,随着领域逻辑变得越来越负责,它就慢慢转变成了一个更大的领域模型,此时简单活动记录就会逐渐无法满足需求。当领域类和数据表一对一匹配也开始随着将领域逻辑放入更小的类而失效,由于关系数据库无法处理继承,因此使用策略模式等面向对象模式非常困难。一种更好的办法是把数据库与数据库完全独立,让间接层完全领域对象和数据表之间的映射关系,这个映射类也称为数据映射器(DataMapper)。这个映射类处理数据库和领域模型之间所有的存取操作,并且允许双方都能独立变化。如果领域逻辑非常简单且数据表十分一致,使用简单的ActiveRecord活动记录就足够了,但如果领域逻辑比较复杂且是长线项目,DataMapper数据映射器则可能是需要的。

Swoft查询器

ORM有多种设计模式,Swoft采用的是DataMapping,将业务于实体分开,但也实现了类似ActiveRecord的操作方式,其实都是同一个实现的。

Swoft查询器,提供可使用面向对象的方法操作数据库。

Swoft提供了一套基础查询,类ActiveRecord方法,方便快捷的实现数据库操作,但实体必须先继承Model类,且不能使用事务。如果需要使用事务,需使用高级查询。

Swoft基础查询提供延迟收包和非延迟收包两种方式,非延迟收包一般用于并发使用。

配置数据库

创建数据表

CREATETABLE`user`(`id`int(11)unsignedNOTNULLAUTO_INCREMENT,`name`varchar(20)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciDEFAULT''COMMENT'名称',`age`tinyint(1)unsignedDEFAULT'0'COMMENT'年龄',`sex`tinyint(1)unsignedDEFAULT'0'COMMENT'性别',`description`varchar(255)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciDEFAULT''COMMENT'描述',  PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=3DEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_unicode_ciCOMMENT='用户表';

配置数据库连接

$ vim .env

# 数据库主节点配置# 连接池节点名称用于服务发现DB_NAME=dbMaster# 连接地址信息DB_URI=192.168.99.100:3306/test?user=jc&password=junchow&charset=utf8,192.168.99.100:3306/test?user=jc&password=junchow&charset=utf8# 最小活跃连接数DB_MIN_ACTIVE=5# 最大活跃连接数DB_MAX_ACTIVE=10# 最大等待连接DB_MAX_WAIT=20# 最大等待时间DB_MAX_WAIT_TIME=3# 连接最大空闲时间DB_MAX_IDLE_TIME=60# 连接超时时间DB_TIMEOUT=2

如果对数据库主从不是很清晰,可参见《MySQL读写分离主从复制》

创建实体模型

实体模型继承自Model类,位于swoft/db/src/Model.php。

实体模型主要针对单表简单CURD操作,不支持事务,但支持并发查询。

创建实体命令帮助

$ php bin/swoft entity:create -hDescription:  Auto create entity by table structureUsage:  entity:create -d[|--database]   entity:create -d[|--database] [table]  entity:create -d[|--database] -i[|--include]   entity:create -d[|--database] -i[|--include]   entity:create -d[|--database] -i[|--include] -e[|--exclude]   entity:create -d[|--database] -i[|--include] -e[|--exclude] Options:  -d                    数据库  --database            数据库  -i                    指定特定的数据表,多表之间用逗号分隔  --include            指定特定的数据表,多表之间用逗号分隔  -e                    排除指定的数据表,多表之间用逗号分隔  --exclude            排除指定的数据表,多表之间用逗号分隔  --remove-table-prefix 去除表前缀  --entity-file-path    实体路径(必须在以@app开头并且在app目录下存在的目录,否则将会重定向到@app/Models/Entity)Example:  php bin/swoft entity:create -d test

使用命令行创建实体

php bin/swoft entity:create -d test user

注意:如果命令中缺少-d参数则会报错databases doesn't not empty!。

创建成功后会生成模型的实体文件

$ vim app/Models/Entity/User.php

<?phpnamespaceApp\Models\Entity;useSwoft\Db\Bean\Annotation\Id;useSwoft\Db\Bean\Annotation\Required;useSwoft\Db\Bean\Annotation\Table;useSwoft\Db\Bean\Annotation\Column;useSwoft\Db\Bean\Annotation\Entity;useSwoft\Db\Model;useSwoft\Db\Types;/** * 用户实体 * *@Entity() *@Table(name="user") *@usesUser *@version2017年08月23日 *@authorstelin *@copyrightCopyright 2010-2016 Swoft software *@licensePHP Version 7.x {@linkhttp://www.php.net/license/3_0.txt} */classUserextendsModel{/**    * 主键ID    *    *@Id()    *@Column(name="id", type=Types::INT)    *@varnull|int    */private$id;/**    * 名称    *    *@Column(name="name", type=Types::STRING, length=20)    *@Required()    *@varnull|string    */private$name;/**    * 年龄    *    *@Column(name="age", type=Types::INT)    *@varint    */private$age =0;/**    * 性别    *    *@Column(name="sex", type="int")    *@varint    */private$sex =0;/**    * 描述    *    *@Column(name="description", type="string")    *@varstring    */private$desc ='';/**    * 非数据库字段,未定义映射关系    *    *@varmixed    */private$otherProperty;/**    *@returnint|null    */publicfunctiongetId(){return$this->id;    }/**    *@paramint|null $id    */publicfunctionsetId($id){$this->id = $id;    }/**    *@returnnull|string    */publicfunctiongetName(){return$this->name;    }/**    *@paramnull|string $name    */publicfunctionsetName($name){$this->name = $name;    }/**    *@returnint    */publicfunctiongetAge():int{return$this->age;    }/**    *@paramint $age    */publicfunctionsetAge(int $age){$this->age = $age;    }/**    *@returnint    */publicfunctiongetSex():int{return$this->sex;    }/**    *@paramint $sex    */publicfunctionsetSex(int $sex){$this->sex = $sex;    }/**    *@returnstring    */publicfunctiongetDesc():string{return$this->desc;    }/**    *@paramstring $desc    */publicfunctionsetDesc(string $desc){$this->desc = $desc;    }/**    *@returnmixed    */publicfunctiongetOtherProperty(){return$this->otherProperty;    }/**    *@parammixed $otherProperty    */publicfunctionsetOtherProperty($otherProperty){$this->otherProperty = $otherProperty;    }}

在此需要注意的是虽然数据表中使用是description, 但使用@Column(name="description", type="string")。

/** * 描述 * *@Column(name="description", type="string") *@varstring */private$desc ='';

AR操作

关于ActiveRecord,可以理解为一个不同SQL数据库的Wrapper,同时为上层提供一种简洁优雅的API或DSL,能极大地减轻开发者的负担并提升工作效率。

ActiveRecord

由于每个AR类代表一个数据表或视图,数据表或视图的列在AR类中体现为类的属性,一个AR实例则表示为数据表中的一行。常见的CURD增删改查操作作为AR的方法实现。因此,可以以一种更加面向对象的方式访问数据。

新增

setter使用对象方式插入数据

直接返回结果

//使用ORM的ActiveRecord$model =newUser();$model->setName("junchow");$model->setSex(1);$model->setAge(mt_rand(1,100));$model->setDesc("this is a test");$result = $model->save()->getResult();var_dump($result);//int(3)

容器中开启日志并查看输出

docker@default ~$ docker logs myswoft -ft2019-04-25T09:00:54.596389052Z int(3)

对比观察,发现返回的结果$result与数据库的自增主键id保持一致。

延迟操作

//使用ORM的ActiveRecord$model =newUser();$model->setName("junchow");$model->setSex(1);$model->setAge(mt_rand(1,100));$model->setDesc("this is a test");$result = $model->save(true)->getResult();var_dump($result);

使用数组方式保存数据

//使用ORM的ActiveRecord$model =newUser();//使用数组方式保存数据$model["name"] ="bob";$model["sex"] =1;$model["age"] =20;$model["desc"] ="this is a description demo";$query = $model->save();$result = $query->getResult();//直接输出var_dump($result);

注意:这里使用的是实体属性desc而非数据表中的description。

fill使用数组方式填充数据

//使用ORM的ActiveRecord$model =newUser();//使用数组方式填充数据$data = [];$data["name"] ="alice";$data["sex"] =1;$data["age"] =20;$data["desc"] ="this is a description demo";$query = $model->fill($data)->save();$result = $query->getResult();//直接输出var_dump($result);

注意:这里使用的是实体属性desc而非数据表中的description。

batchInsert使用批量插入方式添加数据

//使用ORM的ActiveRecord$rows = [];//使用数组方式保存数据$row = [];$row["name"] ="bob";$row["sex"] =1;$row["age"] =20;$row["description"] ="this is a description demo";$rows[] = $row;//使用数组方式保存数据$row = [];$row["name"] ="carl";$row["sex"] =1;$row["age"] =21;$row["description"] ="this is a description demo";$rows[] = $row;$query = User::batchInsert($rows);$result = $query->getResult();//批量插入var_dump($result);

注意:

这里使用的是数据表中的description,而非实体属性desc。

批量插入后返回的是第一条记录的自增主键ID

删除

deleteById使用主键删除单条记录

$id =2;$query = User::deleteById($id);$result = $query->getResult();var_dump($result);

返回值:删除记录的条数

deleteByIds使用主键删除多条记录

$ids = [];$ids[] =3;$ids[] =4;$ids[] =5;$query = User::deleteByIds($ids);$result = $query->getResult();var_dump($result);

返回值$result是删除记录的条数

deleteOne删除单条数据

$where = [];$where["name"] ="bob";$where["age"] =20;$query = User::deleteOne($where);$result = $query->getResult();var_dump($result);

删除条件:删除name为bob而且age等于20的记录,注意这里条件是AND的关系。

返回值:删除记录的条数

deleteAll删除多条数据

$where = [];$where["id"] = [6,7];$where["age"] =21;$query = User::deleteAll($where);$result = $query->getResult();var_dump($result);

删除条件:删除用户编号为6或7,而且年龄为21的记录。

返回值:删除记录条数

修改

注意:由于在数据库中获取数据属于读操作,修改、新增属于写操作,因此需要注意配置文件中的主从和读写。

update使用实体的方式更新记录

$id =1;//根据用户ID判断用户是否存在$query = User::findById($id);$user = $query->getResult();if(!empty($user)){    var_dump($user);//重新设置姓名$user->setName("lisa");//更新用户数据$result = $user->update()->getResult();    var_dump($result);}

updateOne更新单条数据

$update = [];$update["name"] ="superman";$where = [];$where["id"] =11;$query = User::updateOne($update, $where);$result = $query->getResult();var_dump($result);

返回值:成功返回受影响行数,上例$result为1。

updateAll更新多条数据

$update = [];$update["name"] ="lucy";$where = [];$where["id"] = [10,20,30];$query =  User::updateAll($update, $where);$result =$query->getResult();var_dump($result);

返回值:成功返回受影响行数,上例$result为3。

查询

使用AR实体查询,返回结果为实体对象,而非数组。

1. 获取单条数据findOne

$fields = ["name","sex"];$query = User::findOne($where, $fields);$result = $query->getResult();var_dump($result);

返回值:

object(App\Models\Entity\User)#1413 (7) {["id":"App\Models\Entity\User":private]=>  int(10)  ["name":"App\Models\Entity\User":private]=>  string(4)"lucy"["age":"App\Models\Entity\User":private]=>  int(21)  ["sex":"App\Models\Entity\User":private]=>  int(1)  ["desc":"App\Models\Entity\User":private]=>  string(26)"this is a description demo"["otherProperty":"App\Models\Entity\User":private]=>NULL["attrs":"Swoft\Db\Model":private]=>array(5) {    ["id"]=>    int(10)    ["name"]=>    string(4)"lucy"["age"]=>    int(21)    ["sex"]=>    int(1)    ["desc"]=>    string(26)"this is a description demo"}}

观察可以发现返回的是一个object实体对象,如果要从实体对象中获取属性,则需使用getter方法。

var_dump($result->getName());//string(4) "lucy"var_dump($result->getSex());//int(1)var_dump($result->getDesc());//string(26) "this is a description demo"

若需要将object实体对象转化为数组,可使用toArray()方法。

$arr = $result->toArray();

2. 查询多条数据findAll

用法

/**

  * 查看多条数据

  * $condition 数组 查询条件

  * $options 数组 额外条件如orderby、limit、offset

  */findAll(array$condition = [],array$options = [])

用法1

$condition = [];$condition["id"] = [20,21,22,23,24,25];$condition["sex"] =1;$options = [];$options["orderby"] = ["age"=>"DESC"];$optoins["limit"] =10;$query = User::findAll($condition, $options);$result = $query->getResult();var_dump($result);

返回值

object(Swoft\Db\Collection)#1421 (1) {["items":protected]=>array(3) {    [0]=>    object(App\Models\Entity\User)#1410 (7) {["id":"App\Models\Entity\User":private]=>      int(21)      ["name":"App\Models\Entity\User":private]=>      string(10)"1556193516"["age":"App\Models\Entity\User":private]=>      int(57)      ["sex":"App\Models\Entity\User":private]=>      int(1)      ["desc":"App\Models\Entity\User":private]=>      string(14)"this is a test"["otherProperty":"App\Models\Entity\User":private]=>NULL["attrs":"Swoft\Db\Model":private]=>array(5) {        ["id"]=>        int(21)        ["name"]=>        string(10)"1556193516"["age"]=>        int(57)        ["sex"]=>        int(1)        ["desc"]=>        string(14)"this is a test"}    }    [1]=>    object(App\Models\Entity\User)#1419 (7) {["id":"App\Models\Entity\User":private]=>      int(22)      ["name":"App\Models\Entity\User":private]=>      string(10)"1556193538"["age":"App\Models\Entity\User":private]=>      int(49)      ["sex":"App\Models\Entity\User":private]=>      int(1)      ["desc":"App\Models\Entity\User":private]=>      string(14)"this is a test"["otherProperty":"App\Models\Entity\User":private]=>NULL["attrs":"Swoft\Db\Model":private]=>array(5) {        ["id"]=>        int(22)        ["name"]=>        string(10)"1556193538"["age"]=>        int(49)        ["sex"]=>        int(1)        ["desc"]=>        string(14)"this is a test"}    }    [2]=>    object(App\Models\Entity\User)#1420 (7) {["id":"App\Models\Entity\User":private]=>      int(23)      ["name":"App\Models\Entity\User":private]=>      string(10)"1556193540"["age":"App\Models\Entity\User":private]=>      int(19)      ["sex":"App\Models\Entity\User":private]=>      int(1)      ["desc":"App\Models\Entity\User":private]=>      string(14)"this is a test"["otherProperty":"App\Models\Entity\User":private]=>NULL["attrs":"Swoft\Db\Model":private]=>array(5) {        ["id"]=>        int(23)        ["name"]=>        string(10)"1556193540"["age"]=>        int(19)        ["sex"]=>        int(1)        ["desc"]=>        string(14)"this is a test"}    }  }}

观察可以发现,返回值是一个Swoft\Db\Collection类型的object。

用法2:排序条件与分页设置

$condition = [];$condition["sex"] =1;$condition[] = ["id",">",30];

分页设置一般是由offset偏移量和分页条数limit共同构成,例如:SQL语句limit 0,10表示从第0条开始向后取10条。

$options = [];$options["orderby"] = ["age"=>"DESC"];$optoins["offset"] =1;$optoins["limit"] =10;

排序条件$options["orderby"] = ["age"=>"DESC"];

偏移量(从第n条开始)$optoins["offset"] = 1;

条数限制$optoins["limit"] = 10;

因此,如果在分页中需要根据页码和每页条数来获取数据。

//分页$page =1;//页码$pagesize =5;//每页显示条数$offset = ($page -1) * $pagesize;$options["offset"] = $offset;$options["limit"] = $pagesize;

3. 根据主键查询单条数据findById

$id = 22;$query = User::findById($id);$result =$query->getResult();var_dump($result);

4. 根据主键查询多条数据findByIds

多个主键以数组方式[]使用

返回值是Swoft\Db\Collection类型的object

$ids = [21,22,23];$options = [];$options["fields"] = ["name","sex"];$options["orderby"] = ["id"=>"DESC"];$options["limit"] =2;$query = User::findByIds($ids, $options);$result = $query->getResult();var_dump($result);

5. 判断主键是否存在exists

若主键存在则返回true否则返回false

$id =10;$query = User::exist($id);$result = $query->getResult();var_dump($result);//bool(true)

6. 查询计数count

返回满足条件的行数

$fields ="id";$options = [];$options["id"] = [10,11];$query = User::count($fields, $options);$result = $query->getResult();var_dump($result);//string(1) "2"

小结

在了解ORM对象关系映射之后,常见使用ORM做数据查询的方式有两种AR和DataMapping,AR主要针对单表简单的查询,DataMapping主要针对复杂的多条查询。这里需要注意的一点的是由于数据库的操作主要分为读操作和写操作,如果提前在配置文件中使用读写分离,将读数据库和写数据库分开后,使用AR是查询操作实际上隶属于读操作,而插入、更新、删除隶属于写操作,这一点将在后续针对性的分析。接下来,需要挖掘的针对多表连表时的操作,DataMapping是如何使用的呢

出自:https://www.jianshu.com/p/0e96fa1a13c1

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容