三十七、Qt缓存之QCache
阅读原文时间:2021年04月21日阅读:1

这吊玩意,到处是坑!慎用!!!

一、基本介绍

  QCache类是一个模板类。QCache 就定义了一个缓存,其类似于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);
}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器