From acc04dd1dead1a75452ee8b4ffd5338d81e3d609 Mon Sep 17 00:00:00 2001 From: QwQ-dev Date: Sun, 22 Dec 2024 09:34:00 +0800 Subject: [PATCH] feat: Automatic lock processing, overall reconstruction. --- cache/README.md | 152 ++++++++++------ .../qwqdev/library/cache/CacheLauncher.java | 2 +- .../cache/factory/CacheServiceFactory.java | 79 +++++++++ .../CaffeineAsyncCacheServiceFactory.java | 38 ---- .../factory/CaffeineCacheServiceFactory.java | 38 ---- .../factory/MemoryCacheServiceFactory.java | 41 ----- .../factory/RedisCacheServiceFactory.java | 26 --- .../library/cache/model/LockSettings.java | 33 ++++ .../cache/service/AbstractCacheService.java | 98 +++++++++++ .../cache/service/AbstractLockableCache.java | 69 ++++++++ .../cache/service/CacheServiceInterface.java | 65 +++++++ .../library/cache/service/LockableCache.java | 37 ++++ .../caffeine/CaffeineAsyncCacheService.java | 80 ++------- .../CaffeineAsyncCacheServiceInterface.java | 48 ----- .../caffeine/CaffeineCacheService.java | 64 ++----- .../CaffeineCacheServiceInterface.java | 33 ---- .../service/memory/MemoryCacheService.java | 74 -------- .../memory/MemoryCacheServiceInterface.java | 50 ------ .../service/redis/RedisCacheService.java | 166 ++++++++++-------- .../redis/RedisCacheServiceInterface.java | 81 +++++---- 20 files changed, 651 insertions(+), 623 deletions(-) create mode 100644 cache/src/main/java/me/qwqdev/library/cache/factory/CacheServiceFactory.java delete mode 100644 cache/src/main/java/me/qwqdev/library/cache/factory/CaffeineAsyncCacheServiceFactory.java delete mode 100644 cache/src/main/java/me/qwqdev/library/cache/factory/CaffeineCacheServiceFactory.java delete mode 100644 cache/src/main/java/me/qwqdev/library/cache/factory/MemoryCacheServiceFactory.java delete mode 100644 cache/src/main/java/me/qwqdev/library/cache/factory/RedisCacheServiceFactory.java create mode 100644 cache/src/main/java/me/qwqdev/library/cache/model/LockSettings.java create mode 100644 cache/src/main/java/me/qwqdev/library/cache/service/AbstractCacheService.java create mode 100644 cache/src/main/java/me/qwqdev/library/cache/service/AbstractLockableCache.java create mode 100644 cache/src/main/java/me/qwqdev/library/cache/service/CacheServiceInterface.java create mode 100644 cache/src/main/java/me/qwqdev/library/cache/service/LockableCache.java delete mode 100644 cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineAsyncCacheServiceInterface.java delete mode 100644 cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineCacheServiceInterface.java delete mode 100644 cache/src/main/java/me/qwqdev/library/cache/service/memory/MemoryCacheService.java delete mode 100644 cache/src/main/java/me/qwqdev/library/cache/service/memory/MemoryCacheServiceInterface.java diff --git a/cache/README.md b/cache/README.md index bdb9dc0..42d3c5b 100644 --- a/cache/README.md +++ b/cache/README.md @@ -1,6 +1,7 @@ ### cache -The cache module based on memory and memory database, the part based on Memory and Caffeine has been completed. +The module is a caching solution that supports `Caffeine`'s `Cache` and `AsyncCache`, +as well as an in-memory `Redis` database implementation, offering functional programming support and automatic lock handling. The original purpose of this module was to design a L1 cache for the `mongodb` module, but now it is **general-purpose**. @@ -15,77 +16,114 @@ dependencies { ``` ```java -public class Main { +public class CacheLauncher { public static void main(String[] args) { - // test object - Person person = new Person(); - person.setUuid(UUID.randomUUID()); - person.setName("Johannes"); - person.setAge(20); + Config config = new Config(); + config.useSingleServer().setAddress("redis://127.0.0.1:6379"); - Person person2 = new Person(); - person2.setUuid(UUID.randomUUID()); - person2.setName("Johannes"); - person2.setAge(45); + /* + * Redis cache example + */ + RedisCacheServiceInterface redisCache = + CacheServiceFactory.createRedisCache(config); + + // get method will return object type, getWithType can specify the return type + int integer = redisCache.getWithType( + // get cache value by key + cache -> cache.getBucket("key").get(), + + // if cache miss, do this, like query from database + () -> 1, + + // if cacheAfterQuery is true, do this, store to cache + (cache, queryValue) -> cache.getBucket("key").set(queryValue), + + // cacheAfterQuery + true + ); - // create connection - MongoDBConnectionConfig mongoDBConnectionConfig = - MongoDBConnectionConfigFactory.create("test", "mongodb://localhost:27017/"); - Datastore datastore = mongoDBConnectionConfig.getDatastore(); + // string + String string = redisCache.getWithType( + // get cache value by key + cache -> cache.getBucket("key2").get(), + + // if cache miss, do this, like query from database + () -> "qwq", + + // if cacheAfterQuery is true, do this, store to cache + (cache, queryValue) -> cache.getBucket("key2").set(queryValue), + + // cacheAfterQuery + true + ); - // save data - datastore.save(person); - datastore.save(person2); - /* - * create memory cache service - * - * By default, MemoryCacheService uses ConcurrentHashMap, which means it is thread-safe. - * When a custom Map is passed in, thread safety is tied to the Map. + * Caffeine cache example + * + * Cache + * - cache key and cache value type + * + * Cache, String> + * - Cache value type */ - MemoryCacheServiceInterface memoryCacheService = MemoryCacheServiceFactory.create(); - - // expiration settings (this is for one element, not the entire map) - ExpirationSettings expirationSettings = ExpirationSettings.of(100, TimeUnit.DAYS); - - // db query - Supplier dbQuery = () -> String.valueOf( - datastore.find(Person.class) - .filter( - Filters.eq("name", "Johannes"), - Filters.gt("age", 30) - ) - .iterator() - .tryNext() + CacheServiceInterface, String> caffeineCache = + CacheServiceFactory.createCaffeineCache(); + + // get + String qwq = caffeineCache.get( + // get cache value by key + + cache -> cache.getIfPresent(1), + // if cache miss, do this, like query from database + () -> "qwq", + + // if cacheAfterQuery is true, do this, store to cache + (cache, queryValue) -> cache.put(1, queryValue), + + // cacheAfterQuery + true ); - // Testing time: 45, db query - memoryCacheService.get("testKey", dbQuery, true, expirationSettings); + // get with lock + String qwq2 = caffeineCache.get( + // get lock + cache -> new ReentrantLock(), - // Now we query again test L1 cache, Testing time: 0 - memoryCacheService.get("testKey", dbQuery, true, expirationSettings); - } -} + // get cache value by key + cache -> cache.getIfPresent(1), -@Data -@Entity("persons") -class Person { - @Id - private UUID uuid; + // if cache miss, do this, like query from database + () -> "qwq", - private String name; - private int age; -} -``` + // if cacheAfterQuery is true, do this, store to cache + (cache, queryValue) -> cache.put(1, queryValue), -It is very simple to use. We have created cache service classes for `Memory`, `Caffeine`-based `Cache`, and `AsyncCache`. + // cacheAfterQuery + true, -We only need to create them through the factory class to use them without manually writing the internal logic. + // lock settings + LockSettings.of(1, 1, TimeUnit.MINUTES) + ); -### scalability + // thread-safe execution of something, redis cache same + caffeineCache.execute( + // get lock + cache -> new ReentrantLock(), -In-memory database level cache is already in the planning. + // do something + cache -> cache.getIfPresent(1), -For caches like L1 and L2, developers should manually nest them or directly use the L1 and L2 level caches provided by the `data` module. \ No newline at end of file + // lock settings + LockSettings.of(1, 1, TimeUnit.MINUTES) + ); + + /* + * Although most of caffeine's methods are thread-safe, + * we can directly use getCache() to operate these methods. + */ + caffeineCache.getCache().put(2, "hi"); + } +} +``` \ No newline at end of file diff --git a/cache/src/main/java/me/qwqdev/library/cache/CacheLauncher.java b/cache/src/main/java/me/qwqdev/library/cache/CacheLauncher.java index bf76c98..4cc541c 100644 --- a/cache/src/main/java/me/qwqdev/library/cache/CacheLauncher.java +++ b/cache/src/main/java/me/qwqdev/library/cache/CacheLauncher.java @@ -13,4 +13,4 @@ @FairyLaunch @InjectableComponent public class CacheLauncher extends Plugin { -} +} \ No newline at end of file diff --git a/cache/src/main/java/me/qwqdev/library/cache/factory/CacheServiceFactory.java b/cache/src/main/java/me/qwqdev/library/cache/factory/CacheServiceFactory.java new file mode 100644 index 0000000..a948e0e --- /dev/null +++ b/cache/src/main/java/me/qwqdev/library/cache/factory/CacheServiceFactory.java @@ -0,0 +1,79 @@ +package me.qwqdev.library.cache.factory; + +import com.github.benmanes.caffeine.cache.AsyncCache; +import com.github.benmanes.caffeine.cache.Cache; +import lombok.experimental.UtilityClass; +import me.qwqdev.library.cache.service.CacheServiceInterface; +import me.qwqdev.library.cache.service.caffeine.CaffeineAsyncCacheService; +import me.qwqdev.library.cache.service.caffeine.CaffeineCacheService; +import me.qwqdev.library.cache.service.redis.RedisCacheService; +import me.qwqdev.library.cache.service.redis.RedisCacheServiceInterface; +import org.redisson.config.Config; + +/** + * Factory for creating cache service instances. + * + *

Provides centralized creation of different cache implementations + * with various configuration options. + * + * @author qwq-dev + * @since 2024-12-21 20:10 + */ +@UtilityClass +public final class CacheServiceFactory { + /** + * Creates a Redis cache service with the specified configuration. + * + * @param config the Redis configuration + * @return a new Redis cache service instance + */ + public static RedisCacheServiceInterface createRedisCache(Config config) { + return new RedisCacheService(config); + } + + /** + * Creates a Caffeine synchronous cache service with custom configuration. + * + * @param cache the Caffeine cache + * @param the cache key type + * @param the cache value type + * @return a new Caffeine cache service instance + */ + public static CacheServiceInterface, V> createCaffeineCache(Cache cache) { + return new CaffeineCacheService<>(cache); + } + + /** + * Creates a Caffeine synchronous cache service with default configuration. + * + * @param the cache key type + * @param the cache value type + * @return a new Caffeine cache service instance + */ + public static CacheServiceInterface, V> createCaffeineCache() { + return new CaffeineCacheService<>(); + } + + /** + * Creates a Caffeine asynchronous cache service with custom configuration. + * + * @param asyncCache the Caffeine async cache + * @param the cache key type + * @param the cache value type + * @return a new async Caffeine cache service instance + */ + public static CacheServiceInterface, V> createCaffeineAsyncCache(AsyncCache asyncCache) { + return new CaffeineAsyncCacheService<>(asyncCache); + } + + /** + * Creates a Caffeine asynchronous cache service with default configuration. + * + * @param the cache key type + * @param the cache value type + * @return a new async Caffeine cache service instance + */ + public static CacheServiceInterface, V> createCaffeineAsyncCache() { + return new CaffeineAsyncCacheService<>(); + } +} diff --git a/cache/src/main/java/me/qwqdev/library/cache/factory/CaffeineAsyncCacheServiceFactory.java b/cache/src/main/java/me/qwqdev/library/cache/factory/CaffeineAsyncCacheServiceFactory.java deleted file mode 100644 index 90d84bc..0000000 --- a/cache/src/main/java/me/qwqdev/library/cache/factory/CaffeineAsyncCacheServiceFactory.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.qwqdev.library.cache.factory; - -import com.github.benmanes.caffeine.cache.AsyncCache; -import lombok.experimental.UtilityClass; -import me.qwqdev.library.cache.service.caffeine.CaffeineAsyncCacheService; -import me.qwqdev.library.cache.service.caffeine.CaffeineAsyncCacheServiceInterface; - -/** - * Factory class for creating instances of {@link CaffeineAsyncCacheService}. - * - * @author qwq-dev - * @since 2024-12-20 20:39 - */ -@UtilityClass -public class CaffeineAsyncCacheServiceFactory { - /** - * Creates a new instance of {@link CaffeineAsyncCacheService} with a default {@link AsyncCache}. - * - * @param the type of keys maintained by the cache - * @param the type of mapped values - * @return a new instance of {@link CaffeineAsyncCacheService} - */ - public static CaffeineAsyncCacheServiceInterface create() { - return new CaffeineAsyncCacheService<>(); - } - - /** - * Creates a new instance of {@link CaffeineAsyncCacheService} with the specified {@link AsyncCache}. - * - * @param cache the {@link AsyncCache} to be used by the service - * @param the type of keys maintained by the cache - * @param the type of mapped values - * @return a new instance of {@link CaffeineAsyncCacheService} - */ - public static CaffeineAsyncCacheServiceInterface create(AsyncCache cache) { - return new CaffeineAsyncCacheService<>(cache); - } -} \ No newline at end of file diff --git a/cache/src/main/java/me/qwqdev/library/cache/factory/CaffeineCacheServiceFactory.java b/cache/src/main/java/me/qwqdev/library/cache/factory/CaffeineCacheServiceFactory.java deleted file mode 100644 index a934e65..0000000 --- a/cache/src/main/java/me/qwqdev/library/cache/factory/CaffeineCacheServiceFactory.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.qwqdev.library.cache.factory; - -import com.github.benmanes.caffeine.cache.Cache; -import lombok.experimental.UtilityClass; -import me.qwqdev.library.cache.service.caffeine.CaffeineCacheService; -import me.qwqdev.library.cache.service.caffeine.CaffeineCacheServiceInterface; - -/** - * Factory class for creating instances of {@link CaffeineCacheService}. - * - * @author qwq-dev - * @since 2024-12-20 20:39 - */ -@UtilityClass -public class CaffeineCacheServiceFactory { - /** - * Creates a new instance of {@link CaffeineCacheService} with a default {@link Cache}. - * - * @param the type of keys maintained by the cache - * @param the type of mapped values - * @return a new instance of {@link CaffeineCacheService} - */ - public static CaffeineCacheServiceInterface create() { - return new CaffeineCacheService<>(); - } - - /** - * Creates a new instance of {@link CaffeineCacheService} with the specified {@link Cache}. - * - * @param cache the {@link Cache} to be used by the service - * @param the type of keys maintained by the cache - * @param the type of mapped values - * @return a new instance of {@link CaffeineCacheService} - */ - public static CaffeineCacheServiceInterface create(Cache cache) { - return new CaffeineCacheService<>(cache); - } -} \ No newline at end of file diff --git a/cache/src/main/java/me/qwqdev/library/cache/factory/MemoryCacheServiceFactory.java b/cache/src/main/java/me/qwqdev/library/cache/factory/MemoryCacheServiceFactory.java deleted file mode 100644 index bb7e0c0..0000000 --- a/cache/src/main/java/me/qwqdev/library/cache/factory/MemoryCacheServiceFactory.java +++ /dev/null @@ -1,41 +0,0 @@ -package me.qwqdev.library.cache.factory; - -import lombok.experimental.UtilityClass; -import me.qwqdev.library.cache.model.CacheItem; -import me.qwqdev.library.cache.service.memory.MemoryCacheService; -import me.qwqdev.library.cache.service.memory.MemoryCacheServiceInterface; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Factory class for creating instances of {@link MemoryCacheService}. - * - * @author qwq-dev - * @since 2024-12-20 20:39 - */ -@UtilityClass -public class MemoryCacheServiceFactory { - /** - * Creates a new instance of {@link MemoryCacheService} with a default {@link ConcurrentHashMap}. - * - * @param the type of keys maintained by the cache - * @param the type of mapped values - * @return a new instance of {@link MemoryCacheService} - */ - public static MemoryCacheServiceInterface create() { - return new MemoryCacheService<>(); - } - - /** - * Creates a new instance of {@link MemoryCacheService} with the specified {@link Map}. - * - * @param cache the {@link Map} to be used by the service - * @param the type of keys maintained by the cache - * @param the type of mapped values - * @return a new instance of {@link MemoryCacheService} - */ - public static MemoryCacheServiceInterface create(Map> cache) { - return new MemoryCacheService<>(cache); - } -} \ No newline at end of file diff --git a/cache/src/main/java/me/qwqdev/library/cache/factory/RedisCacheServiceFactory.java b/cache/src/main/java/me/qwqdev/library/cache/factory/RedisCacheServiceFactory.java deleted file mode 100644 index c83385a..0000000 --- a/cache/src/main/java/me/qwqdev/library/cache/factory/RedisCacheServiceFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -package me.qwqdev.library.cache.factory; - -import lombok.experimental.UtilityClass; -import me.qwqdev.library.cache.service.redis.RedisCacheService; -import me.qwqdev.library.cache.service.redis.RedisCacheServiceInterface; -import org.redisson.config.Config; - -/** - * Factory class for creating instances of {@link RedisCacheService}. - * - * @author qwq-dev - * @since 2024-12-21 12:27 - */ -@UtilityClass -public class RedisCacheServiceFactory { - /** - * Creates a new instance of {@link RedisCacheService} with a default {@link RedisCacheService}. - * - * @param the type of the key - * @param the type of the value - * @return a new instance of {@link RedisCacheService} - */ - public static RedisCacheServiceInterface create(Config config) { - return new RedisCacheService<>(config); - } -} diff --git a/cache/src/main/java/me/qwqdev/library/cache/model/LockSettings.java b/cache/src/main/java/me/qwqdev/library/cache/model/LockSettings.java new file mode 100644 index 0000000..c42cc14 --- /dev/null +++ b/cache/src/main/java/me/qwqdev/library/cache/model/LockSettings.java @@ -0,0 +1,33 @@ +package me.qwqdev.library.cache.model; + +import lombok.Data; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +/** + * Encapsulation of the parameters passed in when calling the {@link Lock#tryLock()} method. + * + * @author qwq-dev + * @see Lock + * @see Lock#tryLock() + * @since 2024-12-21 17:56 + */ +@Data +public class LockSettings { + private final long waitTime; + private final long leaseTime; + private final TimeUnit timeUnit; + + /** + * Creates an instance of {@link LockSettings}. + * + * @param waitTime the maximum time to wait for the lock + * @param leaseTime the time to hold the lock after acquiring it + * @param timeUnit the time unit for the wait and lease times + * @return a {@link LockSettings} instance + */ + public static LockSettings of(long waitTime, long leaseTime, TimeUnit timeUnit) { + return new LockSettings(waitTime, leaseTime, timeUnit); + } +} diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/AbstractCacheService.java b/cache/src/main/java/me/qwqdev/library/cache/service/AbstractCacheService.java new file mode 100644 index 0000000..71be91d --- /dev/null +++ b/cache/src/main/java/me/qwqdev/library/cache/service/AbstractCacheService.java @@ -0,0 +1,98 @@ +package me.qwqdev.library.cache.service; + +import me.qwqdev.library.cache.model.LockSettings; + +import java.util.concurrent.locks.Lock; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Abstract base class for cache services that provides common caching functionality. + * + *

The service supports both locked and non-locked cache operations, + * with configurable behavior for cache misses and storage policies. + * + * @param the cache implementation type, representing the underlying cache technology + * @param the cache value type that will be stored and retrieved + * @author qwq-dev + * @see AbstractLockableCache + * @see LockSettings + * @see Lock + * @since 2024-12-21 18:53 + */ +public abstract class AbstractCacheService extends AbstractLockableCache { + /** + * Creates a new cache service instance with the specified cache implementation. + * + * @param cache the underlying cache implementation to be used + */ + protected AbstractCacheService(C cache) { + super(cache); + } + + /** + * Retrieves a value from cache or computes it if not present. + * This method provides a non-locked approach to cache access and updates. + * + * @param getCacheFunction the function to retrieve value from cache, typically implementing + * the specific cache technology's retrieval mechanism + * @param query the supplier to compute value if not found in cache, executed only on cache misses + * @param cacheBiConsumer the consumer to handle cache operations after successful query, + * can be used for additional cache maintenance tasks + * @param cacheAfterQuery flag indicating whether to store the computed value in cache after retrieval + * @return the cached value if present, otherwise the newly computed and optionally cached value + */ + public V get(Function getCacheFunction, Supplier query, + BiConsumer cacheBiConsumer, boolean cacheAfterQuery) { + return retrieveOrStoreInCache(getCacheFunction.apply(getCache()), + query, cacheBiConsumer, cacheAfterQuery); + } + + /** + * Retrieves a value from cache or computes it if not present, with lock protection. + * This method ensures thread-safe access to the cache through a locking mechanism. + * + * @param getLockFunction the function to obtain the lock for thread-safe operations + * @param getCacheFunction the function to retrieve value from cache, executed under lock protection + * @param query the supplier to compute value if not found in cache, executed only on cache misses + * @param cacheBiConsumer the consumer to handle cache operations after successful query + * @param cacheAfterQuery flag indicating whether to store the computed value in cache after retrieval + * @param lockSettings the settings controlling lock behavior including timeout and retry policies + * @return the cached value if present, otherwise the newly computed and optionally cached value + * @see LockSettings + */ + public V get(Function getLockFunction, Function getCacheFunction, + Supplier query, BiConsumer cacheBiConsumer, + boolean cacheAfterQuery, LockSettings lockSettings) { + return execute(getLockFunction, + cache -> retrieveOrStoreInCache(getCacheFunction.apply(cache), + query, cacheBiConsumer, cacheAfterQuery), + lockSettings); + } + + /** + * Core method implementing the cache retrieval and storage logic. + * + * @param value the value from cache, may be null indicating a cache miss + * @param query the supplier to compute value if not found in cache + * @param cacheBiConsumer the consumer to handle cache storage operations + * @param cacheAfterQuery flag indicating whether to store the computed value in cache + * @return the cached value if present, otherwise the newly computed and optionally cached value + */ + protected V retrieveOrStoreInCache(V value, Supplier query, + BiConsumer cacheBiConsumer, + boolean cacheAfterQuery) { + if (value != null) { + return value; + } + + value = query.get(); + + if (value != null && cacheAfterQuery) { + cacheBiConsumer.accept(getCache(), value); + } + + return value; + } +} diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/AbstractLockableCache.java b/cache/src/main/java/me/qwqdev/library/cache/service/AbstractLockableCache.java new file mode 100644 index 0000000..a886a81 --- /dev/null +++ b/cache/src/main/java/me/qwqdev/library/cache/service/AbstractLockableCache.java @@ -0,0 +1,69 @@ +package me.qwqdev.library.cache.service; + +import lombok.Data; +import me.qwqdev.library.cache.model.LockSettings; + +import java.util.concurrent.locks.Lock; +import java.util.function.Function; + +/** + * Abstract base class for lockable cache implementations. + * + *

This class provides a foundation for implementing thread-safe cache operations + * through locking mechanisms, with configurable timeout and retry policies. + * + * @param the cache implementation type + * @author qwq-dev + * @see LockableCache + * @see LockSettings + * @see Lock + * @since 2024-12-21 20:03 + */ +@Data +public abstract class AbstractLockableCache implements LockableCache { + private final C cache; + + /** + * Creates a new lockable cache instance with the specified cache implementation. + * + * @param cache the underlying cache implementation to be used + */ + protected AbstractLockableCache(C cache) { + this.cache = cache; + } + + /** + * Executes a function with lock protection. + * This method handles the acquisition and release of locks, ensuring thread-safe operations. + * + * @param getLockFunction the function to obtain the lock for thread-safe operations + * @param function the function to execute under lock protection + * @param lockSettings the settings controlling lock behavior including timeout and retry policies + * @param the return type of the executed function + * @return the result of the executed function + * @throws RuntimeException if lock acquisition fails or the thread is interrupted + * @see Lock + * @see LockSettings + */ + @Override + public T execute(Function getLockFunction, + Function function, + LockSettings lockSettings) { + Lock lock = getLockFunction.apply(cache); + String simpleName = lock.getClass().getSimpleName(); + + try { + if (lock.tryLock(lockSettings.getWaitTime(), lockSettings.getTimeUnit())) { + try { + return function.apply(cache); + } finally { + lock.unlock(); + } + } + throw new RuntimeException("Could not acquire lock: " + simpleName); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Thread interrupted while trying to acquire lock: " + simpleName, exception); + } + } +} diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/CacheServiceInterface.java b/cache/src/main/java/me/qwqdev/library/cache/service/CacheServiceInterface.java new file mode 100644 index 0000000..948a323 --- /dev/null +++ b/cache/src/main/java/me/qwqdev/library/cache/service/CacheServiceInterface.java @@ -0,0 +1,65 @@ +package me.qwqdev.library.cache.service; + +import me.qwqdev.library.cache.model.LockSettings; + +import java.util.concurrent.locks.Lock; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Interface defining core cache service operations. + * + *

This interface provides methods for accessing and manipulating cache, supporting + * both locked and non-locked cache operations. + * + *

It offers a generic structure that can be implemented by various cache services, + * including both in-memory and distributed caches, and supports both the standard + * cache access and computation as well as lock-protected access. + * + * @param the type of the cache implementation + * @param the type of the cache value + * @author qwq-dev + * @see LockableCache + * @see LockSettings + * @since 2024-12-21 18:54 + */ +public interface CacheServiceInterface extends LockableCache { + + /** + * Retrieves a value from the cache or computes it if not found. + * + *

This method first attempts to retrieve the value from the cache using the + * {@code getCacheFunction}. If the value is not found, it will be computed using + * the {@code query} supplier, and then optionally stored in the cache using + * the {@code cacheBiConsumer}. + * + * @param getCacheFunction function to retrieve the cached value from the cache + * @param query the supplier to compute the value if not found in the cache + * @param cacheBiConsumer the consumer to store the computed value in the cache + * @param cacheAfterQuery flag indicating whether the computed value should be cached + * @return the cached value if present, or the newly computed value + */ + V get(Function getCacheFunction, Supplier query, + BiConsumer cacheBiConsumer, boolean cacheAfterQuery); + + /** + * Retrieves a value from the cache or computes it if not found, with lock protection. + * + *

This method is similar to {@link #get(Function, Supplier, BiConsumer, boolean)}, + * but it includes lock protection to ensure that only one thread can compute and store + * the value at a time. The lock is obtained using the {@code getLockFunction}, and the + * lock settings are provided through {@code lockSettings}. + * + * @param getLockFunction function to obtain the lock for cache operations + * @param getCacheFunction function to retrieve the cached value from the cache + * @param query the supplier to compute the value if not found in the cache + * @param cacheBiConsumer the consumer to store the computed value in the cache + * @param cacheAfterQuery flag indicating whether the computed value should be cached + * @param lockSettings settings that define the lock behavior + * @return the cached value if present, or the newly computed value + */ + V get(Function getLockFunction, Function getCacheFunction, + Supplier query, BiConsumer cacheBiConsumer, + boolean cacheAfterQuery, LockSettings lockSettings); +} diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/LockableCache.java b/cache/src/main/java/me/qwqdev/library/cache/service/LockableCache.java new file mode 100644 index 0000000..7d80f74 --- /dev/null +++ b/cache/src/main/java/me/qwqdev/library/cache/service/LockableCache.java @@ -0,0 +1,37 @@ +package me.qwqdev.library.cache.service; + +import me.qwqdev.library.cache.model.LockSettings; + +import java.util.concurrent.locks.Lock; +import java.util.function.Function; + +/** + * Interface defining basic lockable cache operations. + * + *

Provides core functionality for cache access and lock-protected operations. + * + * @param the cache implementation type + * @author qwq-dev + * @see Lock + * @see LockSettings + * @since 2024-12-21 19:13 + */ +public interface LockableCache { + /** + * Gets the underlying cache implementation. + * + * @return the cache implementation + */ + C getCache(); + + /** + * Executes a function with lock protection. + * + * @param getLockFunction the function to obtain the lock + * @param function the function to execute under lock protection + * @param lockSettings the settings for lock behavior + * @param the return type of the function + * @return the result of the executed function + */ + T execute(Function getLockFunction, Function function, LockSettings lockSettings); +} diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineAsyncCacheService.java b/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineAsyncCacheService.java index cadba64..e9bb0a2 100644 --- a/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineAsyncCacheService.java +++ b/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineAsyncCacheService.java @@ -2,84 +2,36 @@ import com.github.benmanes.caffeine.cache.AsyncCache; import com.github.benmanes.caffeine.cache.Caffeine; -import lombok.Data; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.ForkJoinPool; -import java.util.function.Supplier; +import me.qwqdev.library.cache.service.AbstractCacheService; +import me.qwqdev.library.cache.service.CacheServiceInterface; /** - * Implementation of the {@link CaffeineAsyncCacheServiceInterface} using Caffeine's {@link AsyncCache}. + * Asynchronous Caffeine cache service implementation. + * + *

Provides caching functionality using Caffeine's asynchronous cache implementation. * - * @param the type of keys maintained by this cache - * @param the type of mapped values + * @param the cache key type + * @param the cache value type * @author qwq-dev - * @see CaffeineAsyncCacheServiceInterface * @see AsyncCache - * @since 2024-12-20 20:18 + * @see AbstractCacheService + * @see CacheServiceInterface + * @since 2024-12-21 20:03 */ -@Data -public class CaffeineAsyncCacheService implements CaffeineAsyncCacheServiceInterface { - private final AsyncCache cache; - +public class CaffeineAsyncCacheService extends AbstractCacheService, V> implements CacheServiceInterface, V> { /** - * Instantiates with a default {@link AsyncCache}. - * - * @see Caffeine#buildAsync() + * Creates a new async cache service with default Caffeine settings. */ public CaffeineAsyncCacheService() { - this.cache = Caffeine.newBuilder().buildAsync(); + super(Caffeine.newBuilder().buildAsync()); } /** - * Instantiates with a specified {@link AsyncCache}. + * Creates a new async cache service with a pre-configured cache. * - * @param cache the {@link AsyncCache} to be used by this service + * @param cache the pre-configured async cache instance */ public CaffeineAsyncCacheService(AsyncCache cache) { - this.cache = cache; - } - - /** - * {@inheritDoc} - * - * @param key {@inheritDoc} - * @param query {@inheritDoc} - * @param cacheAfterQuery {@inheritDoc} - * @return - */ - @Override - public CompletableFuture get(K key, Supplier query, boolean cacheAfterQuery) { - return get(key, query, cacheAfterQuery, ForkJoinPool.commonPool()); - } - - /** - * {@inheritDoc} - * - * @param key {@inheritDoc} - * @param query {@inheritDoc} - * @param cacheAfterQuery {@inheritDoc} - * @param executor {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public CompletableFuture get(K key, Supplier query, boolean cacheAfterQuery, Executor executor) { - CompletableFuture cachedValue = cache.getIfPresent(key); - return cachedValue != null ? cachedValue : fetchAndCache(key, query, cacheAfterQuery, executor); - } - - private CompletableFuture fetchAndCache(K key, Supplier query, boolean cacheAfterQuery, Executor executor) { - CompletableFuture future = CompletableFuture.supplyAsync(query, executor); - - if (cacheAfterQuery) { - future.thenAccept(value -> { - if (value != null) { - cache.put(key, CompletableFuture.completedFuture(value)); - } - }); - } - - return future; + super(cache); } } \ No newline at end of file diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineAsyncCacheServiceInterface.java b/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineAsyncCacheServiceInterface.java deleted file mode 100644 index e0ce9be..0000000 --- a/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineAsyncCacheServiceInterface.java +++ /dev/null @@ -1,48 +0,0 @@ -package me.qwqdev.library.cache.service.caffeine; - -import com.github.benmanes.caffeine.cache.AsyncCache; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.function.Supplier; - -/** - * Interface for an asynchronous cache service using Caffeine. - * - * @param the type of keys maintained by this cache - * @param the type of mapped values - * @author qwq-dev - * @since 2024-12-20 20:18 - */ -public interface CaffeineAsyncCacheServiceInterface { - /** - * Get the {@link AsyncCache} that implements the cache - * - * @return the {@link AsyncCache} - */ - AsyncCache getCache(); - - /** - * Retrieves a value from the cache asynchronously. If the value is not present in the cache, - * it will be fetched using the provided query supplier and optionally cached. - * - * @param key the key whose associated value is to be returned - * @param query a supplier to fetch the value if it is not present in the cache - * @param cacheAfterQuery if true, the fetched value will be cached - * @return a CompletableFuture containing the value associated with the specified key - */ - CompletableFuture get(K key, Supplier query, boolean cacheAfterQuery); - - /** - * Retrieves a value from the cache asynchronously using the specified executor. If the value - * is not present in the cache, it will be fetched using the provided query supplier and - * optionally cached. - * - * @param key the key whose associated value is to be returned - * @param query a supplier to fetch the value if it is not present in the cache - * @param cacheAfterQuery if true, the fetched value will be cached - * @param executor the executor to use for asynchronous execution - * @return a CompletableFuture containing the value associated with the specified key - */ - CompletableFuture get(K key, Supplier query, boolean cacheAfterQuery, Executor executor); -} \ No newline at end of file diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineCacheService.java b/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineCacheService.java index 337744b..68c1cfb 100644 --- a/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineCacheService.java +++ b/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineCacheService.java @@ -2,68 +2,36 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import lombok.Data; - -import java.util.function.Supplier; +import me.qwqdev.library.cache.service.AbstractCacheService; +import me.qwqdev.library.cache.service.CacheServiceInterface; /** - * Implementation of the {@link CaffeineCacheServiceInterface} using Caffeine's {@link Cache}. + * Synchronous Caffeine cache service implementation. + * + *

Provides caching functionality using Caffeine's synchronous cache implementation. * - * @param the type of keys maintained by this cache - * @param the type of mapped values + * @param the cache key type + * @param the cache value type * @author qwq-dev - * @see CaffeineCacheServiceInterface * @see Cache - * @since 2024-12-20 20:18 + * @see AbstractCacheService + * @see CacheServiceInterface + * @since 2024-12-21 20:03 */ -@Data -public class CaffeineCacheService implements CaffeineCacheServiceInterface { - private final Cache cache; - +public class CaffeineCacheService extends AbstractCacheService, V> implements CacheServiceInterface, V> { /** - * Instantiates with a default {@link Cache}. - * - * @see Caffeine#build() + * Creates a new cache service with default Caffeine settings. */ public CaffeineCacheService() { - this.cache = Caffeine.newBuilder().build(); + super(Caffeine.newBuilder().build()); } /** - * Instantiates with a specified {@link Cache}. + * Creates a new cache service with a pre-configured cache. * - * @param cache the {@link Cache} to be used by this service + * @param cache the pre-configured cache instance */ public CaffeineCacheService(Cache cache) { - this.cache = cache; - } - - /** - * {@inheritDoc} - * - * @param key {@inheritDoc} - * @param query {@inheritDoc} - * @param cacheAfterQuery {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public V get(K key, Supplier query, boolean cacheAfterQuery) { - V value = cache.getIfPresent(key); - - if (value != null) { - return value; - } - - value = query.get(); - - if (value == null) { - return null; - } - - if (cacheAfterQuery) { - cache.put(key, value); - } - - return value; + super(cache); } } \ No newline at end of file diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineCacheServiceInterface.java b/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineCacheServiceInterface.java deleted file mode 100644 index e25af58..0000000 --- a/cache/src/main/java/me/qwqdev/library/cache/service/caffeine/CaffeineCacheServiceInterface.java +++ /dev/null @@ -1,33 +0,0 @@ -package me.qwqdev.library.cache.service.caffeine; - -import com.github.benmanes.caffeine.cache.Cache; - -import java.util.function.Supplier; - -/** - * Interface for a cache service using Caffeine. - * - * @param the type of keys maintained by this cache - * @param the type of mapped values - * @author qwq-dev - * @since 2024-12-20 20:18 - */ -public interface CaffeineCacheServiceInterface { - /** - * Get the {@link Cache} that implements the cache - * - * @return the {@link Cache} - */ - Cache getCache(); - - /** - * Retrieves a value from the cache. If the value is not present in the cache, - * it will be fetched using the provided query supplier and optionally cached. - * - * @param key the key whose associated value is to be returned - * @param query a supplier to fetch the value if it is not present in the cache - * @param cacheAfterQuery if true, the fetched value will be cached - * @return the value associated with the specified key - */ - V get(K key, Supplier query, boolean cacheAfterQuery); -} \ No newline at end of file diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/memory/MemoryCacheService.java b/cache/src/main/java/me/qwqdev/library/cache/service/memory/MemoryCacheService.java deleted file mode 100644 index 44a1ed6..0000000 --- a/cache/src/main/java/me/qwqdev/library/cache/service/memory/MemoryCacheService.java +++ /dev/null @@ -1,74 +0,0 @@ -package me.qwqdev.library.cache.service.memory; - -import lombok.Data; -import me.qwqdev.library.cache.model.CacheItem; -import me.qwqdev.library.cache.model.ExpirationSettings; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; - -/** - * Implementation of the {@link MemoryCacheServiceInterface} using an in-memory {@link Map}. - * - * @param the type of keys maintained by this cache - * @param the type of mapped values - * @author qwq-dev - * @see MemoryCacheServiceInterface - * @see CacheItem - * @see ExpirationSettings - * @since 2024-12-20 20:39 - */ -@Data -public class MemoryCacheService implements MemoryCacheServiceInterface { - private final Map> cache; - - /** - * Instantiates with a default {@link ConcurrentHashMap}. - * - * @see ConcurrentHashMap - */ - public MemoryCacheService() { - this.cache = new ConcurrentHashMap<>(); - } - - /** - * Instantiates with a specified {@link Map}. - * - * @param cache the {@link Map} to be used by this service - */ - public MemoryCacheService(Map> cache) { - this.cache = cache; - } - - /** - * {@inheritDoc} - * - * @param key {@inheritDoc} - * @param query {@inheritDoc} - * @param cacheAfterQuery {@inheritDoc} - * @param expirationSettings {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public V get(K key, Supplier query, boolean cacheAfterQuery, ExpirationSettings expirationSettings) { - CacheItem cacheItem = cache.get(key); - - if (cacheItem == null) { - V value = query.get(); - - if (cacheAfterQuery) { - cache.put(key, new CacheItem<>(value, expirationSettings)); - } - - return value; - } - - if (cacheItem.isExpired()) { - cache.remove(key); - return null; - } - - return cacheItem.getValue(); - } -} \ No newline at end of file diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/memory/MemoryCacheServiceInterface.java b/cache/src/main/java/me/qwqdev/library/cache/service/memory/MemoryCacheServiceInterface.java deleted file mode 100644 index 4f1d4f7..0000000 --- a/cache/src/main/java/me/qwqdev/library/cache/service/memory/MemoryCacheServiceInterface.java +++ /dev/null @@ -1,50 +0,0 @@ -package me.qwqdev.library.cache.service.memory; - -import me.qwqdev.library.cache.model.CacheItem; -import me.qwqdev.library.cache.model.ExpirationSettings; - -import java.util.Map; -import java.util.function.Supplier; - -/** - * Interface for a memory cache service. - * - * @param the type of keys maintained by this cache - * @param the type of mapped values - * @author qwq-dev - * @since 2024-12-20 20:39 - */ -public interface MemoryCacheServiceInterface { - /** - * Get the Map that implements the cache - * - * @return the cache map - */ - Map> getCache(); - - /** - * Retrieves a value from the cache. If the value is not present in the cache, - * it will be fetched using the provided query supplier and optionally cached - * with the specified expiration settings. - * - *

It should be noted that the Memory cache based on the expiration time - * of the element will only be checked when the get method is called. - * - *

If we need a better expiration strategy, please consider - * using {@link me.qwqdev.library.cache.service.caffeine.CaffeineCacheService} or - * {@link me.qwqdev.library.cache.service.caffeine.CaffeineAsyncCacheService} and - * create them using the factory class. - * - * @param key the key whose associated value is to be returned - * @param query a supplier to fetch the value if it is not present in the cache - * @param cacheAfterQuery if true, the fetched value will be cached - * @param expirationSettings the settings for cache item expiration - * @return the value associated with the specified key - * @see ExpirationSettings - * @see me.qwqdev.library.cache.service.caffeine.CaffeineCacheService - * @see me.qwqdev.library.cache.service.caffeine.CaffeineAsyncCacheService - * @see me.qwqdev.library.cache.factory.CaffeineCacheServiceFactory - * @see me.qwqdev.library.cache.factory.CaffeineAsyncCacheServiceFactory - */ - V get(K key, Supplier query, boolean cacheAfterQuery, ExpirationSettings expirationSettings); -} \ No newline at end of file diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/redis/RedisCacheService.java b/cache/src/main/java/me/qwqdev/library/cache/service/redis/RedisCacheService.java index a86d47f..c4dad6c 100644 --- a/cache/src/main/java/me/qwqdev/library/cache/service/redis/RedisCacheService.java +++ b/cache/src/main/java/me/qwqdev/library/cache/service/redis/RedisCacheService.java @@ -1,129 +1,153 @@ package me.qwqdev.library.cache.service.redis; import io.fairyproject.log.Log; -import lombok.Data; -import me.qwqdev.library.cache.model.ExpirationSettings; +import me.qwqdev.library.cache.model.LockSettings; +import me.qwqdev.library.cache.service.AbstractLockableCache; import org.redisson.Redisson; -import org.redisson.api.RBucket; -import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.redisson.config.Config; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; /** - * Redis Cache Service that supports flexible functional programming to operate the cache. + * Redis implementation of cache service using Redisson client. + * + *

Provides Redis-specific caching functionality with support for + * expiration and lock-protected operations. * - * @param the type of the key - * @param the type of the value * @author qwq-dev - * @since 2024-12-21 12:00 + * @see RedissonClient + * @see RedisCacheServiceInterface + * @see AbstractLockableCache + * @since 2024-12-21 20:03 */ -@Data -public class RedisCacheService implements RedisCacheServiceInterface { - private final RedissonClient redissonClient; - +public class RedisCacheService extends AbstractLockableCache implements RedisCacheServiceInterface { /** - * Initializes the Redis Cache Service with the provided Redisson configuration. + * Constructs a new Redis cache service with the specified Redisson client. * - * @param config the Redisson configuration + * @param config {@link Config} + * @see Config */ public RedisCacheService(Config config) { - this.redissonClient = Redisson.create(config); + super(Redisson.create(config)); } /** * {@inheritDoc} * - * @param key {@inheritDoc} - * @param function {@inheritDoc} - * @param query {@inheritDoc} - * @param cacheAfterQuery {@inheritDoc} - * @param expirationSettings {@inheritDoc} - * @return + * @param getCacheFunction {@inheritDoc} + * @param query {@inheritDoc} + * @param cacheBiConsumer {@inheritDoc} + * @param cacheAfterQuery {@inheritDoc} + * @param {@inheritDoc} + * @return {@inheritDoc} */ @Override - public V get(K key, Function> function, Supplier query, boolean cacheAfterQuery, ExpirationSettings expirationSettings) { - return executeCacheOperation(function, key, query, cacheAfterQuery, expirationSettings); + @SuppressWarnings("unchecked") + public R getWithType(Function getCacheFunction, Supplier query, + BiConsumer cacheBiConsumer, boolean cacheAfterQuery) { + return (R) retrieveOrStoreInCache(getCacheFunction.apply(getCache()), query, cacheBiConsumer, cacheAfterQuery); } /** * {@inheritDoc} * - * @param key {@inheritDoc} - * @param function {@inheritDoc} - * @param query {@inheritDoc} - * @param cacheAfterQuery {@inheritDoc} - * @param expirationSettings {@inheritDoc} + * @param getLockFunction {@inheritDoc} + * @param getCacheFunction {@inheritDoc} + * @param query {@inheritDoc} + * @param cacheBiConsumer {@inheritDoc} + * @param cacheAfterQuery {@inheritDoc} + * @param lockSettings {@inheritDoc} + * @param {@inheritDoc} * @return {@inheritDoc} */ @Override - public V getWithLock(K key, Function> function, Supplier query, boolean cacheAfterQuery, ExpirationSettings expirationSettings) { - return executeCacheOperationWithLock(function, key, query, cacheAfterQuery, expirationSettings); - } - - private V executeCacheOperation(Function> function, K key, Supplier query, boolean cacheAfterQuery, ExpirationSettings expirationSettings) { - RBucket rBucket = function.apply(redissonClient); - return retrieveOrStoreInCache(rBucket, key, query, cacheAfterQuery, expirationSettings); + @SuppressWarnings("unchecked") + public R getWithType(Function getLockFunction, Function getCacheFunction, + Supplier query, BiConsumer cacheBiConsumer, boolean cacheAfterQuery, + LockSettings lockSettings) { + return (R) execute(getLockFunction, + cache -> retrieveOrStoreInCache(getCacheFunction.apply(cache), + query, cacheBiConsumer, cacheAfterQuery), lockSettings + ); } - private V executeCacheOperationWithLock(Function> function, K key, Supplier query, boolean cacheAfterQuery, ExpirationSettings expirationSettings) { - RLock lock = redissonClient.getLock("lock:" + key); - try { - if (lock.tryLock(30, 10, TimeUnit.SECONDS)) { - return executeCacheOperation(function, key, query, cacheAfterQuery, expirationSettings); - } - String msg = "Could not acquire lock for key: " + key; - Log.error(msg); - throw new RuntimeException(msg); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - String msg = "Thread interrupted while trying to acquire lock for key: " + key; - Log.error(msg, exception); - throw new RuntimeException(msg, exception); - } finally { - try { - lock.unlock(); - } catch (Exception exception) { - Log.error("Error unlocking lock for key: " + key, exception); - } - } + /** + * {@inheritDoc} + * + * @param getCacheFunction {@inheritDoc} + * @param query {@inheritDoc} + * @param cacheBiConsumer {@inheritDoc} + * @param cacheAfterQuery {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Object get(Function getCacheFunction, Supplier query, + BiConsumer cacheBiConsumer, boolean cacheAfterQuery) { + return retrieveOrStoreInCache(getCacheFunction.apply(getCache()), query, cacheBiConsumer, cacheAfterQuery); } - private V retrieveOrStoreInCache(RBucket rBucket, K key, Supplier query, boolean cacheAfterQuery, ExpirationSettings expirationSettings) { - V value = rBucket.get(); + /** + * {@inheritDoc} + * + * @param getLockFunction {@inheritDoc} + * @param getCacheFunction {@inheritDoc} + * @param query {@inheritDoc} + * @param cacheBiConsumer {@inheritDoc} + * @param cacheAfterQuery {@inheritDoc} + * @param lockSettings {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Object get(Function getLockFunction, + Function getCacheFunction, Supplier query, + BiConsumer cacheBiConsumer, boolean cacheAfterQuery, + LockSettings lockSettings) { + return execute(getLockFunction, + cache -> retrieveOrStoreInCache(getCacheFunction.apply(cache), + query, cacheBiConsumer, cacheAfterQuery), lockSettings + ); + } + /** + * Core method implementing the cache retrieval and storage logic with expiration support. + * + * @param value the value from cache, may be null indicating a cache miss + * @param query the supplier to compute value if not found in cache + * @param cacheBiConsumer the consumer to handle cache storage operations + * @param cacheAfterQuery flag indicating whether to store the computed value in cache + * @return the cached value if present, otherwise the newly computed value + */ + protected Object retrieveOrStoreInCache(Object value, Supplier query, + BiConsumer cacheBiConsumer, + boolean cacheAfterQuery) { if (value != null) { return value; } value = query.get(); - if (cacheAfterQuery) { - storeInCache(rBucket, value, expirationSettings); + if (value != null && cacheAfterQuery && cacheBiConsumer != null) { + cacheBiConsumer.accept(getCache(), value); } return value; } - private void storeInCache(RBucket rBucket, V value, ExpirationSettings expirationSettings) { - if (expirationSettings != null) { - rBucket.set(value, expirationSettings.toDuration()); - } else { - rBucket.set(value); - } - } - /** * {@inheritDoc} */ + @Override public void shutdown() { - if (redissonClient != null) { + RedissonClient cache = getCache(); + + if (cache != null) { try { - redissonClient.shutdown(); + cache.shutdown(); } catch (Exception exception) { Log.error("Error shutting down Redisson client", exception); } diff --git a/cache/src/main/java/me/qwqdev/library/cache/service/redis/RedisCacheServiceInterface.java b/cache/src/main/java/me/qwqdev/library/cache/service/redis/RedisCacheServiceInterface.java index b5d79ac..855cc7e 100644 --- a/cache/src/main/java/me/qwqdev/library/cache/service/redis/RedisCacheServiceInterface.java +++ b/cache/src/main/java/me/qwqdev/library/cache/service/redis/RedisCacheServiceInterface.java @@ -1,58 +1,71 @@ package me.qwqdev.library.cache.service.redis; -import me.qwqdev.library.cache.model.ExpirationSettings; -import org.redisson.api.RBucket; +import me.qwqdev.library.cache.model.LockSettings; +import me.qwqdev.library.cache.service.CacheServiceInterface; import org.redisson.api.RedissonClient; +import java.util.concurrent.locks.Lock; +import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; /** - * Interface for redis cache service. + * Interface for the Redis cache service, providing methods to interact with the cache + * using Redisson client, including support for lockable cache operations. + * + *

This interface extends {@link CacheServiceInterface} and provides Redis-specific + * cache operations. It includes methods for retrieving or storing values in the cache, + * with support for both locked and non-locked operations. * - * @param the type of the key - * @param the type of the value * @author qwq-dev - * @since 2024-12-21 12:16 + * @see CacheServiceInterface + * @see RedissonClient + * @since 2024-12-21 20:03 */ -public interface RedisCacheServiceInterface { - /** - * Get the {@link RedissonClient} that implements the cache - * - * @return the {@link RedissonClient} - */ - RedissonClient getRedissonClient(); +public interface RedisCacheServiceInterface extends CacheServiceInterface { /** - * Retrieves a value from the Redis cache. If the value is not found, it fetches the value - * using the provided query function and optionally stores it in the cache. + * Retrieves a value from the Redis cache or computes it if not found, without using a lock. * - * @param key the cache key - * @param function function to access the Redis cache (e.g., using {@link RedissonClient#getBucket(String)}) - * @param query the query function to fetch the value when not present in the cache - * @param cacheAfterQuery whether to cache the value after querying - * @param expirationSettings the expiration settings for the cache item - * @return the value from the cache, or the queried value if not found + *

This method will first attempt to retrieve the value from the cache using the + * {@code getCacheFunction}. If the value is not present, it will be computed by + * the {@code query} supplier, and the result can be stored in the cache using + * the {@code cacheBiConsumer}. + * + * @param getCacheFunction function to retrieve the cached value from Redis + * @param query the supplier to compute the value if not found in cache + * @param cacheBiConsumer the consumer to store the value in the cache after computation + * @param cacheAfterQuery flag indicating whether the computed value should be cached + * @return the cached or computed value */ - V get(K key, Function> function, Supplier query, boolean cacheAfterQuery, ExpirationSettings expirationSettings); + R getWithType(Function getCacheFunction, Supplier query, + BiConsumer cacheBiConsumer, boolean cacheAfterQuery); /** - * Retrieves a value from the Redis cache with a distributed lock to ensure exclusive access - * in high-concurrency environments. If the value is not found, it fetches the value using - * the provided query function and optionally stores it in the cache. + * Retrieves a value from the Redis cache or computes it if not found, with lock protection. + * + *

This method is similar to {@link #getWithType(Function, Supplier, BiConsumer, boolean)}, + * but it involves a locking mechanism to ensure that only one thread can compute and store + * the value at a time. The lock is obtained using the {@code getLockFunction}, and the lock + * settings are specified through {@code lockSettings}. * - * @param key the cache key - * @param function function to access the Redis cache (e.g., using {@link RedissonClient#getBucket(String)}) - * @param query the query function to fetch the value when not present in the cache - * @param cacheAfterQuery whether to cache the value after querying - * @param expirationSettings the expiration settings for the cache item - * @return the value from the cache, or the queried value if not found - * @throws RuntimeException if the lock cannot be acquired + * @param getLockFunction function to obtain the lock for cache operations + * @param getCacheFunction function to retrieve the cached value from Redis + * @param query the supplier to compute the value if not found in cache + * @param cacheBiConsumer the consumer to store the value in the cache after computation + * @param cacheAfterQuery flag indicating whether the computed value should be cached + * @param lockSettings the settings that define the lock behavior + * @return the cached or computed value */ - V getWithLock(K key, Function> function, Supplier query, boolean cacheAfterQuery, ExpirationSettings expirationSettings); + R getWithType(Function getLockFunction, Function getCacheFunction, + Supplier query, BiConsumer cacheBiConsumer, boolean cacheAfterQuery, + LockSettings lockSettings); /** - * Shuts down the Redisson client and releases resources. + * Shuts down the Redis client connection gracefully. + * + *

This method is responsible for closing the connection to the Redis server + * and releasing any resources associated with the Redisson client. */ void shutdown(); }