JCache and Beyond
This is a legacy Apache Ignite documentation
The new documentation is hosted here: https://ignite.apache.org/docs/latest/
- Overview
- IgniteCache API
- Dynamic Cache
- Basic Operations
- EntryProcessor
- Asynchronous Support
- Using Ignite as JCache Provider
Overview
Apache Ignite data grid is an implementation of the JCache (JSR 107) specification. JCache provides a very simple to use, but yet very powerful API for data access. However, the specification purposely omits any details about data distribution and consistency to allow vendors enough freedom in their own implementations.
With JCache support you get the following:
- Basic Cache Operations
- ConcurrentMap APIs
- Collocated Processing (EntryProcessor)
- Events and Metrics
- Pluggable Persistence
In addition to JCache, Ignite provides ACID transactions, data querying capabilities (including SQL), various memory models, queries, transactions, and more.
IgniteCache
The IgniteCache
interface is a gateway into the Ignite cache implementation and provides methods for storing and retrieving data, executing queries, including SQL, iterating and scanning, etc.
IgniteCache
is based on JCache (JSR 107), so at the very basic level the APIs can be reduced to the javax.cache.Cache
interface. However, the IgniteCache
API also provides functionality that facilitates features outside of the JCache spec, like data loading, querying, asynchronous mode, etc.
You can get an instance of IgniteCache
directly from Ignite
:
Ignite ignite = Ignition.ignite();
// Obtain instance of cache named "myCache".
// Note that different caches may have different generics.
IgniteCache<Integer, String> cache = ignite.cache("myCache");
Cache Name Restriction
Because of a known issue, cache names cannot contain characters that are prohibited in file names (such as '/' on Linux or '' on Windows).
Dynamic Cache
You can also create an instance of the cache on the fly, in which case Ignite will create and deploy the cache across all server cluster members that match the cache node filter. After a dynamic cache has been started, it will also be automatically deployed on all the newly joined server cluster members that match the cache node filter.
Ignite ignite = Ignition.ignite();
CacheConfiguration cfg = new CacheConfiguration();
cfg.setName("myCache");
cfg.setAtomicityMode(TRANSACTIONAL);
// Create cache with given name, if it does not exist.
IgniteCache<Integer, String> cache = ignite.getOrCreateCache(cfg);
XML Configuration
All caches defined in Ignite Spring XML configuration on any cluster member will also be automatically created and deployed on all the cluster servers (no need to specify the same configuration on each cluster member).
Basic Operations
Here are some basic JCache atomic operation examples:
try (Ignite ignite = Ignition.start("examples/config/example-cache.xml")) {
IgniteCache<Integer, String> cache = ignite.cache(CACHE_NAME);
// Store keys in cache (values will end up on different cache nodes).
for (int i = 0; i < 10; i++)
cache.put(i, Integer.toString(i));
for (int i = 0; i < 10; i++)
System.out.println("Got [key=" + i + ", val=" + cache.get(i) + ']');
}
// Put-if-absent which returns previous value.
Integer oldVal = cache.getAndPutIfAbsent("Hello", 11);
// Put-if-absent which returns boolean success flag.
boolean success = cache.putIfAbsent("World", 22);
// Replace-if-exists operation (opposite of getAndPutIfAbsent), returns previous value.
oldVal = cache.getAndReplace("Hello", 11);
// Replace-if-exists operation (opposite of putIfAbsent), returns boolean success flag.
success = cache.replace("World", 22);
// Replace-if-matches operation.
success = cache.replace("World", 2, 22);
// Remove-if-matches operation.
success = cache.remove("Hello", 1);
Deadlock
If batch operations (such as
IgniteCache#putAll
,IgniteCache#invokeAll
, etc.) are performed in parallel, then keys should be ordered in the same way to avoid deadlock. UseTreeMap
instead ofHashMap
to guarantee consistent ordering. Note that this is true for bothATOMIC
andTRANSACTIONAL
caches.
EntryProcessor
Whenever doing puts
and updates
in cache, you are usually sending the full object state across the network. EntryProcessor
allows for processing data directly on primary nodes, often transferring only the deltas instead of the full state.
Moreover, you can embed your own logic into EntryProcessors
, for example, taking a previous cached value and incrementing it by 1.
IgniteCache<String, Integer> cache = ignite.cache("mycache");
// Increment cache value 10 times.
for (int i = 0; i < 10; i++)
cache.invoke("mykey", (entry, args) -> {
Integer val = entry.getValue();
entry.setValue(val == null ? 1 : val + 1);
return null;
});
IgniteCache<String, Integer> cache = ignite.cache("mycache");
// Increment cache value 10 times.
for (int i = 0; i < 10; i++)
cache.invoke("mykey", new EntryProcessor<String, Integer, Void>() {
@Override
public Object process(MutableEntry<Integer, String> entry, Object... args) {
Integer val = entry.getValue();
entry.setValue(val == null ? 1 : val + 1);
return null;
}
});
Atomicity
EntryProcessors
are executed atomically within a lock on the given cache key.
Asynchronous Support
Just like all distributed APIs in Ignite, IgniteCache
extends the IgniteAsynchronousSupport interface and can be used in asynchronous mode.
// Enable asynchronous mode.
IgniteCache<String, Integer> asyncCache = ignite.cache("mycache").withAsync();
// Asynhronously store value in cache.
asyncCache.getAndPut("1", 1);
// Get future for the above invocation.
IgniteFuture<Integer> fut = asyncCache.future();
// Asynchronously listen for the operation to complete.
fut.listenAsync(f -> System.out.println("Previous cache value: " + f.get()));
Using Ignite as JCache Provider
JCache API accepts a provider-specific configuration if a cache instance needs to be created using the JCache manager. This allows you to take advantage of the distributed caching capability that Ignite provides, without any modifications to your existing code if you're migrating from a different JCache implementation.
Here is an example of how to provide an Ignite cache configuration using JCache manager:
// Get or create a cache manager.
CacheManager cacheMgr = Caching.getCachingProvider().getCacheManager();
// This is an Ignite configuration object (org.apache.ignite.configuration.CacheConfiguration).
CacheConfiguration<Integer, String> cfg = new CacheConfiguration<>();
// Specify cache mode and/or any other Ignite-specific configuration properties.
cfg.setCacheMode(CacheMode.PARTITIONED);
// Create a cache based on the configuration created above.
Cache<Integer, String> cache = cacheMgr.createCache("aCache", cfg);
// Cache operations
Integer key = 1;
String value = "11";
cache.put(key, value");
System.out.println(cache.get(key));
Additionally, the CachingProvider.getCacheManager(..)
method accepts a provider-specific URI which, for Ignite, should point to the XML configuration file. For example:
// Get or create a cache manager.
CacheManager cacheMgr = Caching.getCachingProvider().getCacheManager(
Paths.get("path/to/ignite/xml/configuration/file").toUri(), null);
// Get a cache defined in the configuration file provided above.
Cache<Integer, String> cache = cacheMgr.getCache("myCache");
// Cache operations
Integer key = 1;
String value = "11";
cache.put(key, value");
System.out.println(cache.get(key));
It's worth mentioning that the above Cache<K, V>
instance will only support features of javax.cache.Cache
type.
To use extended features provided by Ignite, such as SQL, scan, or continuous queries, you need to convert javax.cache.Cache
instance to IgniteCache
instance, like so:
// Get or create a cache manager.
CacheManager cacheMgr = Caching.getCachingProvider().getCacheManager(
Paths.get("path/to/ignite/xml/configuration/file").toUri(), null);
// Get a cache defined in the configuration file provided above.
Cache<Integer, String> cache = cacheMgr.getCache("myCache");
// Obtain org.apache.ignite.IgniteCache instance.
IgniteCache igniteCache = (IgniteCache) cache;
// Ignite specific cache operations
//......
Updated 2 months ago