LoginSignup
14
13

More than 5 years have passed since last update.

Doctrine DBALで取得したデータをキャッシュする

Posted at

はじめに

例えば、都道府県のデータをデータベースに格納していて、単純に検索するだけの処理がある場合、検索結果はほぼ普遍的なわけで、都度データベースにクエリを投げるのではなく、同じクエリであれば、結果をキャッシュしたほうが、より効率的です。

Doctrine DBALの下記ページを参考に試しに、キャッシュしてみます。
http://doctrine-orm.readthedocs.org/projects/doctrine-dbal/en/latest/reference/caching.html

QueryCacheProfileの作成

キャッシュをどのように保存するか、どの程度キャッシュするかなど、キャッシュに関する設定を行います。

use Doctrine\Common\Cache\FilesystemCache;
use Doctrine\DBAL\Cache\QueryCacheProfile;

$cache = new FilesystemCache('/path/to/save');
$cacheProfile = new QueryCacheProfile(0, 'cache-key', $cache);

いろいろなcache

doctrine/cache には、いろいろなcacheクラスがあります。
https://github.com/doctrine/cache/tree/master/lib/Doctrine/Common/Cache

  • FilesystemCache
  • MongoDBCache
  • MemcachedCache
  • PhpFileCache
  • RedisCache

executeCacheQuery

executeQueryにて、QueryCacheProfileを指定すると、executeCacheQueryがコールされます。

/**
 * Executes a caching query.
 *
 * @param string                                 $query  The SQL query to execute.
 * @param array                                  $params The parameters to bind to the query, if any.
 * @param array                                  $types  The types the previous parameters are in.
 * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp    The query cache profile.
 *
 * @return \Doctrine\DBAL\Driver\ResultStatement
 *
 * @throws \Doctrine\DBAL\Cache\CacheException
 */
public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
{
    $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
    if ( ! $resultCache) {
        throw CacheException::noResultDriverConfigured();
    }

    list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types);

    // fetch the row pointers entry
    if ($data = $resultCache->fetch($cacheKey)) {
        // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
        if (isset($data[$realKey])) {
            $stmt = new ArrayStatement($data[$realKey]);
        } elseif (array_key_exists($realKey, $data)) {
            $stmt = new ArrayStatement(array());
        }
    }

    if (!isset($stmt)) {
        $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
    }

    $stmt->setFetchMode($this->defaultFetchMode);

    return $stmt;
}

closeCursor

キャッシュがない場合、ResultCacheStatement::closeCursorをコールすることで、結果セットをキャッシュすることができます。
https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php

public function closeCursor()
{
    $this->statement->closeCursor();
    if ($this->emptied && $this->data !== null) {
        $data = $this->resultCache->fetch($this->cacheKey);
        if ( ! $data) {
            $data = array();
        }
        $data[$this->realKey] = $this->data;

        $this->resultCache->save($this->cacheKey, $data, $this->lifetime);
        unset($this->data);
    }
}

実行結果

キャッシュがない場合、executeQueryは、データベースからデータを取得し、ResultCacheStatementを返却します。
キャッシュがある場合、executeQueryは、キャッシュからデータを取得し、ArrayStatementを返却します。

use Doctrine\DBAL\Cache\QueryCacheProfile;

public function findBySql($sql, $params = [], QueryCacheProfile $cacheProfile) {
    $statement = $this->connection->executeQuery($sql, $params, [], $cacheProfile);
    /* キャッシュがない場合 */
    /* @var $statement \Doctrine\DBAL\Cache\ResultCacheStatement */
    /* キャッシュがない場合 */
    /* @var $statement \Doctrine\DBAL\Cache\ArrayStatement */
    $result = $statement->fetchAll();
    $statement->closeCursor();
    return $result;
}

おわりに

Symfony Advent Calendarなのに、Doctrine/DBALの小ネタで大変恐縮ですが、実戦ですぐ使えそうなネタだったので、調査もかねて書いてみました。
これで、2年連続参加できました。
来年も懲りずに、参加しようと思います。
それでは、少し早いですが、よいお年を。

14
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
13