Doctrine2
SymfonyDay 20

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

More than 3 years have passed since last update.


はじめに

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

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がコールされます。

https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Connection.php

/**

* 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年連続参加できました。

来年も懲りずに、参加しようと思います。

それでは、少し早いですが、よいお年を。