Infinispan is an open-source distributed in-memory data grid and caching platform written in Java. It provides advanced distributed caching, data grid capabilities, and can function as a NoSQL datastore.
Basic Infinispan Setup
1. Dependencies Configuration
Maven:
<properties> <infinispan.version>14.0.18.Final</infinispan.version> </properties> <dependencies> <!-- Infinispan Core --> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-core</artifactId> <version>${infinispan.version}</version> </dependency> <!-- Infinispan Spring Boot Starter --> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-spring-boot-starter</artifactId> <version>${infinispan.version}</version> </dependency> <!-- Infinispan Query (for distributed queries) --> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-query</artifactId> <version>${infinispan.version}</version> </dependency> <!-- Infinispan Remote Client (Hot Rod) --> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-client-hotrod</artifactId> <version>${infinispan.version}</version> </dependency> </dependencies> Gradle:
dependencies { implementation 'org.infinispan:infinispan-core:14.0.18.Final' implementation 'org.infinispan:infinispan-spring-boot-starter:14.0.18.Final' implementation 'org.infinispan:infinispan-query:14.0.18.Final' implementation 'org.infinispan:infinispan-client-hotrod:14.0.18.Final' } 2. Basic Cache Configuration
Programmatic Configuration:
import org.infinispan.Cache; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.Configuration; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.global.GlobalConfiguration; import org.infinispan.configuration.global.GlobalConfigurationBuilder; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.manager.EmbeddedCacheManager; public class BasicInfinispanExample { public static void main(String[] args) { // Create embedded cache manager GlobalConfiguration globalConfig = new GlobalConfigurationBuilder() .clusteredDefault() .transport().defaultTransport() .globalJmxStatistics().enable() .build(); Configuration cacheConfig = new ConfigurationBuilder() .clustering() .cacheMode(CacheMode.DIST_SYNC) // Distributed synchronous cache .memory() .size(1000) // Maximum number of entries .expiration() .lifespan(60000) // 60 seconds .maxIdle(30000) // 30 seconds idle .build(); EmbeddedCacheManager cacheManager = new DefaultCacheManager(globalConfig, cacheConfig); // Get or create cache Cache<String, String> cache = cacheManager.getCache("basicCache"); // Basic operations cache.put("key1", "value1"); String value = cache.get("key1"); System.out.println("Retrieved: " + value); // Bulk operations Map<String, String> data = new HashMap<>(); data.put("key2", "value2"); data.put("key3", "value3"); cache.putAll(data); // Close cache manager cacheManager.stop(); } } XML Configuration (infinispan.xml):
<?xml version="1.0" encoding="UTF-8"?> <infinispan xmlns="urn:infinispan:config:14.0"> <cache-container name="MyCacheManager" default-cache="default-cache"> <transport cluster="my-cluster" stack="tcp"/> <!-- Local cache configuration --> <local-cache name="local-cache"> <memory max-count="10000" when-full="REMOVE"/> <expiration lifespan="300000" max-idle="60000"/> </local-cache> <!-- Distributed cache configuration --> <distributed-cache name="distributed-cache" owners="2"> <memory max-count="50000" when-full="REMOVE"/> <expiration lifespan="600000" max-idle="120000"/> <locking isolation="REPEATABLE_READ"/> <transaction mode="NON_XA"/> </distributed-cache> <!-- Replicated cache configuration --> <replicated-cache name="replicated-cache"> <memory max-count="20000" when-full="REMOVE"/> <expiration lifespan="300000"/> </replicated-cache> <!-- Cache with persistence --> <distributed-cache name="persistent-cache" owners="2"> <memory max-count="100000" when-full="REMOVE"/> <persistence passivation="false"> <file-store relative-to="infinispan.server.data.path" path="persistent-data" max-entries="50000"/> </persistence> </distributed-cache> </cache-container> </infinispan>
Comprehensive Cache Service Implementation
3. Generic Infinispan Cache Service
import org.infinispan.Cache; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.commons.api.CacheContainerAdmin; import java.time.Duration; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; public class InfinispanCacheService<K, V> { private final Cache<K, V> cache; private final String cacheName; public InfinispanCacheService(EmbeddedCacheManager cacheManager, String cacheName, Class<K> keyType, Class<V> valueType) { this.cacheName = cacheName; // Get or create cache with admin privileges this.cache = cacheManager.administration() .withFlags(CacheContainerAdmin.AdminFlag.VOLATILE) .getOrCreateCache(cacheName, cacheManager.getDefaultCacheConfiguration()); } // Basic synchronous operations public void put(K key, V value) { cache.put(key, value); } public void put(K key, V value, Duration ttl) { cache.put(key, value, ttl.toMillis(), TimeUnit.MILLISECONDS); } public Optional<V> get(K key) { V value = cache.get(key); return Optional.ofNullable(value); } public boolean contains(K key) { return cache.containsKey(key); } public void remove(K key) { cache.remove(key); } public void clear() { cache.clear(); } // Advanced operations public V computeIfAbsent(K key, Function<K, V> mappingFunction) { return cache.computeIfAbsent(key, mappingFunction); } public V computeIfAbsent(K key, Function<K, V> mappingFunction, Duration ttl) { V value = cache.computeIfAbsent(key, mappingFunction); if (value != null) { cache.replace(key, value, ttl.toMillis(), TimeUnit.MILLISECONDS); } return value; } public V getOrDefault(K key, V defaultValue) { return cache.getOrDefault(key, defaultValue); } public V putIfAbsent(K key, V value) { return cache.putIfAbsent(key, value); } public boolean replace(K key, V oldValue, V newValue) { return cache.replace(key, oldValue, newValue); } // Bulk operations public void putAll(Map<K, V> entries) { cache.putAll(entries); } public void putAll(Map<K, V> entries, Duration ttl) { Map<K, V> entriesWithMetadata = new HashMap<>(); entries.forEach((k, v) -> entriesWithMetadata.put(k, v)); // Note: Bulk put with TTL requires advanced configuration cache.putAll(entriesWithMetadata); } public Map<K, V> getAll(Set<K> keys) { return cache.getAll(keys); } // Async operations public CompletableFuture<V> getAsync(K key) { return cache.getAsync(key); } public CompletableFuture<Void> putAsync(K key, V value) { return cache.putAsync(key, value); } public CompletableFuture<Void> putAsync(K key, V value, Duration ttl) { return cache.putAsync(key, value, ttl.toMillis(), TimeUnit.MILLISECONDS); } // Atomic operations public boolean replaceAtomic(K key, V expectedValue, V newValue) { return cache.replace(key, expectedValue, newValue); } public V merge(K key, V value, Function<V, V> remappingFunction) { return cache.merge(key, value, remappingFunction); } // Statistics public long getSize() { return cache.size(); } public Map<String, String> getStats() { Map<String, String> stats = new HashMap<>(); stats.put("cacheName", cacheName); stats.put("size", String.valueOf(cache.size())); stats.put("memoryUsage", "N/A"); // Requires JMX configuration return stats; } // Cache management public void evict(K key) { cache.evict(key); } public String getCacheInfo() { return String.format("Cache: %s, Size: %d, Mode: %s", cacheName, getSize(), cache.getCacheConfiguration().clustering().cacheMode()); } } 4. Cache Manager Factory
import org.infinispan.manager.DefaultCacheManager; import org.infinispan.manager.EmbeddedCacheManager; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class InfinispanCacheManagerFactory { private static InfinispanCacheManagerFactory instance; private final ConcurrentMap<String, EmbeddedCacheManager> cacheManagers; private final EmbeddedCacheManager defaultCacheManager; private InfinispanCacheManagerFactory() { this.cacheManagers = new ConcurrentHashMap<>(); this.defaultCacheManager = createDefaultCacheManager(); } public static synchronized InfinispanCacheManagerFactory getInstance() { if (instance == null) { instance = new InfinispanCacheManagerFactory(); } return instance; } public EmbeddedCacheManager getDefaultCacheManager() { return defaultCacheManager; } public EmbeddedCacheManager getCacheManager(String configFile) { return cacheManagers.computeIfAbsent(configFile, this::createCacheManagerFromConfig); } public <K, V> InfinispanCacheService<K, V> createCacheService(String cacheName, Class<K> keyType, Class<V> valueType) { return new InfinispanCacheService<>(defaultCacheManager, cacheName, keyType, valueType); } public <K, V> InfinispanCacheService<K, V> createCacheService(String configFile, String cacheName, Class<K> keyType, Class<V> valueType) { EmbeddedCacheManager cacheManager = getCacheManager(configFile); return new InfinispanCacheService<>(cacheManager, cacheName, keyType, valueType); } private EmbeddedCacheManager createDefaultCacheManager() { try { return new DefaultCacheManager("infinispan.xml"); } catch (IOException e) { // Fallback to programmatic configuration return new DefaultCacheManager(); } } private EmbeddedCacheManager createCacheManagerFromConfig(String configFile) { try { return new DefaultCacheManager(configFile); } catch (IOException e) { throw new RuntimeException("Failed to create cache manager from config: " + configFile, e); } } public void shutdown() { cacheManagers.values().forEach(EmbeddedCacheManager::stop); defaultCacheManager.stop(); cacheManagers.clear(); } public void shutdown(String configFile) { EmbeddedCacheManager cacheManager = cacheManagers.remove(configFile); if (cacheManager != null) { cacheManager.stop(); } } } Domain-Specific Cache Services
5. User Session Cache Service
import java.time.Duration; import java.util.List; import java.util.Optional; public class UserSessionCacheService { private final InfinispanCacheService<String, UserSession> sessionCache; private final InfinispanCacheService<Long, List<String>> userSessionsCache; private final InfinispanCacheService<String, UserProfile> profileCache; public UserSessionCacheService(EmbeddedCacheManager cacheManager) { this.sessionCache = new InfinispanCacheService<>( cacheManager, "userSessions", String.class, UserSession.class); this.userSessionsCache = new InfinispanCacheService<>( cacheManager, "userSessionsIndex", Long.class, (Class<List<String>>) (Class<?>) List.class); this.profileCache = new InfinispanCacheService<>( cacheManager, "userProfiles", String.class, UserProfile.class); } // Session operations public void createSession(UserSession session) { String sessionId = session.getSessionId(); sessionCache.put(sessionId, session, Duration.ofMinutes(30)); // Update user-sessions index Long userId = session.getUserId(); List<String> userSessions = userSessionsCache.get(userId) .orElse(new ArrayList<>()); userSessions.add(sessionId); userSessionsCache.put(userId, userSessions); } public Optional<UserSession> getSession(String sessionId) { return sessionCache.get(sessionId); } public void updateSession(UserSession session) { sessionCache.put(session.getSessionId(), session, Duration.ofMinutes(30)); } public void invalidateSession(String sessionId) { Optional<UserSession> session = sessionCache.get(sessionId); sessionCache.remove(sessionId); // Remove from index session.ifPresent(s -> { Long userId = s.getUserId(); userSessionsCache.get(userId).ifPresent(sessions -> { sessions.remove(sessionId); userSessionsCache.put(userId, sessions); }); }); } public void invalidateAllUserSessions(Long userId) { userSessionsCache.get(userId).ifPresent(sessions -> { sessions.forEach(sessionCache::remove); userSessionsCache.remove(userId); }); } public List<UserSession> getUserSessions(Long userId) { return userSessionsCache.get(userId) .orElse(Collections.emptyList()) .stream() .map(sessionCache::get) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); } // Profile operations public void cacheUserProfile(UserProfile profile) { profileCache.put(profile.getUsername(), profile, Duration.ofHours(1)); } public Optional<UserProfile> getUserProfile(String username) { return profileCache.get(username); } public UserProfile getOrLoadUserProfile(String username, Function<String, UserProfile> loader) { return profileCache.computeIfAbsent(username, loader, Duration.ofHours(1)); } public void evictUserProfile(String username) { profileCache.remove(username); } // Statistics public SessionStats getSessionStats() { long totalSessions = sessionCache.getSize(); long totalUsers = userSessionsCache.getSize(); return new SessionStats(totalSessions, totalUsers); } // Cleanup expired sessions public void cleanupExpiredSessions() { // Infinispan handles expiration automatically // This method can be used for additional cleanup logic System.out.println("Session cleanup completed"); } } class UserSession { private final String sessionId; private final Long userId; private final String username; private final LocalDateTime createdAt; private final LocalDateTime lastAccessedAt; private final Map<String, Object> attributes; public UserSession(String sessionId, Long userId, String username) { this.sessionId = sessionId; this.userId = userId; this.username = username; this.createdAt = LocalDateTime.now(); this.lastAccessedAt = LocalDateTime.now(); this.attributes = new ConcurrentHashMap<>(); } // Getters and setters public String getSessionId() { return sessionId; } public Long getUserId() { return userId; } public String getUsername() { return username; } public LocalDateTime getCreatedAt() { return createdAt; } public LocalDateTime getLastAccessedAt() { return lastAccessedAt; } public Map<String, Object> getAttributes() { return attributes; } public void setAttribute(String key, Object value) { attributes.put(key, value); } public Object getAttribute(String key) { return attributes.get(key); } public void updateLastAccess() { // This would typically be handled by the session manager } } class SessionStats { private final long totalSessions; private final long activeUsers; public SessionStats(long totalSessions, long activeUsers) { this.totalSessions = totalSessions; this.activeUsers = activeUsers; } // Getters public long getTotalSessions() { return totalSessions; } public long getActiveUsers() { return activeUsers; } } 6. Product Catalog Cache Service with Distributed Queries
import org.infinispan.query.Search; import org.infinispan.query.dsl.Query; import org.infinispan.query.dsl.QueryFactory; import org.infinispan.query.dsl.QueryResult; public class ProductCatalogCacheService { private final InfinispanCacheService<Long, Product> productCache; private final InfinispanCacheService<String, List<Long>> categoryIndex; private final EmbeddedCacheManager cacheManager; public ProductCatalogCacheService(EmbeddedCacheManager cacheManager) { this.cacheManager = cacheManager; this.productCache = new InfinispanCacheService<>( cacheManager, "products", Long.class, Product.class); this.categoryIndex = new InfinispanCacheService<>( cacheManager, "productCategories", String.class, (Class<List<Long>>) (Class<?>) List.class); } // Basic product operations public void cacheProduct(Product product) { productCache.put(product.getId(), product, Duration.ofHours(2)); updateCategoryIndex(product); } public Optional<Product> getProduct(Long productId) { return productCache.get(productId); } public Product getOrLoadProduct(Long productId, Function<Long, Product> loader) { return productCache.computeIfAbsent(productId, loader, Duration.ofHours(2)); } public void evictProduct(Long productId) { Optional<Product> product = getProduct(productId); productCache.remove(productId); // Update category index product.ifPresent(this::removeFromCategoryIndex); } // Category-based operations public List<Product> getProductsByCategory(String category) { return categoryIndex.get(category) .orElse(Collections.emptyList()) .stream() .map(productCache::get) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); } public void cacheProductsByCategory(String category, List<Product> products) { List<Long> productIds = products.stream() .map(Product::getId) .collect(Collectors.toList()); categoryIndex.put(category, productIds, Duration.ofHours(1)); // Cache individual products products.forEach(product -> productCache.put(product.getId(), product, Duration.ofHours(2))); } // Advanced query operations using Infinispan Query public List<Product> searchProducts(String searchTerm) { Cache<Long, Product> cache = cacheManager.getCache("products"); QueryFactory queryFactory = Search.getQueryFactory(cache); Query<Product> query = queryFactory.create( "FROM com.example.Product p WHERE p.name LIKE :searchTerm OR p.description LIKE :searchTerm"); query.setParameter("searchTerm", "%" + searchTerm + "%"); query.maxResults(100); QueryResult<Product> result = query.execute(); return result.list(); } public List<Product> searchProductsByPriceRange(String category, double minPrice, double maxPrice) { Cache<Long, Product> cache = cacheManager.getCache("products"); QueryFactory queryFactory = Search.getQueryFactory(cache); String queryString = "FROM com.example.Product p WHERE " + "p.category = :category AND p.price BETWEEN :minPrice AND :maxPrice"; Query<Product> query = queryFactory.create(queryString); query.setParameter("category", category); query.setParameter("minPrice", minPrice); query.setParameter("maxPrice", maxPrice); QueryResult<Product> result = query.execute(); return result.list(); } public List<Product> getTopRatedProducts(int limit) { Cache<Long, Product> cache = cacheManager.getCache("products"); QueryFactory queryFactory = Search.getQueryFactory(cache); Query<Product> query = queryFactory.create( "FROM com.example.Product p ORDER BY p.rating DESC"); query.maxResults(limit); QueryResult<Product> result = query.execute(); return result.list(); } // Bulk operations public void cacheAllProducts(List<Product> products) { Map<Long, Product> productMap = products.stream() .collect(Collectors.toMap(Product::getId, Function.identity())); productCache.putAll(productMap, Duration.ofHours(2)); // Update category indices products.stream() .collect(Collectors.groupingBy(Product::getCategory)) .forEach(this::cacheProductsByCategory); } // Index management private void updateCategoryIndex(Product product) { String category = product.getCategory(); List<Long> productIds = categoryIndex.get(category) .orElse(new ArrayList<>()); if (!productIds.contains(product.getId())) { productIds.add(product.getId()); categoryIndex.put(category, productIds, Duration.ofHours(1)); } } private void removeFromCategoryIndex(Product product) { String category = product.getCategory(); categoryIndex.get(category).ifPresent(productIds -> { productIds.remove(product.getId()); if (productIds.isEmpty()) { categoryIndex.remove(category); } else { categoryIndex.put(category, productIds, Duration.ofHours(1)); } }); } // Statistics public CatalogStats getCatalogStats() { long totalProducts = productCache.getSize(); long totalCategories = categoryIndex.getSize(); return new CatalogStats(totalProducts, totalCategories); } } // Product class with Hibernate Search annotations for querying import org.infinispan.protostream.annotations.ProtoField; import org.hibernate.search.annotations.*; @Indexed public class Product implements Serializable { @ProtoField(number = 1) private Long id; @Field(analyze = Analyze.YES) @ProtoField(number = 2) private String name; @Field(analyze = Analyze.YES) @ProtoField(number = 3) private String description; @Field @ProtoField(number = 4) private String category; @Field @NumericField @ProtoField(number = 5) private Double price; @Field @NumericField @ProtoField(number = 6) private Double rating; @Field @ProtoField(number = 7) private Integer stock; // Constructors, getters, setters public Product() {} public Product(Long id, String name, String description, String category, Double price, Double rating, Integer stock) { this.id = id; this.name = name; this.description = description; this.category = category; this.price = price; this.rating = rating; this.stock = stock; } // Getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public Double getRating() { return rating; } public void setRating(Double rating) { this.rating = rating; } public Integer getStock() { return stock; } public void setStock(Integer stock) { this.stock = stock; } } Spring Boot Integration
7. Spring Configuration
@Configuration @EnableCaching public class InfinispanConfig { @Bean public EmbeddedCacheManager embeddedCacheManager() { try { return new DefaultCacheManager("infinispan.xml"); } catch (IOException e) { // Fallback configuration GlobalConfiguration globalConfig = new GlobalConfigurationBuilder() .clusteredDefault() .transport().defaultTransport() .globalJmxStatistics().enable() .build(); Configuration defaultConfig = new ConfigurationBuilder() .clustering().cacheMode(CacheMode.DIST_SYNC) .memory().size(10000) .expiration().lifespan(60000) .build(); return new DefaultCacheManager(globalConfig, defaultConfig); } } @Bean public UserSessionCacheService userSessionCacheService() { return new UserSessionCacheService(embeddedCacheManager()); } @Bean public ProductCatalogCacheService productCatalogCacheService() { return new ProductCatalogCacheService(embeddedCacheManager()); } @Bean public CacheManager cacheManager() { return new SpringEmbeddedCacheManager(embeddedCacheManager()); } } 8. Spring Service with Infinispan Caching
@Service @Transactional public class CachedProductService { private final ProductRepository productRepository; private final ProductCatalogCacheService cacheService; public CachedProductService(ProductRepository productRepository, ProductCatalogCacheService cacheService) { this.productRepository = productRepository; this.cacheService = cacheService; } @Cacheable(value = "products", key = "#productId") public Product getProductById(Long productId) { System.out.println("Fetching product from database: " + productId); return productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException("Product not found: " + productId)); } public Product getProductByIdWithCache(Long productId) { return cacheService.getOrLoadProduct(productId, id -> productRepository.findById(id) .orElseThrow(() -> new ProductNotFoundException("Product not found: " + id))); } @CacheEvict(value = "products", key = "#product.id") public Product updateProduct(Product product) { Product updatedProduct = productRepository.save(product); cacheService.cacheProduct(updatedProduct); return updatedProduct; } @CacheEvict(value = "products", key = "#productId") public void deleteProduct(Long productId) { productRepository.deleteById(productId); cacheService.evictProduct(productId); } public List<Product> searchProducts(String searchTerm) { // Try cache first for recent searches List<Product> cachedResults = cacheService.searchProducts(searchTerm); if (!cachedResults.isEmpty()) { return cachedResults; } // Search in database List<Product> results = productRepository.findBySearchTerm(searchTerm); // Cache the results by updating individual product cache results.forEach(cacheService::cacheProduct); return results; } public List<Product> getProductsByCategory(String category) { return cacheService.getProductsByCategory(category); } // Cache warming on application startup @EventListener(ApplicationReadyEvent.class) public void warmUpCaches() { System.out.println("Warming up product caches..."); List<Product> popularProducts = productRepository.findPopularProducts(1000); cacheService.cacheAllProducts(popularProducts); System.out.println("Product cache warm-up completed."); } } Hot Rod Client for Remote Access
9. Remote Cache Configuration
import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.RemoteCacheManager; import org.infinispan.client.hotrod.configuration.ConfigurationBuilder; public class RemoteCacheService<K, V> { private final RemoteCache<K, V> remoteCache; private final RemoteCacheManager remoteCacheManager; private final String cacheName; public RemoteCacheService(String cacheName, Class<K> keyType, Class<V> valueType, String serverHost, int serverPort) { this.cacheName = cacheName; ConfigurationBuilder builder = new ConfigurationBuilder(); builder.addServer() .host(serverHost) .port(serverPort) .security() .authentication() .username("admin") .password("password") .realm("default") .saslMechanism("DIGEST-MD5"); this.remoteCacheManager = new RemoteCacheManager(builder.build()); this.remoteCache = remoteCacheManager.getCache(cacheName); } // Basic operations public void put(K key, V value) { remoteCache.put(key, value); } public void put(K key, V value, long lifespan, TimeUnit unit) { remoteCache.put(key, value, lifespan, unit); } public Optional<V> get(K key) { V value = remoteCache.get(key); return Optional.ofNullable(value); } public boolean contains(K key) { return remoteCache.containsKey(key); } public void remove(K key) { remoteCache.remove(key); } // Async operations public CompletableFuture<Void> putAsync(K key, V value) { return remoteCache.putAsync(key, value); } public CompletableFuture<V> getAsync(K key) { return remoteCache.getAsync(key); } // Bulk operations public void putAll(Map<K, V> data) { remoteCache.putAll(data); } public Map<K, V> getAll(Set<K> keys) { return remoteCache.getAll(keys); } // Query operations (requires proper configuration on server) public <T> List<T> query(String queryString, Map<String, Object> parameters) { // This requires Infinispan Query and proper entity configuration // Similar to embedded query but using remote cache return Collections.emptyList(); // Implementation depends on setup } public void close() { remoteCacheManager.close(); } // Statistics public ServerStatistics getStatistics() { return remoteCache.serverStatistics(); } } Advanced Infinispan Features
10. Event Listeners and Monitoring
import org.infinispan.notifications.Listener; import org.infinispan.notifications.cachelistener.annotation.*; import org.infinispan.notifications.cachelistener.event.*; @Listener public class CacheEventListener { private static final Logger logger = LoggerFactory.getLogger(CacheEventListener.class); @CacheEntryCreated public void entryCreated(CacheEntryCreatedEvent<String, Object> event) { if (!event.isPre()) { logger.info("Entry created - Key: {}, Value: {}", event.getKey(), event.getValue()); } } @CacheEntryModified public void entryModified(CacheEntryModifiedEvent<String, Object> event) { if (!event.isPre()) { logger.info("Entry modified - Key: {}, Old: {}, New: {}", event.getKey(), event.getOldValue(), event.getNewValue()); } } @CacheEntryRemoved public void entryRemoved(CacheEntryRemovedEvent<String, Object> event) { if (!event.isPre()) { logger.info("Entry removed - Key: {}, Value: {}", event.getKey(), event.getOldValue()); } } @CacheEntryExpired public void entryExpired(CacheEntryExpiredEvent<String, Object> event) { logger.info("Entry expired - Key: {}, Value: {}", event.getKey(), event.getOldValue()); } } // Usage public class CacheWithEventListener { public void setupEventListeners(Cache<String, Object> cache) { cache.addListener(new CacheEventListener()); } } 11. Cache Configuration with Advanced Features
public class AdvancedCacheConfiguration { public Configuration createAdvancedCacheConfig() { return new ConfigurationBuilder() .clustering() .cacheMode(CacheMode.DIST_SYNC) .hash().numOwners(2) // Number of backup copies .memory() .size(50000) // Maximum entries .evictionType(EvictionType.COUNT) .storage(StorageType.OFF_HEAP) // Use off-heap storage .expiration() .lifespan(3600000) // 1 hour .maxIdle(1800000) // 30 minutes idle .persistence() .passivation(false) .addSingleFileStore() .location("/tmp/infinispan") .maxEntries(100000) .locking() .isolationLevel(IsolationLevel.REPEATABLE_READ) .concurrencyLevel(1000) .transaction() .transactionMode(TransactionMode.TRANSACTIONAL) .lockingMode(LockingMode.PESSIMISTIC) .jmxStatistics() .enable() .build(); } public Configuration createIndexedCacheConfig() { return new ConfigurationBuilder() .indexing() .enable() .index(Index.PRIMARY_OWNER) .addIndexedEntity(Product.class) .clustering() .cacheMode(CacheMode.DIST_SYNC) .memory() .size(10000) .build(); } } Testing Infinispan Implementations
12. Cache Testing
@ExtendWith(MockitoExtension.class) class ProductCatalogCacheServiceTest { private EmbeddedCacheManager cacheManager; private ProductCatalogCacheService cacheService; @BeforeEach void setUp() { cacheManager = new DefaultCacheManager(); cacheService = new ProductCatalogCacheService(cacheManager); } @AfterEach void tearDown() { cacheManager.stop(); } @Test void cacheProduct_ThenRetrieve_ShouldReturnCachedProduct() { // Arrange Product product = new Product(1L, "Laptop", "High-performance laptop", "Electronics", 999.99, 4.5, 10); // Act cacheService.cacheProduct(product); Optional<Product> cachedProduct = cacheService.getProduct(1L); // Assert assertTrue(cachedProduct.isPresent()); assertEquals(product.getId(), cachedProduct.get().getId()); assertEquals(product.getName(), cachedProduct.get().getName()); } @Test void searchProducts_WithExistingData_ShouldReturnResults() { // Arrange Product product1 = new Product(1L, "Dell Laptop", "Gaming laptop", "Electronics", 1299.99, 4.7, 5); Product product2 = new Product(2L, "MacBook Pro", "Professional laptop", "Electronics", 1999.99, 4.8, 3); cacheService.cacheProduct(product1); cacheService.cacheProduct(product2); // Act List<Product> results = cacheService.searchProducts("laptop"); // Assert assertFalse(results.isEmpty()); assertTrue(results.stream().anyMatch(p -> p.getName().contains("Laptop"))); } } @SpringBootTest class CachedProductServiceIntegrationTest { @Autowired private CachedProductService cachedProductService; @Autowired private ProductRepository productRepository; @Test void getProductById_WithCaching_ShouldHitCacheOnSecondCall() { // Arrange Product product = new Product(1L, "Test Product", "Test Description", "Test", 99.99, 4.0, 10); productRepository.save(product); // Act - First call (should hit database) Product firstResult = cachedProductService.getProductById(1L); // Act - Second call (should hit cache) Product secondResult = cachedProductService.getProductById(1L); // Assert assertEquals(product.getId(), firstResult.getId()); assertEquals(product.getId(), secondResult.getId()); } } Key Benefits of Infinispan
- Distributed Caching: True distributed cache with data partitioning
- High Availability: Automatic failover and data replication
- Data Grid Capabilities: Advanced features like distributed execution, map/reduce
- Persistence: Support for various persistent stores
- Transactions: Full ACID transaction support
- Querying: Distributed query capabilities using Apache Lucene
- Cloud Native: Kubernetes and OpenShift support
- Protocol Support: Multiple protocols including Hot Rod, REST, Memcached
Infinispan provides enterprise-grade distributed caching and data grid capabilities suitable for high-performance, scalable applications requiring distributed data management.