
JCache – CacheLoader ve CacheWriter Kullanmak
Bir önceki yazıda JCache standardının temellerinden bahsetmiştik. (Bkz. JSR 107 – JCache Standardına İlk Bakış) Şimdi ise bu temel üzerinden CacheLoader ve CacheWriter yapılarını inceleyeceğiz.
JCache standardında temel olarak üç tip operasyon bulunmaktadır;
- Ekleme
put,putAll,putIfAbsent,getAndPut- Getirme
get,getAll,getAndPut,getAndRemove,getAndReplace- Silme
remove,removeAll,getAndRemove
Varsayılan ayarlarda tüm bu operasyonlar Cache nesnesinin içerisinde anabellek üzerinde sürdürülmektedir.
- CacheLoader
Getirmeoperasyonlarında, talep edilen verininCacheiçerisinde bulunmama durumunda ikincil bir verikaynağından alınabilmesi için tasarlanan bir arayüzdür.

Varsayılan ayarlarla anahtar(key) değerine göre değer(value) döndürülür. Değer bulunamıyorsa yokluğu temsilen null döndürülür.

CacheLoader türünden bir nesne konfigürasyona eklendiğinde ise, talep edilen veri Cache alanında bulunmadığında (yani null olduğunda), CacheLoader içinde tanımlanan diğer bir verikaynağından veri sunulabilir. Buradaki verikaynağının ne olacağı ise tamamiyle geliştiricinin seçimine bağlıdır. (Örn: DB, NoSQL, HDFS, FS vb.)
public interface CacheLoader<K, V> { (1)
V load(K key); (2)
Map<K, V> loadAll(Iterable<? extends K> keys); (3)
}
K→ anahtarın tipini,V→ değerin tipini temsil eder.- key değerine göre value döndürmelidir.
- key değerlerine göre
key⇒`value` çiftlerini döndürmelidir.
CacheLoader bir ve birden çok key (anahtar) veriye göre değer ve değerler döndüren load ve loadAll metodlarına sahiptir. Aşağıda yer alan BackupReader sınıfı, CacheLoader arayüzünün uygulanışını içeren bir örnektir. Yükleme işlemini JPA kullanarak bir ilişkisel veritabanından yapmaktadır.
public class BackupReader implements CacheLoader<Long, Book> {
private final EntityManager em = //..;
private static final Logger logger = //..
@Override (1)
public Book load(Long isbn) {
logger.info(isbn + " Cache içinde bulunamadı.");
logger.info(isbn + " db'den yükleniyor.");
Book found = em.find(Book.class, isbn);
if (Objects.nonNull(found))
logger.info(isbn + " db'den yüklendi.");
else
logger.info(isbn + " db'de de bulunamadı.");
return found;
}
@Override (2)
public Map<Long, Book> loadAll(Iterable<? extends Long> keys){
Map<Long, Book> allBooks = new HashMap<>();
for (Long key : keys) {
allBooks.put(key, load(key));
}
return allBooks;
}
}
- isbn numarasına göre kaydı DB’den yükler
- isbn numaralarına göre kayıtları DB’den yükler

CacheWriter yapılandırması yapıldığında, herhangi bir silme veya ekleme operasyonunda, aynı işlem diğer veri kaynağında da uygulanabilmektedir.
public interface CacheWriter<K, V> {
void write(Cache.Entry<K,V> entry); (1)
void writeAll(Collection<Cache.Entry<K,V>> entries); (2)
void delete(Object key); (3)
void deleteAll(Collection<?> keys); (4)
}
- Bir
K,Vgirdisini yazar. - Birden fazla
K,Vgirdisini yazar. - key girdisine göre kaydı siler.
- key girdilerine göre kayıtları siler.
CacheWriter sınıfı hem yazma hem de silme operasyonlarını takip edebilecek metodlar bulundırmaktadır.
Aşağıda yer alan BackupWriter sınıfı, CacheWriter arayüzünün uygulanışını içeren bir örnektir. Yazma ve Silme işlemlerini JPA kullanarak bir ilişkisel veritabanından yapmaktadır.
public class BackupWriter implements CacheWriter<Long, Book> {
private final EntityManager em = //..
private static final Logger logger = //..
@Override
public void write(Cache.Entry<Long,Book> entry){ (1)
Long isbn = entry.getKey();
Book book = entry.getValue();
logger.info(isbn + " db'ye yazılıyor");
em.getTransaction().begin();
em.merge(book);
em.getTransaction().commit();
logger.info(isbn + " db'ye yazıldı");
}
@Override
public void writeAll(Collection<Cache.Entry<Long,Book>> entries){
entries.forEach(this::write); (2)
}
@Override
public void delete(Object isbn) { (3)
Book found = em.find(Book.class, isbn);
if (Objects.nonNull(found)) {
logger.info(isbn + " db'den siliniyor.");
em.getTransaction().begin();
em.remove(found);
em.getTransaction().commit();
logger.info(isbn + " db'den silindi.");
}
}
@Override
public void deleteAll(Collection<?> keys) { (4)
keys.forEach(this::delete);
}
}
Cache’e yazılan `Booktüründen bir nesneyi veritabanına yazar.- Koleksiyon içeriği kadar
(1)‘i tekrarlar. isbn(Key)’e göreBooktüründen bir nesnesi veritabanından siler.- Koleksiyon içeriği kadar
(3)‘ü tekrarlar.
CacheLoader ve CacheWriter Aktifleştirmek
CacheLoader ve CacheWriter türünden yükleyicileri tanıtabilmek için Configuration sınıfının setWriteThrough ve setReadThrough metodlarına true parametresi gönderilmelidir.
MutableConfiguration config = new MutableConfiguration();
..
config.setWriteThrough(true);
config.setReadThrough(true);
..
Write Through ve Read Through özellikleri açıldıktan sonra, bir üretici nesne (Factory) üzerinden CacheLoader ve CacheWriter nesneleri tanıtılmalıdır. Tanıtım işlemi Configuration sınıfının setCacheLoaderFactory ve setCacheWriterFactory metodları üzerinden yapılmaktadır.
MutableConfiguration config = new MutableConfiguration();
..
config.setCacheWriterFactory(()-> new BackupWriter());
// veya
config.setCacheWriterFactory(BackupWriter::new);
// -----------
config.setCacheLoaderFactory(()-> new BackupReader());
// veya
config.setCacheLoaderFactory(BackupReader::new);
..
- Not
- Bu örnekte Lambda deyimlerinden ve Method referans özelliklerinden faydalanılmıştır.
Uygulama Zamanı
Şimdi BackupReader ve BackupWriter kullanarak bir JCache uygulaması geliştirelim.
Uygulama Long türündeki isbn anahtarına göre Book türündeki nesne değerlerini tutacaktır.
@Entity
public class Book implements Serializable {
@Id
private Long isbn;
private String name;
private double price;
// getter,setter,constructor metodları
}
Book sınıfı önbellekleme için kullanılacak bir JPA Entity sınıfıdır.
- Not
Cacheiçerisine eklenen nesnelerSerializableolmalıdır.
Uygulama içerisinde 123444L, 123445L ve 123446L isbn numaralı Book nesneleri Cache içerisine eklenmektedir.
Uygulama içerisinde eklenmeyen ama veritabanından bulunan ayrıca 123447L, 123448L ve 123449L isbn numaralı kayıtlar veritabanında bulunmaktadır.
CachingProvider cachingProvider = Caching.getCachingProvider();
CacheManager cacheManager = cachingProvider.getCacheManager();
MutableConfiguration config = new MutableConfiguration();
config.setTypes(Long.class, Book.class);
config.setWriteThrough(true);
config.setReadThrough(true);
config.setCacheWriterFactory(()-> new BackupWriter());
config.setCacheLoaderFactory(BackupReader::new);
Cache<Long, Book> kodcu = cacheManager.createCache("kodcu", config);
kodcu.put(123444L, new Book(123444L, "Java ve Yazılım Tasarımı", 35));
kodcu.put(123445L, new Book(123445L, "Java Mim. Kur. Çözümler", 25));
kodcu.put(123446L, new Book(123446L, "Java ve WebSocket", 30));
Book b1 = kodcu.get(123445L); // in Cache
Book b2 = kodcu.get(123447L); // not in Cache but in DB
Book b3 = kodcu.get(123999L); // neither in Cache nor DB
3 adet put ve 3 adet get isteğinden sonra Log çıktıları aşağıdaki gibi olmaktadır.
INFO: 123444 db'ye yazılıyor INFO: 123444 db'ye yazıldı INFO: 123445 db'ye yazılıyor INFO: 123445 db'ye yazıldı INFO: 123446 db'ye yazılıyor INFO: 123446 db'ye yazıldı INFO: 123447 Cache içinde bulunamadı. INFO: 123447 db'den yükleniyor. INFO: 123447 db'den yüklendi. INFO: 123999 Cache içinde bulunamadı. INFO: 123999 db'den yükleniyor. INFO: 123999 db içinde de de bulunamadı.
- Cache içerisine put ile atılan nesneler aynı zamanda veritabanına da kazınmaktadır.
- Cache içerisinde var olan veriler Cache den sunulmaktadır.
- Cache içerisinde var olmayan veriler ise Veritabanından sunulmaktadır.
Uygulama örneğine aşağıdaki bağlantıdan erişebilirsiniz
Tekrar görüşmek dileğiyle.
Tag:backend, cache, cachewriter, cahcereader, jcache



