|
package ru.circumflex.orm
import ru.circumflex.core._
import collection.mutable.HashMap
import net.sf.ehcache._
import java.util.concurrent.atomic._
import java.io.Serializable
|
Context-Level Cache
The CacheService trait defines minimum functionality required for organizing context-level cache.
The context-level cache is designed to maintain records within a single transaction. This functionality is required for all data-retrieval operations.
The cache consists of two logical parts:
- record cache holds individual records by their relations and
id s;
- inverse cache holds sequences of records by their associations and their parent's
id s.
|
trait CacheService {
def invalidate: Unit = {
invalidateRecords
invalidateInverse
}
|
Records Cache
Following methods are used to maintain records cache:
invalidateRecords clears all records from cache or only those who correspond to specified relation ;
getRecord retrieves a record from cache by specified relation and id ;
updateRecord updates a cache with specified record ;
evictRecord removes a record from cache by specified relation and id .
|
def invalidateRecords: Unit
def invalidateRecords[PK, R <: Record[PK, R]](
relation: Relation[PK, R]): Unit
def cacheRecord[PK, R <: Record[PK, R]](
id: PK, relation: Relation[PK, R], record: => Option[R]): Option[R]
def evictRecord[PK, R <: Record[PK, R]](
id: PK, relation: Relation[PK, R]): Unit
def updateRecord[PK, R <: Record[PK, R]](
id: PK, relation: Relation[PK, R], record: R): R = {
evictRecord(id, relation)
cacheRecord(id, relation, Some(record))
record
}
|
Inverse Cache
Following methods are used to maintain inverse cache:
invalidateInverse clears all records from inverse cache or only those who correspond to specified association ;
cacheInverse retrieves children records from cache by specified association and their parentId or updates cache correspondingly;
updateInverse updates an inverse cache with specified children ;
evictInverse removes children from inverse cache by specified association and parentId ;
|
def invalidateInverse: Unit
def invalidateInverse[K, C <: Record[_, C], P <: Record[K, P]](
association: Association[K, C, P]): Unit
def cacheInverse[K, C <: Record[_, C], P <: Record[K, P]](
parentId: K, association: Association[K, C, P], children: => Seq[C]): Seq[C]
def evictInverse[K, C <: Record[_, C], P <: Record[K, P]](
parentId: K, association: Association[K, C, P]): Unit
def updateInverse[K, C <: Record[_, C], P <: Record[K, P]](
parentId: K, association: Association[K, C, P], children: Seq[C]): Seq[C] = {
evictInverse(parentId, association)
cacheInverse(parentId, association, children)
}
def evictInverse[K, P <: Record[K, P]](
parent: P): Unit
}
|
The default cache service implementation relies on Scala mutable HashMap s. It can be overriden by setting the orm.contextCache parameter.
|
class DefaultCacheService extends CacheService {
class CacheMap extends HashMap[Any, HashMap[Any, Any]] {
override def apply(key: Any): HashMap[Any, Any] =
super.getOrElseUpdate(key, new HashMap[Any, Any])
}
val _recordsCache = new CacheMap
val _inverseCache = new CacheMap
// Records cache
def invalidateRecords: Unit = {
_recordsCache.clear()
Cacheable.relations.foreach(_.invalidateCache)
}
def invalidateRecords[PK, R <: Record[PK, R]](relation: Relation[PK, R]): Unit =
relation match {
case c: Cacheable[_, _] => c.invalidateCache
case _ => _recordsCache.remove(relation)
}
def evictRecord[PK, R <: Record[PK, R]](id: PK, relation: Relation[PK, R]): Unit =
relation match {
case c: Cacheable[_, _] => c.evict(id)
case _ => _recordsCache(relation).remove(id)
}
def cacheRecord[PK, R <: Record[PK, R]](
id: PK, relation: Relation[PK, R], record: => Option[R]): Option[R] =
relation match {
case c: Cacheable[PK, R] => c.cache(id, record)
case _ =>
val c = _recordsCache(relation)
c.get(id).map { r =>
Statistics.recordCacheHits.incrementAndGet
r.asInstanceOf[R]
} orElse {
Statistics.recordCacheMisses.incrementAndGet
val v = record
v.map { r =>
c.update(id, r)
r
}
}
}
// Inverse cache
def invalidateInverse: Unit =
_inverseCache.clear()
def invalidateInverse[K, C <: Record[_, C], P <: Record[K, P]](association: Association[K, C, P]): Unit =
_inverseCache(association).clear()
def cacheInverse[K, C <: Record[_, C], P <: Record[K, P]](
parentId: K, association: Association[K, C, P], children: => Seq[C]): Seq[C] = {
val cache = _inverseCache(association)
cache.get(parentId) match {
case Some(children: Seq[C]) =>
Statistics.inverseCacheHits.incrementAndGet
children
case _ =>
Statistics.inverseCacheMisses.incrementAndGet
val c = children
cache.update(parentId, c)
c
}
}
def evictInverse[K, C <: Record[_, C], P <: Record[K, P]](
parentId: K, association: Association[K, C, P]): Unit =
_inverseCache(association).remove(parentId)
def evictInverse[K, P <: Record[K, P]](
parent: P): Unit = {
_inverseCache.keys.foreach {
case a: Association[K, _, P] =>
if (a.parentRelation == parent.relation)
_inverseCache(a).remove(parent.PRIMARY_KEY())
case _ =>
}
}
}
|
The CacheService object is used to retrieve context-bound cache service.
|
object CacheService {
def get: CacheService = ctx.get("orm.contextCache") match {
case Some(cs: CacheService) => cs
case _ =>
val cs = cx.instantiate[CacheService]("orm.contextCache", new DefaultCacheService)
ctx.update("orm.contextCache", cs)
return cs
}
}
|
Application-Level Cache
Circumflex ORM lets you organize application-scope cache (backed by Terracotta Ehcache) for any relation of your application: just mix in the Cacheable trait into your relation. Note that since one record instance may become accessible to several threads, the modification of such records is a subject for concurrency control.
|
trait Cacheable[PK, R <: Record[PK, R]] extends Relation[PK, R] { this: R =>
protected val _cache: Ehcache = ehcacheManager.addCacheIfAbsent(qualifiedName)
// Per-relation statistics
val cacheHits = new AtomicInteger(0)
val cacheMisses = new AtomicInteger(0)
def cache(id: PK, record: => Option[R]): Option[R] = {
var elem = _cache.get(id)
if (elem == null) {
elem = new Element(id, record)
_cache.put(elem)
cacheMisses.incrementAndGet
Statistics.recordCacheMisses.incrementAndGet
} else {
cacheHits.incrementAndGet
Statistics.recordCacheHits.incrementAndGet
}
return elem.getValue().asInstanceOf[Option[R]]
}
def evict(id: PK): Unit =
_cache.remove(id)
def invalidateCache(): Unit =
_cache.removeAll()
afterInsert(r => cache(r.PRIMARY_KEY(), Some(r)))
afterUpdate(r => cache(r.PRIMARY_KEY(), Some(r)))
afterDelete(r => evict(r.PRIMARY_KEY()))
Cacheable.add(this)
}
object Cacheable {
private var _relations: Seq[Cacheable[_, _]] = Nil
def relations = _relations
def add[PK, R <: Record[PK, R]](relation: Cacheable[PK, R]): this.type = {
_relations ++= List(relation)
return this
}
}
|