在一对一关联情况下的数据缓存的一种尝试
介绍
在一对一关联情况下的数据缓存的一种尝试
初衷
在多表连接查询的时候,我的一般做法,我会先查主表,然后再array_column条件id去匹配副表信息.
例如, users
和 phones
两张表,它们通过 user_id
进行关联
表结构
1. users 表:
字段名 | 类型 | 描述 |
---|---|---|
id | Integer | 主键 |
name | String | 用户名 |
String | 邮箱地址 |
2. phones 表:
字段名 | 类型 | 描述 |
---|---|---|
id | Integer | 主键 |
user_id | Integer | 外键,关联到 users 表的 id |
phone_number | String | 电话号码 |
实现方式
use App\Models\User;
use App\Models\Phone;
$userData = User::select('id', 'name', 'email')
->get();
// 获取所有用户的 id
$userIds = $userData->pluck('id')->toArray();
// 获取每个用户的电话号码
$phoneData = Phone::whereIn('user_id', $userIds)
->select('user_id', 'phone_number')
->get()
->groupBy('user_id');
// 将电话号码数据合并到用户数据中
foreach ($userData as $user) {
if ($phoneData->has($user->id)) {
$user->phones = $phoneData[$user->id]->pluck('phone_number')->toArray();
} else {
$user->phones = []; // 如果没有电话号码,则设为空数组或 null,视情况而定
}
}
// 现在 $userData 中每个 $user 对象都有 phones 属性,包含了该用户的所有电话号码
return $userData;
为了简化上述操作,进行了封装,并且对查询出来的结果使用redis将数据缓存
使用案例
1. 获取用户id为10000的信息
use App\Models\User;
use App\Models\Phone;
$userEloquent = new UserEloquent();
$users=$userEloquent->getById(10000,['info','phone']);
// 结果
Array
(
"info" => Array
(
"id" => 10000,
"name" => "Jane",
"email" => "jane@example.com"
),
"phone" => Array
(
"id" => 1,
"user_id" => 10000,
"phone_number" => "13681985439"
)
)
2. 获取用户10000和10001的信息
```php
use App\Models\User;
use App\Models\Phone;
$userEloquent = new UserEloquent();
$users=$userEloquent->getByIds([10000,10001],['info','phone']);
// 结果
Array
(
10000 => Array
(
"info" => Array
(
"id" => 10000,
"name" => "Jane",
"email" => "jane@example.com"
),
"phone" => Array
(
"id" => 1,
"user_id" => 10000,
"phone_number" => "13681985439"
)
),
10001 => Array
(
"info" => Array
(
"id" => 10001,
"name" => "Jane Doe",
"email" => "jane.doe@example.com"
),
"phone" => Array
(
"id" => 2,
"user_id" => 10001,
"phone_number" => "13712345678"
)
)
// 可能还有其他用户的信息,取决于查询到的用户数量
)
### 如何实现上述查询
```php
//可能存在bug
composer require asfop/eloquent
在Hyperf中使用
创建Eloquent
文件夹 目录结构如下
├── User
│ ├── UserDrive.php
│ ├── UserEloquent.php
│ └── Attribute
│ ├── Info.php
│ └── Phone.php
User
模型 一对一关联UserDrive.php
属性名对应的数据查询类UserEloquent.php
自定义快捷查询的类。可定义查询单个用户名,和多个用户函数名Attribute/Info.php
基础信息实现类Attribute/Phone.php
Phone信息实现类
UserEloquent.php 解释
<?php
namespace App\Eloquent\Activity;
use App\Log\Log;
use Asfop\Eloquent\Cache;
use Asfop\Eloquent\Eloquent;
use Hyperf\Context\ApplicationContext;
use Hyperf\Redis\RedisFactory;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class ActivityEloquent
{
/**
* 缓存键的前缀,控制整个缓存版本,版本号更改后所有用户缓存失效
*/
const CACHE_KEY_PREFIX = "c:eloquent:user:v1";
/**
* 根据一组用户ID获取多个信息
*
* @param array $ids
* @param array $attrs
* [[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554) array
*/
public function getByIds(array $ids, array $attrs = []): array
{
try {
// 获取 Redis 实例并创建缓存对象
$redis = ApplicationContext::getContainer()->get(RedisFactory::class)->get('default');
$cache = new Cache($redis);
// 创建数据驱动对象并实例化 Eloquent 类
$drive = new UserDrive(); // 假设这是用户数据驱动的实例化
$eloquent = new Eloquent($cache, $drive, self::CACHE_KEY_PREFIX);
// 调用 Eloquent 实例的方法获取信息列表
return $eloquent->getInfoList(array_unique($ids), $attrs);
} catch (\Exception $exception) {
// 记录错误日志
Log::error("user_eloquent_get_by_ids", [$exception->getMessage(), $exception->getFile(), $exception->getLine()]);
} catch (NotFoundExceptionInterface | ContainerExceptionInterface $exception) {
// 记录错误日志
Log::error("user_eloquent_get_by_ids", [$exception->getMessage(), $exception->getFile(), $exception->getLine()]);
}
return [];
}
/**
* 根据用户ID获取单个信息
*
* @param int $id
* @param array $attrs
* [[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554) array
*/
public function getById(int $id, array $attrs = []): array
{
try {
// 获取 Redis 实例并创建缓存对象
$redis = ApplicationContext::getContainer()->get(RedisFactory::class)->get('default');
$cache = new Cache($redis);
// 创建数据驱动对象并实例化 Eloquent 类
$drive = new UserDrive(); // 假设这是用户数据驱动的实例化
$eloquent = new Eloquent($cache, $drive, self::CACHE_KEY_PREFIX);
// 调用 Eloquent 实例的方法获取信息列表
return $eloquent->getInfoList([$id], $attrs);
} catch (\Exception $exception) {
// 记录错误日志
Log::error("user_eloquent_get_by_id", [$exception->getMessage(), $exception->getFile(), $exception->getLine()]);
} catch (NotFoundExceptionInterface | ContainerExceptionInterface $exception) {
// 记录错误日志
Log::error("user_eloquent_get_by_id", [$exception->getMessage(), $exception->getFile(), $exception->getLine()]);
}
return [];
}
/**
* 清除特定用户特定属性的缓存
*
* @param int $id
* @param string $attr
* [[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554) void
*/
public function forgetCache(int $id, string $attr): void
{
try {
// 获取 Redis 实例并创建缓存对象
$redis = ApplicationContext::getContainer()->get(RedisFactory::class)->get('default');
$cache = new Cache($redis);
// 创建数据驱动对象并实例化 Eloquent 类
$drive = new UserDrive(); // 假设这是用户数据驱动的实例化
$eloquent = new Eloquent($cache, $drive, self::CACHE_KEY_PREFIX);
// 调用 Eloquent 实例的方法清除缓存
$eloquent->forgetCache($id, $attr);
} catch (\Exception $exception) {
// 记录错误日志
Log::error("user_eloquent_forget_cache", [$exception->getMessage()]);
} catch (NotFoundExceptionInterface | ContainerExceptionInterface $exception) {
// 记录错误日志
Log::error("user_eloquent_forget_cache", [$exception->getMessage()]);
}
}
}
Info.php 解释
<?php
namespace App\Eloquent\User\Attribute;
use App\Models\User;
use Asfop\Eloquent\attribute\Base;
class Info extends Base
{
/**
* 当前属性的版本缓存版本,用于在当前属性查询字段新增或减少后更新缓存
* @var string
*/
protected $cacheVersion = "v1";
/**
* 获取此类的名称或类型标识
* @return string
*/
public static function getNames(): string
{
return 'info';
}
/**
* @inheritDoc
*/
public function getField(): array
{
return ["id", "name", "email"];
}
/**
* 根据给定的 id 数组从数据库获取成员信息
* 当缓存不存在时会执行该方法,存在缓存后则不再执行
* @inheritDoc
*/
public function getInfoByIds(): array
{
$userData = User::whereIn('uid', $this->getIds())
->select($this->getField())
->get();
return $userData;
}
/**
* 根据数据进行转换处理,每次调用都会执行此方法
* @inheritDoc
*/
public function transform($data): array
{
if (empty($data)) {
return [];
}
return [
"id" => $data["id"],
"email" => $data["email"],
"name" => $data["name"],
];
}
}
开发计划
- 基本功能够使用
- 提供基础文档
- 查询功能加上数据缓存,缓存加上版本控制,缓存支持清空
- 支持是否使用缓存开关
- 支持那些属性查询不走缓存
- 支持更多的缓存方式如文件,Memcache缓存方式,
- 支持并发重复请求
- 支持多级缓存,内存缓存->redis缓存
- 命名优化,功能优化
- 完善测试用例
本作品采用《CC 协议》,转载必须注明作者和本文链接
不是一句话的事情吗?弄这么复杂
快周末了,不要再讨论什么代码了,你带你的破电脑回到家并不能给你带来任何实质性作用,朋友们兜里掏大把钱吃喝玩乐,你默默的在家里摆弄你的破烂儿框架。 亲戚朋友吃饭问你收获了什么,你说我做了个组件,把表关联、缓存、数据库连接优点都结合了一遍,亲戚们懵逼了。 你还在心里默默嘲笑他们,笑他们不懂你的框架、不懂你的算法、不懂你的封装,也笑他们连个复杂点的密码都记不住。 你父母的同事都在说自己的子女今年的收获,儿子买了个房,女儿买了个车,姑娘升职加薪了。 你的父母默默无言,说我的儿子搞了个破电脑开起来噏噏响、家里电表走得越来越快了,你的父母还在想你什么时候能买套房子,什么时候能成个家。 你却一天到晚想的是怎么封装一个牛逼的组件,你的和有钱人的差距就是你的格局一直在于代码,而你的身边的人会想怎么挣钱你一天到晚看那个破文档,封装源码框架,什么cache源码深度解析,数据库多缓存,github早就一堆。 但是你还是乐此不彼的写着自己的另外一套,因为你会觉得这是你自己写的不一样,实际上毛的用处都没有,时间轮回一年又一年你还在想着新技术出来了继续学习什么laravel,什么PHP8,而你身边的人在考虑啥时候买第二套房子,什么时候生二胎,你还在捣鼓你的破代码!