Swoft ORM AR 活动记录

什么是ORM?

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

4933701-639f44dd13841d26.png
ORM

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

4933701-b6c89cd5bbed3f28.png
ORM

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

4933701-3adedb318d858c0f.png
ORM

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

  • 基础的活动记录ActiveRecord用于快速开发和常规查询
  • 高级的数据映射DataMapping用于事务和复杂业务查询

ActiveRecord

  • 每个数据表对应创建一个类,类的每个对象实例对应数据表中的一行记录,通常表的每个字段在类中都有对应的字段。
  • ActiveRecord同时负责将自己持久化,在ActiveRecord中封装了对数据库的访问即CURD。
  • ActiveRecord是一种领域模型(Domain Model, DM)封装了部分业务逻辑

DataMapping

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

4933701-73d369eaf08e72eb.png
数据映射

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

查询器

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

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

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

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

配置数据库

创建数据表

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '名称',
  `age` tinyint(1) unsigned DEFAULT '0' COMMENT '年龄',
  `sex` tinyint(1) unsigned DEFAULT '0' COMMENT '性别',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';

配置数据库连接

$ 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 -h
Description:
  Auto create entity by table structure

Usage:
  entity:create -d[|--database] <database>
  entity:create -d[|--database] <database> [table]
  entity:create -d[|--database] <database> -i[|--include] <table>
  entity:create -d[|--database] <database> -i[|--include] <table1,table2>
  entity:create -d[|--database] <database> -i[|--include] <table1,table2> -e[|--exclude] <table3>
  entity:create -d[|--database] <database> -i[|--include] <table1,table2> -e[|--exclude] <table3,table4>

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
<?php
namespace App\Models\Entity;

use Swoft\Db\Bean\Annotation\Id;
use Swoft\Db\Bean\Annotation\Required;
use Swoft\Db\Bean\Annotation\Table;
use Swoft\Db\Bean\Annotation\Column;
use Swoft\Db\Bean\Annotation\Entity;
use Swoft\Db\Model;
use Swoft\Db\Types;

/**
 * 用户实体
 *
 * @Entity()
 * @Table(name="user")
 * @uses      User
 * @version   2017年08月23日
 * @author    stelin <phpcrazy@126.com>
 * @copyright Copyright 2010-2016 Swoft software
 * @license   PHP Version 7.x {@link http://www.php.net/license/3_0.txt}
 */
class User extends Model
{
    /**
     * 主键ID
     *
     * @Id()
     * @Column(name="id", type=Types::INT)
     * @var null|int
     */
    private $id;

    /**
     * 名称
     *
     * @Column(name="name", type=Types::STRING, length=20)
     * @Required()
     * @var null|string
     */
    private $name;

    /**
     * 年龄
     *
     * @Column(name="age", type=Types::INT)
     * @var int
     */
    private $age = 0;

    /**
     * 性别
     *
     * @Column(name="sex", type="int")
     * @var int
     */
    private $sex = 0;

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

    /**
     * 非数据库字段,未定义映射关系
     *
     * @var mixed
     */
    private $otherProperty;

    /**
     * @return int|null
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param int|null $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

    /**
     * @return null|string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param null|string $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * @return int
     */
    public function getAge(): int
    {
        return $this->age;
    }

    /**
     * @param int $age
     */
    public function setAge(int $age)
    {
        $this->age = $age;
    }

    /**
     * @return int
     */
    public function getSex(): int
    {
        return $this->sex;
    }

    /**
     * @param int $sex
     */
    public function setSex(int $sex)
    {
        $this->sex = $sex;
    }

    /**
     * @return string
     */
    public function getDesc(): string
    {
        return $this->desc;
    }

    /**
     * @param string $desc
     */
    public function setDesc(string $desc)
    {
        $this->desc = $desc;
    }

    /**
     * @return mixed
     */
    public function getOtherProperty()
    {
        return $this->otherProperty;
    }

    /**
     * @param mixed $otherProperty
     */
    public function setOtherProperty($otherProperty)
    {
        $this->otherProperty = $otherProperty;
    }
}

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

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

AR操作

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

4933701-bcfe3c2d1a99d2e7.png
ActiveRecord

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

新增

  1. setter使用对象方式插入数据
  • 直接返回结果
//使用ORM的ActiveRecord
$model = new User();
$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 -ft
2019-04-25T09:00:54.596389052Z int(3)

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

  • 延迟操作
//使用ORM的ActiveRecord
$model = new User();
$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);
  1. 使用数组方式保存数据
//使用ORM的ActiveRecord
$model = new User();
//使用数组方式保存数据
$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

  1. fill使用数组方式填充数据
//使用ORM的ActiveRecord
$model = new User();
//使用数组方式填充数据
$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

  1. 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

删除

  1. deleteById使用主键删除单条记录
$id = 2;
$query = User::deleteById($id);
$result = $query->getResult();
var_dump($result);

返回值:删除记录的条数

  1. deleteByIds使用主键删除多条记录
$ids = [];
$ids[] = 3;
$ids[] = 4;
$ids[] = 5;

$query = User::deleteByIds($ids);
$result = $query->getResult();
var_dump($result);

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

  1. deleteOne删除单条数据
$where = [];
$where["name"] = "bob";
$where["age"] = 20;

$query = User::deleteOne($where);
$result = $query->getResult();
var_dump($result);

删除条件:删除namebob而且age等于20的记录,注意这里条件是AND的关系。
返回值:删除记录的条数

  1. deleteAll删除多条数据
$where = [];
$where["id"] = [6,7];
$where["age"] = 21;

$query = User::deleteAll($where);
$result = $query->getResult();
var_dump($result);

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

修改

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

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

第一种方式是直接使用setter设置器的方式进行修改

$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);
}

第二种是使用数组的方式进行修改

$query = User::findById($id);
$user = $query->getResult();
$user["name"] = "lisa";
$result = $user->update()->getResult();

更新结果若返回字符串的0表示更新失败,否则更新成功。

  1. updateOne更新单条数据
$update = [];
$update["name"] = "superman";
$where = [];
$where["id"] = 11;
$query = User::updateOne($update, $where);
$result = $query->getResult();
var_dump($result);

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

  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();
  1. 查询多条数据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;
  1. 根据主键查询单条数据findById
$id = 22;
$query = User::findById($id);
$result = $query->getResult();
var_dump($result);
  1. 根据主键查询多条数据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);
  1. 判断主键是否存在exists
  • 若主键存在则返回true否则返回false
$id = 10;
$query = User::exist($id);
$result = $query->getResult();
var_dump($result);//bool(true)
  1. 查询计数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是如何使用的呢?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值