这吊玩意,到处是坑!慎用!!!
一、基本介绍
QCache类是一个模板类。QCache<key, T> 就定义了一个缓存,其类似于map,也是存储的键值对。bool QCache<Key,T>::insert(const Key &akey, T *aobject, int acost)
:这是插入缓存操作,可以看到QCache里存储的值只能是一个指针。
用QCache和其他类型基于键值对的数据结构,如QMap或者QHash相比,好处是QCache自动获得被插入对象的所有权,并在需要的时候自动释放他们来为新插入的对象腾出空间。(PS:这句话很重要,简单点说,就是你在向里面存值时,可以无限的 new class() 来创建对象指针,而不用去管何时 delete 这些指针。这也是必须的,向里面存值,存储的指针必须是 new 产生的,否则就会涉及到内存泄漏的问题)。
将对象插入缓存时,可以指定其成本(cost),该成本应与对象占用的内存量具有某种近似关系。 当所有对象的成本(totalCost())的总和超过缓存的限制(maxCost())时,QCache就开始删除缓存中的对象以保持在限制之下,删除操作从最近访问较少的对象开始(内部的缓存淘汰机制:LRU)。
默认情况下,QCache的maxCost()为100。您可以在QCache构造函数中指定不同的值
二、常用函数介绍
- 使用insert() 函数来插入对象。
- 使用object()函数来获取对象。
- 使用remove() 函数来删除对象。
- 使用take() 函数来取出对象,但是不删除。(这里的取出和获取不是一回事,取出就是在缓存中找不到了)
- 使用clear() 函数来释放缓存中所有的对象。
- 使用contains() 函数判断当前缓存中是否包含某个key。
- 使用count() 或 size() 函数获得当前缓存中保存的对象的个数。
- 使用isEmpty() 函数判断当前缓存是否包含有对象。
- 使用keys()函数获取当前缓存中的key列表。
- 使用setMaxCost ()函数设置缓存的最大允许总成本,如果当前总成本大于要设置的成本,则会立即删除某些对象
三、案例
UserDao.h
#ifndef USERDAO_H
#define USERDAO_H
#include <QList>
#include <QString>
#include <QVariant>
#include <QVariantMap>
class User;
class UserDao
{
public:
static User findUserById(int id);
static QList<User> findAll();
static int insert(User *user);
static bool update(User *user);
static bool deleteUser(int id);
private:
/**
* @brief 将 QVariantMap 对象转换成 User
* @param rowMap 数据库查询到的结果转换成的 QVariantMap 对象
* @return User
*/
static User mapToUser(const QVariantMap &rowMap);
static QString getSql(const QString &functionName);
//根据函数名和参数构造缓存的key
static QString buildKey(std::initializer_list<QString> params);
//单条记录缓存,key:"id",value:"User"
static QCache<QString, User> userCache;
//缓存多条记录,key:"方法名+参数",value:id集合
static QCache<QString, QList<int>> usersCache;
};
#endif // USERDAO_H
UserDao.cpp
#include "UserDao.h"
#include "db/SqlUtil.h"
#include "db/DbUtil.h"
#include "demo/bean/User.h"
#include <QCache>
/**
* <?xml version="1.0" encoding="UTF-8"?>
<sqls namespace="User">
<define id="fields">id, username, password, email, mobile</define>
<sql id="findByUserId">
SELECT <include defineId="fields"/> FROM user WHERE id=%1
</sql>
<sql id="findAll">
SELECT id, username, password, email, mobile FROM user
</sql>
<sql id="insert">
INSERT INTO user (username, password, email, mobile)
VALUES (:username, :password, :email, :mobile)
</sql>
<sql id="update">
UPDATE user SET username=:username, password=:password,
email=:email, mobile=:mobile
WHERE id=:id
</sql>
</sqls>
*/
static const QString SQL_NAMESPACE_USER = "User";
/*
* 缓存基本策略:
*
* 单个对象缓存:key:就是对象id;value:就是对象
* 多个对象缓存(比如分页查询): key:就是“函数名+参数1+参数2+...”;value:就是“对象id集合”
*
* 1、更新策略:只更新单个对象缓存
* 2、删除策略:只删除单个对象缓存
* 3、查询策略:查询策略又分为单个对象查询和多个对象查询
* (1)单个对象查询:基本一致
* (2)多个对象查询:获取缓存,取出id集合,然后遍历id集合,再去单个对象缓存里去找:
* a.若全部找到,则返回对象集合
* b.若未找到全部,则说明有对象已被删除,删除该缓存,重新查询数据库,更新缓存,返回
*
* 备注:若是自己写的局部缓存,就按上述策略;若使用像redis这种全局缓存,则重点需要构建key:对象的全局唯一id:id
*/
//在.h文件中定义了静态变量,在.cpp文件中使用,必须再次申明,否则报错
QCache<QString, User> UserDao::userCache;
QCache<QString, QList<int>> UserDao::usersCache;
User UserDao::findUserById(int id)
{
QString key = "id";
if (userCache.contains(key)) {
return *userCache.object(key);
} else {
User user = DbUtil::selectBean(mapToUser, getSql("findUserById").arg(id));
userCache.insert(key, &user);
return user;
}
}
/**
* 查询所有数据,基本思路:
* 1、根据key获取id集合
* 2、遍历id集合,判断缓存是否存在该id对应的对象,一旦没有,则中断循环,清空对象集合
* 3、判断对象集合是否为空,非空则说明缓存中对应的对象都在,返回缓存中对象即可
* 4、若此时程序还未终止,说明缓存中数据有问题,查询数据库,更新缓存
*/
QList<User> UserDao::findAll()
{
QString key = "findUserById";
QList<User> users;
//QCache自动获得被插入对象的所有权,并在需要的时候自动释放他们来为新插入的对象腾出空间,所以不用担心内存泄漏的问题
//而且这里必须使用new,否则在该函数结束以后,集合变量就被删除了,导致缓存中的指针成为野指针!!!!!
QList<int> *ids = new QList<int>();
if (usersCache.contains(key)) {
for (int id : *usersCache.object(key)) {
if (!userCache.contains(QString::number(id))) {
users.clear();
break;
}
users.append(*userCache.object(QString::number(id)));
}
if (!users.isEmpty()) {
return users;
}
}
users = DbUtil::selectBeans(mapToUser, getSql("findAll"));
for (User user : users) {
ids->append(user.getId());
}
usersCache.insert(key, ids);
return users;
}
int UserDao::insert(User *user)
{
//构造参数
QVariantMap params;
params["username"] = user->getUsername();
params["password"] = user->getPassword();
params["email"] = user->getEmail();
params["mobile"] = user->getMobile();
int newId = DbUtil::insert(getSql("insert"), params);
//-1作为判断 User 是否为空的标志位
if (newId != -1) {
userCache.insert(QString::number(newId), user);
//因为新插入数据,所以需要更新数据集合(分页查询、全部查询),若不更新,则永远找不到新数据
//现在采用的策略是删除所有的集合缓存,后续有什么好的方法再改进吧
usersCache.clear();
}
return newId;
}
bool UserDao::update(User *user)
{
//构造参数
QVariantMap params;
params["id"] = user->getId();
params["username"] = user->getUsername();
params["password"] = user->getPassword();
params["email"] = user->getEmail();
params["mobile"] = user->getMobile();
bool result = DbUtil::update(getSql("update"), params);
if (result) {
//修改缓存
userCache.insert(QString::number(user->getId()), user);
}
return result;
}
bool UserDao::deleteUser(int id)
{
//更新缓存
QVariantMap params;
params["id"] = id;
bool result = DbUtil::update(getSql("delete"), params);
if (result) {
userCache.remove(QString::number(id));
}
return result;
}
/**
* @brief 将 QVariantMap 对象转换成 User
* @param rowMap 数据库查询到的结果转换成的 QVariantMap 对象
* @return User
*/
User UserDao::mapToUser(const QVariantMap &rowMap)
{
User user;
user.setId(rowMap.value("id", -1).toInt());
user.setUsername(rowMap.value("username").toString());
user.setPassword(rowMap.value("password").toString());
user.setEmail(rowMap.value("email").toString());
user.setMobile(rowMap.value("mobile").toString());
return user;
}
/**
* @brief 从配置文件中取出sql
* @param 函数名
* @return sql
*/
QString UserDao::getSql(const QString &functionName)
{
return Singleton<SqlUtil>::getInstance().getSql(SQL_NAMESPACE_USER, functionName);
}
QString UserDao::buildKey(std::initializer_list<QString> params)
{
QString key;
for (auto param : params) {
key += param + ":";
}
return key;
}
调用:
void testUpdate() {
//必须是new出来的对象,否则 QCache 拿不到所有权,就无法自动删除该对象,导致内存泄漏
User *user = new User();
user->setId(87);
user->setUsername("Alice2");
user->setPassword("5666");
user->setEmail("23423@164.com");
user->setMobile("1234241234");
UserDao::update(user);
}