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.