1. Overview
GemFire is a high performance distributed data management infrastructure that sits between application cluster and back-end data sources.
With GemFire, data can be managed in-memory, which makes the access faster. Spring Data provides an easy configuration and access to GemFire from Spring application.
In this article, we’ll take a look at how we can use GemFire to meet our application’s caching requirements.
2. Maven Dependencies
To make use of the Spring Data GemFire support, we first need to add the following dependency in our pom.xml:
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-gemfire</artifactId> <version>1.9.1.RELEASE</version> </dependency>
The latest version of this dependency can be found here.
3. GemFire Basic Features
3.1. Cache
The cache in the GemFire provides the essential data management services as well as manages the connectivity to other peers.
The cache configuration (cache.xml) describes how the data will be distributed among different nodes:
<cache> <region name="region"> <region-attributes> <cache-listener> <class-name> ... </class-name> </cache-listener> </region-attributes> </region> ... </cache>
3.2. Regions
Data regions are a logical grouping within a cache for a single data set.
Simply put, a region lets us store data in multiple VMs in the system without consideration to which node the data is stored within the cluster.
Regions are classified into three broad categories:
- Replicated region holds the complete set of data on each node. It gives a high read performance. Write operations are slower as the data update need to be propagated to each node:
<region name="myRegion" refid="REPLICATE"/>
- Partitioned region distributes the data so that each node only stores a part of region contents. A copy of the data is stored on one of the other nodes. It provides a good write performance.
<region name="myRegion" refid="PARTITION"/>
- Local region resides on the defining member node. There is no connectivity with other nodes within the cluster.
<region name="myRegion" refid="LOCAL"/>
3.3. Query the Cache
GemFire provides a query language called OQL (Object Query Language) that allows us to refer to the objects stored in GemFire data regions. This is very similar to SQL in syntax. Let’s see how a very basic query looks like:
SELECT DISTINCT * FROM exampleRegion
GemFire’s QueryService provides methods to create the query object.
3.4. Data Serialization
To manage the data serialization-deserialization, GemFire provides options other than Java serialization that gives a higher performance, provides greater flexibility for data storage and data transfer, also support for different languages.
With that in mind, GemFire has defined Portable Data eXchange(PDX) data format. PDX is a cross-language data format that provides a faster serialization and deserialization, by storing the data in the named field which can be accessed directly without the need of fully deserializing the object.
3.5. Function Execution
In GemFire, a function can reside on a server and can be invoked from a client application or another server without the need to send the function code itself.
The caller can direct a data-dependent function to operate on a particular data set or can lead an independent data function to work on a particular server, member or member group.
3.6. Continuous Querying
With continuous querying, the clients subscribe to server side events by using SQL-type query filtering. The server sends all the events that modify the query results. The continuous querying event delivery uses the client/server subscription framework.
The syntax for a continuous query is similar to basic queries written in OQL. For example, a query which provides the latest stock data from Stock region can be written as:
SELECT * from StockRegion s where s.stockStatus='active';
To get the status update from this query, an implementation of CQListener need to be attached with the StockRegion:
<cache> <region name="StockRegion> <region-attributes refid="REPLICATE"> ... <cache-listener> <class-name>...</class-name> </cache-listener> ... </region-attributes> </region> </cache>
4. Spring Data GemFire Support
4.1. Java Configuration
To simplify configuration, Spring Data GemFire provides various annotations for configuring core GemFire components:
@Configuration public class GemfireConfiguration { @Bean Properties gemfireProperties() { Properties gemfireProperties = new Properties(); gemfireProperties.setProperty("name","SpringDataGemFireApplication"); gemfireProperties.setProperty("mcast-port", "0"); gemfireProperties.setProperty("log-level", "config"); return gemfireProperties; } @Bean CacheFactoryBean gemfireCache() { CacheFactoryBean gemfireCache = new CacheFactoryBean(); gemfireCache.setClose(true); gemfireCache.setProperties(gemfireProperties()); return gemfireCache; } @Bean(name="employee") LocalRegionFactoryBean<String, Employee> getEmployee(final GemFireCache cache) { LocalRegionFactoryBean<String, Employee> employeeRegion = new LocalRegionFactoryBean(); employeeRegion.setCache(cache); employeeRegion.setName("employee"); // ... return employeeRegion; } }
To set up the GemFire cache and region, we have to first setup few specific properties. Here mcast-port is set to zero, which indicates that this GemFire node is disabled for multicast discovery and distribution. These properties are then passed to CacheFactoryBean to create a GemFireCache instance.
Using GemFireCache bean, an instance of LocalRegionFatcoryBean is created which represents the region within the Cache for the Employee instances.
4.2. Entity Mapping
The library provides support to map objects to be stored in GemFire grid. The mapping metadata is defined by using annotations at the domain classes:
@Region("employee") public class Employee { @Id public String name; public double salary; @PersistenceConstructor public Employee(String name, double salary) { this.name = name; this.salary = salary; } // standard getters/setters }
In the example above, we used the following annotations:
- @Region, to specify the region instance of the Employee class
- @Id, to annotate the property that shall be utilized as a cache key
- @PersistenceConstructor, which helps to mark the one constructor that will be used to create entities, in case multiple constructors available
4.3. GemFire Repositories
Next, let’s have a look at a central component in Spring Data – the repository:
@Configuration @EnableGemfireRepositories(basePackages = "com.baeldung.spring.data.gemfire.repository") public class GemfireConfiguration { @Autowired EmployeeRepository employeeRepository; // ... }
4.4. OQL Query support
The repositories allow the definition of query methods to efficiently run the OQL queries against the region the managed entity is mapped to:
@Repository public interface EmployeeRepository extends CrudRepository<Employee, String> { Employee findByName(String name); Iterable<Employee> findBySalaryGreaterThan(double salary); Iterable<Employee> findBySalaryLessThan(double salary); Iterable<Employee> findBySalaryGreaterThanAndSalaryLessThan(double salary1, double salary2); }
4.5. Function Execution Support
We also have annotation support available – to simplify working with GemFire function execution.
There are two concerns to address when we make use of functions, the implementation, and the execution.
Let’s see how a POJO can be exposed as a GemFire function using Spring Data annotations:
@Component public class FunctionImpl { @GemfireFunction public void greeting(String message){ // some logic } // ... }
We need to activate the annotation processing explicitly for @GemfireFunction to work:
@Configuration @EnableGemfireFunctions public class GemfireConfiguration { // ... }
For function execution, a process invoking a remote function need to provide calling arguments, a function id, the execution target (onServer, onRegion, onMember, etc.):
@OnRegion(region="employee") public interface FunctionExecution { @FunctionId("greeting") public void execute(String message); // ... }
To enable the function execution annotation processing, we need to add to activate it using Spring’s component scanning capabilities:
@Configuration @EnableGemfireFunctionExecutions( basePackages = "com.baeldung.spring.data.gemfire.function") public class GemfireConfiguration { // ... }
5. Conclusion
In this article, we have explored GemFire essential features and examined how Spring Data provided APIs make it easy to work with it.
The complete code for this article is available over on GitHub.