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
Getirme
operasyonlarında, talep edilen verininCache
iç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
,V
girdisini yazar. - Birden fazla
K
,V
girdisini 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 `Book
türünden bir nesneyi veritabanına yazar.- Koleksiyon içeriği kadar
(1)
‘i tekrarlar. isbn
(Key)’e göreBook
tü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
Cache
içerisine eklenen nesnelerSerializable
olmalı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