Chapter 14. GemFire: A Distributed Data Grid

vFabricGemFire® (GemFire) is a commercially licensed data management platform that provides access to data throughout widely distributed architectures. It is available as a standalone product and as a component of the VMware vFabric Suite. This chapter provides an overview of Spring Data GemFire. We’ll begin by introducing GemFire and some basic concepts that are prerequisite to developing with GemFire. Feel free to skip to the section Configuring GemFire with the Spring XML Namespace if you are already familiar with GemFire.

GemFire in a Nutshell

GemFire provides an in-memory data grid that offers extremely high throughput, low latency data access, and scalability. Beyond a distributed cache, GemFire provides advanced features including:

  • Event notification

  • OQL (Object Query Language) query syntax

  • Continuous queries

  • Transaction support

  • Remote function execution

  • WAN communications

  • Efficient and portable object serialization (PDX)

  • Tools to aid system administrators in managing and configuring the GemFire distributed system

GemFire may be configured to support a number of distributed system topologies and is completely integrated with the Spring Framework. Figure 14-1 shows a typical client server configuration for a production LAN. The locator acts as a broker for the distributed system to support discovery of new member nodes. Client applications use the locator to acquire connections to cache servers. Additionally, server nodes use the locator to discover each other. Once a server comes online, it communicates directly with its peers. Likewise, once a client is initialized, it communicates directly with cache servers. Since a locator is a single point of failure, two instances are required for redundancy.

GemFire client server topology

Figure 14-1. GemFire client server topology

Simple standalone configurations for GemFire are also possible. Note that the book’s code samples are configured very simply as a single process with an embedded cache, suitable for development and integration testing.

In a client server scenario, the application process uses a connection pool (Figure 14-2) to manage connections between the client cache and the servers. The connection pool manages network connections, allocates threads, and provides a number of tuning options to balance resource usage and performance. The pool is typically configured with the address of the locator(s) [not shown in Figure 14-2]. Once the locator provides a server connection, the client communicates directly with the server. If the primary server becomes unavailable, the pool will acquire a connection to an alternate server if one is available.

The connection pool

Figure 14-2. The connection pool

Caches and Regions

Conceptually, a cache is a singleton object that provides access to a GemFire member and offers a number of configuration options for memory tuning, network communications, and other features. The cache also acts as a container for regions, which provide data management and access.

A region is required to store and retrieve data from the cache. Region is an interface that extends java.uti.Map to perform basic data access using familiar key/value semantics. The Region interface is wired into classes that require it, so the actual region type is decoupled from the programming model (with some caveats, the discovery of which will be left as an exercise for the reader). Typically, each region is associated with one domain object, similar to a table in a relational database. Looking at the sample code, you will see three regions defined: Customer, Product, and Order. Note that GemFire does not manage associations or enforce relational integrity among regions.

GemFire includes the following types of regions:

Replicated

Data is replicated across all cache members that define the region. This provides very high read performance, but writes take longer due to the need to perform the replication.

Partitioned

Data is partitioned into buckets among cache members that define the region. This provides high read and write performance and is suitable for very large datasets that are too big for a single node.

Local

Data exists only on the local node.

Client

Technically, a client region is a local region that acts as a proxy to a replicated or partitioned region hosted on cache servers. It may hold data created or fetched locally; alternatively, it can be empty. Local updates are synchronized to the cache server. Also, a client region may subscribe to events in order to stay synchronized with changes originating from remote processes that access the same region.

Hopefully, this brief overview gives you a sense of GemFire’s flexibility and maturity. A complete discussion of GemFire options and features is beyond the scope of this book. Interested readers will find more details on the product website.

How to Get GemFire

The vFabric GemFire website provides detailed product information, reference guides, and a link to a free developer download, limited to three node connections. For a more comprehensive evaluation, a 60-day trial version is also available.

Note

The product download is not required to run the code samples included with this book. The GemFire jar file that includes the free developer license is available in public repositories and will be automatically downloaded by build tools such as Maven and Gradle when you declare a dependency on Spring Data GemFire. A full product install is necessary to use locators, the management tools, and so on.

Configuring GemFire with the Spring XML Namespace

Spring Data GemFire includes a dedicated XML namespace to allow full configuration of the data grid. In fact, the Spring namespace is considered the preferred way to configure GemFire, replacing GemFire’s native cache.xml file. GemFire will continue to support cache.xml for legacy reasons, but you can now do everything in Spring XML and take advantage of the many wonderful things Spring has to offer, such as modular XML configuration, property placeholders, SpEL, and environment profiles. Behind the namespace, Spring Data GemFire makes extensive use of Spring’s FactoryBean pattern to simplify the creation and initialization of GemFire components.

GemFire provides several callback interfaces, such as CacheListener, CacheWriter, and CacheLoader to allow developers to add custom event handlers. Using the Spring IoC container, these may configured as normal Spring beans and injected into GemFire components. This is a significant improvement over cache.xml, which provides relatively limited configuration options and requires callbacks to implement GemFire’s Declarable interface.

In addition, IDEs such as the Spring Tool Suite (STS) provide excellent support for XML namespaces, such as code completion, pop-up annotations, and real-time validation, making them easy to use.

The following sections are intended to get you started using the Spring XML namespace for GemFire. For a more comprehensive discussion, please refer to the Spring Data GemFire reference guide at the project website.

Cache Configuration

To configure a GemFire cache, create a Spring bean definition file and add the Spring GemFire namespace. In STS (Figure 14-3), select the project and open the context menu (right-click) and select NewSpring Bean Configuration File. Give it a name and click Next.

Create a Spring bean definition file in STS

Figure 14-3. Create a Spring bean definition file in STS

In the XSD namespaces view, select the gfe namespace (Figure 14-4).

Note

Notice that, in addition to the gfe namespace, there is a gfe-data namespace for Spring Data POJO mapping and repository support. The gfe namespace is used for core GemFire configuration.

Click Finish to open the bean definition file in an XML editor with the correct namespace declarations. (See Example 14-1.)

Selecting Spring XML namespaces

Figure 14-4. Selecting Spring XML namespaces

Example 14-1. Declaring a GemFire cache in Spring configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:gfe="http://www.springframework.org/schema/gemfire"
       xsi:schemaLocation="http://www.springframework.org/schema/gemfire 
                           http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
                           http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">  
   
   <gfe:cache/>

</beans>

Now use the gfe namespace to add a cache element. That’s it! This simple cache declaration will create an embedded cache, register it in the Spring ApplicationContext as gemfireCache, and initialize it when the context is created.

Note

Prior releases of Spring Data GemFire created default bean names using hyphens (e.g., gemfire-cache). As of the 1.2.0 release, these are replaced with camelCase names to enable autowiring via annotations (@Autowired). The old-style names are registered as aliases to provide backward compatibility.

You can easily change the bean name by setting the id attribute on the cache element. However, all other namespace elements assume the default name unless explicitly overridden using the cache-ref attribute. So you can save yourself some work by following the convention.

The cache element provides some additional attributes, which STS will happily suggest if you press Ctrl-Space. The most significant is the properties-ref attribute. In addition to the API, GemFire exposes a number of global configuration options via external properties. By default, GemFire looks for a file called gemfire.properties in all the usual places: the user’s home directory, the current directory, and the classpath. While this is convenient, it may result in unintended consequences if you happen to have these files laying around. Spring alleviates this problem by offering several better alternatives via its standard property loading mechanisms. For example, you can simply construct a java.util.Properties object inline or load it from a properties file located on the classpath or filesystem. Example 14-2 uses properties to configure GemFire logging.

Example 14-2. Referencing properties to configure GemFire

Define properties inline:

<util:properties id="props">
   <prop key="log-level">info</prop>
   <prop key="log-file">gemfire.log</prop>
</util:properties>
  
<gfe:cache properties-ref="props" />

Or reference a resource location:

<util:properties id="props" location="gemfire-cache.properties" />

<gfe:cache properties-ref="props" />

Note

It is generally preferable to maintain properties of interest to system administrators in an agreed-upon location in the filesystem rather than defining them in Spring XML or packaging them in .jar files.

Note the use of Spring’s util namespace to create a Properties object. This is related to, but not the same as, Spring’s property placeholder mechanism, which uses token-based substitution to allow properties on any bean to be defined externally from a variety of sources. Additionally, the cache element includes a cache-xml-location attribute to enable the cache to be configured with GemFire’s native configuration schema. As previously noted, this is mostly there for legacy reasons.

The cache element also provides some pdx-* attributes required to enable and configure GemFire’s proprietary serialization feature (PDX). We will address PDX in Repository Usage.

For advanced cache configuration, the cache element provides additional attributes for tuning memory and network communications (shown in Figure 14-5) and child elements to register callbacks such as TransactionListers, and TransactionWriters (Figure 14-6).

Displaying a list of cache attributes in STS

Figure 14-5. Displaying a list of cache attributes in STS

Displaying a list of child elements in STS

Figure 14-6. Displaying a list of child elements in STS

Note

The use-bean-factory-locator attribute (not shown) deserves a mention. The factory bean responsible for creating the cache uses an internal Spring type called a BeanFactoryLocator to enable user classes declared in GemFire’s native cache.xml file to be registered as Spring beans. The BeanFactoryLocator implementation also permits only one bean definition for a cache with a given id. In certain situations, such as running JUnit integration tests from within Eclipse, you’ll need to disable the BeanFactoryLocator by setting this value to false to prevent an exception. This exception may also arise during JUnit tests running from a build script. In this case, the test runner should be configured to fork a new JVM for each test (in Maven, set <forkmode>always</forkmode>). Generally, there is no harm in setting this value to false.

Region Configuration

As mentioned in the chapter opener, GemFire provides a few types of regions. The XML namespace defines replicated-region, partitioned-region, local-region, and client-region elements to create regions. Again, this does not cover all available features but highlights some of the more common ones. A simple region declaration, as shown in Example 14-3, is all you need to get started.

Example 14-3. Basic region declaration

<gfe:cache/>
<gfe:replicated-region id="Customer" />

The region has a dependency on the cache. Internally, the cache creates the region. By convention, the namespace does the wiring implicitly. The default cache declaration creates a Spring bean named gemfireCache. The default region declaration uses the same convention. In other words, Example 14-3 is equivalent to:

<gfe:cache id="gemfireCache" />
<gfe:replicated-region id="Customer" cache-ref="gemfireCache" />

If you prefer, you can supply any valid bean name, but be sure to set cache-ref to the corresponding bean name as required.

Typically, GemFire is deployed as a distributed data grid, hosting replicated or partitioned regions on cache servers. Client applications use client regions to access data. For development and integration testing, it is a best practice to eliminate any dependencies on an external runtime environment. You can do this by simply declaring a replicated or local region with an embedded cache, as is done in the sample code. Spring environment profiles are extremely useful in configuring GemFire for different environments.

In Example 14-4, the dev profile is intended for integration testing, and the prod profile is used for the deployed cache configuration. The cache and region configuration is transparent to the application code. Also note the use of property placeholders to specify the locator hosts and ports from an external properties file. Cache client configuration is discussed further in Cache Client Configuration.

Example 14-4. Sample XML configuration for development and production

<beans profile="dev">
  <gfe:cache/>
  <gfe:replicated-region id="Customer" />
</beans>

<beans profile="prod">
  <context:properties-placeholder location="client-app.properties" />
  <gfe:client-cache pool-name="pool" />
  
  <gfe:client-region id="Customer" />
  
  <gfe:pool id="pool">
    <gfe:locator host="${locator.host.1}" port="${locator.port.1}"/>
    <gfe:locator host="${locator.host.2}" port="${locator.port.2}"/>
 </gfe:pool>
</beans>

Note

Spring provides a few ways to activate the appropriate environment profile(s). You can set the property spring.profiles.active in a system property, a servlet context parameter, or via the @ActiveProfiles annotation.

As shown in Figure 14-7, there are a number of common region configuration options as well as specific options for each type of region. For example, you can configure all regions to back up data to a local disk store synchronously or asynchronously.

Displaying replicated region attributes in STS

Figure 14-7. Displaying replicated region attributes in STS

Additionally, you may configure regions to synchronize selected entries over a WAN gateway to distribute data over a wide geographic area. You may also register CacheListeners, CacheLoaders, and CacheWriters to handle region events. Each of these interfaces is used to implement a callback that gets invoked accordingly. A CacheListener is a generic event handler invoked whenever an entry is created, updated, destroyed, etc. For example, you can write a simple CacheListener to log cache events, which is particularly useful in a distributed environment (see Example 14-5). A CacheLoader is invoked whenever there is a cache miss (i.e., the requested entry does not exist), allowing you to “read through” to a database or other system resource. A CacheWriter is invoked whenever an entry is updated or created to provide “write through” or “write behind” capabilities.

Example 14-5. LoggingCacheListener implementation

public class LoggingCacheListener extends CacheListenerAdapter {
  
  private static Log log = LogFactory.getLog(LoggingCacheListener.class);
 
  @Override
  public void afterCreate(EntryEvent event) {
     String regionName = event.getRegion().getName();
     Object key = event.getKey();
     Object newValue = event.getNewValue();
     log.info("In region [" + regionName + "] created key ["
       + key + "] value [" + newValue + "]");
   }

   @Override
   public void afterDestroy(EntryEvent event) {
    
   }

   @Override
   public void afterUpdate(EntryEvent event) {
    
   }
}

Other options include expiration, the maximum time a region or an entry is held in the cache, and eviction, policies that determine which items are removed from the cache when the defined memory limit or the maximum number of entries is reached. Evicted entries may optionally be stored in a disk overflow.

You can configure partitioned regions to limit the amount of local memory allocated to each partition node, define the number of buckets used, and more. You may even implement your own PartitionResolver to control how data is colocated in partition nodes.

Cache Client Configuration

In a client server configuration, application processes are cache clients—that is, they produce and consume data but do not distribute it directly to other processes. Neither does a cache client implicitly see updates performed by remote processes. As you might expect by now, this is entirely configurable. Example 14-6 shows a basic client-side setup using a client-cache, client-region, and a pool. The client-cache is a lightweight implementation optimized for client-side services, such as managing one or more client regions. The pool represents a connection pool acting as a bridge to the distributed system and is configured with any number of locators.

Note

Typically, two locators are sufficient: the first locator is primary, and the remaining ones are strictly for failover. Every distributed system member should use the same locator configuration. A locator is a separate process, running in a dedicated JVM, but is not strictly required. For development and testing, the pool also provides a server child element to access cache servers directly. This is useful for setting up a simple client/server environment (e.g., on your local machine) but not recommended for production systems. As mentioned in the chapter opener, using a locator requires a full GemFire installation, whereas you can connect to a server directly just using the APIs provided in the publicly available gemfire.jar for development, which supports up to three cache members.

Example 14-6. Configuring a cache pool

<gfe:client-cache pool-name="pool" />

<gfe:client-region id="Customer" />

<gfe:pool id="pool">
  <gfe:locator host="${locator.host.1}" port="${locator.port.1}"/>
  <gfe:locator host="${locator.host.2}" port="${locator.port.2}"/>
</gfe:pool>

You can configure the pool to control thread allocation for connections and network communications. Of note is the subscription-enabled attribute, which you must set to true to enable synchronizing region entry events originating from remote processes (Example 14-7).

Example 14-7. Enabling subscriptions on a cache pool

<gfe:client-region id="Customer">
   <gfe:key-interest durable="false" receive-values="true" />
</client-region>

<gfe:pool id="pool" subcription-enabled="true">
  <gfe:locator host="${locator.host.1}" port="${locator.port.1}"/>
  <gfe:locator host="${locator.host.2}" port="${locator.port.2}"/>
</gfe:pool>

With subscriptions enabled, the client-region may register interest in all keys or specific keys. The subscription may be durable, meaning that the client-region is updated with any events that may have occurred while the client was offline. Also, it is possible to improve performance in some cases by suppressing transmission of values unless explicitly retrieved. In this case, new keys are visible, but the value must be retrieved explicitly with a region.get(key) call, for example.

Cache Server Configuration

Spring also allows you to create and initialize a cache server process simply by declaring the cache and region(s) along with an additional cache-server element to address server-side configuration. To start a cache server, simply configure it using the namespace and start the application context, as shown in Example 14-8.

Example 14-8. Bootstrapping a Spring application context

public static void main(String args[]) {
   new ClassPathXmlApplicationContext("cache-config.xml");
}

Figure 14-8 shows a Spring-configured cache server hosting two partitioned regions and one replicated region. The cache-server exposes many parameters to tune network communications, system resources, and the like.

Configuring a cache server

Figure 14-8. Configuring a cache server

WAN Configuration

WAN configuration is required for geographically distributed systems. For example, a global organization may need to share data across the London, Tokyo, and New York offices. Each location manages its transactions locally, but remote locations need to be synchronized. Since WAN communications can be very costly in terms of performance and reliability, GemFire queues events, processed by a WAN gateway to achieve eventual consistency. It is possible to control which events get synchronized to each remote location. It is also possible to tune the internal queue sizes, synchronization scheduling, persistent backup, and more. While a detailed discussion of GemFire’s WAN gateway architecture is beyond the scope of this book, it is important to note that WAN synchronization must be enabled at the region level. See Example 14-9 for a sample configuration.

Example 14-9. GemFire WAN configuration

<gfe:replicated-region id="region-with-gateway" enable-gateway="true" hub-id="gateway-hub" />

<gfe:gateway-hub id="gateway-hub" manual-start="true">
  <gfe:gateway gateway-id="gateway">
    <gfe:gateway-listener>
       <bean class="..."/>
    </gfe:gateway-listener>
    <gfe:gateway-queue maximum-queue-memory="5" batch-size="3" batch-time-interval="10" />
  </gfe:gateway>
      
  <gfe:gateway gateway-id="gateway2">
    <gfe:gateway-endpoint port="1234" host="host1" endpoint-id="endpoint1" />
    <gfe:gateway-endpoint port="2345" host="host2" endpoint-id="endpoint2" />
  </gfe:gateway>
</gfe:gateway-hub>

This example shows a region enabled for WAN communications using the APIs available in GemFire 6 versions. The enable-gateway attribute must be set to true (or is implied by the presence of the hub-id attribute), and the hub-id must reference a gateway-hub element. Here we see the gateway-hub configured with two gateways. The first has an optional GatewayListener to handle gateway events and configures the gateway queue. The second defines two remote gateway endpoints.

Note

The WAN architecture will be revamped in the upcoming GemFire 7.0 release. This will include new features and APIs, and will generally change the way gateways are configured. Spring Data GemFire is planning a concurrent release that will support all new features introduced in GemFire 7.0. The current WAN architecture will be deprecated.

Disk Store Configuration

GemFire allows you to configure disk stores for persistent backup of regions, disk overflow for evicted cache entries, WAN gateways, and more. Because a disk store may serve multiple purposes, it is defined as a top-level element in the namespace and may be referenced by components that use it. Disk writes may be synchronous or asynchronous.

For asynchronous writes, entries are held in a queue, which is also configurable. Other options control scheduling (e.g., the maximum time that can elapse before a disk write is performed or the maximum file size in megabytes). In Example 14-10, an overflow disk store is configured to store evicted entries. For asynchronous writes, it will store up to 50 entries in a queue, which will be flushed every 10 seconds or if the queue is at capacity. The region is configured for eviction to occur if the total memory size exceeds 2 GB. A custom ObjectSizer is used to estimate memory allocated per entry.

Example 14-10. Disk store configuration

<gfe:partitioned-region id="partition-data" persistent="true" disk-store-ref="ds2">
  <gfe:eviction type="MEMORY_SIZE" threshold="2048" action="LOCAL_DESTROY">
  <gfe:object-sizer>
    <bean class="org.springframework.data.gemfire.SimpleObjectSizer" />
  </gfe:object-sizer>
  </gfe:eviction>
</gfe:partitioned-region>
    
<gfe:disk-store id="ds2" queue-size="50" auto-compact="true" 
     max-oplog-size="10" time-interval="10000">
  <gfe:disk-dir location="/gemfire/diskstore" />
</gfe:disk-store>

Data Access with GemfireTemplate

Spring Data GemFire provides a template class for data access, similar to the JdbcTemplate or JmsTemplate. The GemfireTemplate wraps a single region and provides simple data access and query methods as well as a callback interface to access region operations. One of the key reasons to use the GemfireTemplate is that it performs exception translation from GemFire checked exceptions to Spring’s PersistenceException runtime exception hierarchy. This simplifies exception handling required by the native Region API and allows the template to work more seamlessly with the Spring declarative transactions using the GemfireTransactionManager which, like all Spring transaction managers, performs a rollback for runtime exceptions (but not checked exceptions) by default. Exception translation is also possible for @Repository components, and transactions will work with @Transactional methods that use the Region interface directly, but it will require a little more care.

Example 14-11 is a simple demonstration of a data access object wired with the GemfireTemplate. Notice the template.query() invocation backing findByLastName(...). Queries in GemFire use the Object Query Language (OQL). This method requires only a boolean predicate defining the query criteria. The body of the query, SELECT * from [region name] WHERE..., is assumed. The template also implements find(...) and findUnique(...) methods, which accept parameterized query strings and associated parameters and hide GemFire’s underlying QueryService API.

Example 14-11. Repository implementation using GemfireTemplate

@Repository
class GemfireCustomerRepository implements CustomerRepository {

  private final GemfireTemplate template;

  @Autowired
  public GemfireCustomerRepository(GemfireTemplate template) {
    Assert.notNull(template);
    this.template = template;
  }

  /**
   * Returns all objects in the region. Not advisable for very large datasets.
   */
  public List<Customer> findAll() {
    return new ArrayList<Customer>((Collection<? extends Customer>) 
template.getRegion().values());
  }

  public Customer save(Customer customer) {
    template.put(customer.getId(), customer);
    return customer;
  }

  public List<Customer> findByLastname(String lastname) {

    String queryString = "lastname = '" + lastname + "'";
    SelectResults<Customer> results = template.query(queryString);
    return results.asList();
  }

  public Customer findByEmailAddress(EmailAddress emailAddress) {

    String queryString = "emailAddress = ?1";
    return template.findUnique(queryString, emailAddress);
  }

  public void delete(Customer customer) {
    template.remove(customer.getId());
  }
}

We can configure the GemfireTemplate as a normal Spring bean, as shown in Example 14-12.

Example 14-12. GemfireTemplate configuration

<bean id="template" class="org.springframework.data.gemfire.GemfireTemplate">
  <property name="region" ref="Customer" />
</bean>

Repository Usage

The 1.2.0 release of Spring Data GemFire introduces basic support for Spring Data repositories backed by GemFire. All the core repository features described in Chapter 2 are supported, with the exception of paging and sorting. The sample code demonstrates these features.

POJO Mapping

Since GemFire regions require a unique key for each object, the top-level domain objects—Customer, Order, and Product—all inherit from AbstractPersistentEntity, which defines an id property (Example 14-13).

Example 14-13. AbstractPersistentEntity domain class

import org.springframework.data.annotation.Id;
 
public class AbstractPersistentEntity {
  
  @Id
  private final Long id; 
}

Each domain object is annotated with @Region. By convention, the region name is the same as the simple class name; however, we can override this by setting the annotation value to the desired region name. This must correspond to the region name—that is, the value of id attribute or the name attribute, if provided, of the region element. Common attributes, such as @PersistenceConstructor (shown in Example 14-14) and @Transient, work as expected.

Example 14-14. Product domain class

@Region
public class Product extends AbstractPersistentEntity {

  private String name, description;
  private BigDecimal price;
  private Map<String, String> attributes = new HashMap<String, String>();

  @PersistenceConstructor
  public Product(Long id, String name, BigDecimal price, String description) {
    
    super(id);
    Assert.hasText(name, "Name must not be null or empty!");
    Assert.isTrue(BigDecimal.ZERO.compareTo(price) < 0, "Price must be greater than zero!");

    this.name = name;
    this.price = price;
    this.description = description;
}

Creating a Repository

GemFire repositories support basic CRUD and query operations, which we define using Spring Data’s common method name query mapping mechanism. In addition, we can configure a repository method to execute any OQL query using @Query, as shown in Example 14-15.

Example 14-15. ProductRepository interface

public interface ProductRepository extends CrudRepository<Product, Long> {

  List<Product> findByDescriptionContaining(String description);

  /**
   * Returns all {@link Product}s having the given attribute value.
   * @param attribute
   * @param value
   * @return
   */
  @Query("SELECT * FROM /Product where attributes[$1] = $2")
  List<Product> findByAttributes(String key, String value);

  List<Product> findByName(String name);
}

You can enable repository discovery using a dedicated gfe-data namespace, which is separate from the core gfe namespace. Alternatively, if you’re using Java configuration, simply annotate your configuration class with @EnableGemfireRepositories, as shown in Example 14-16.

Example 14-16. Enabling GemFire repositories using XML

<gfe-data:repositories base-package="com.oreilly.springdata.gemfire" />

PDX Serialization

PDX is GemFire’s proprietary serialization library. It is highly efficient, configurable, interoperable with GemFire client applications written in C# or C++, and supports object versioning. In general, objects must be serialized for operations requiring network transport and disk persistence. Cache entries, if already serialized, are stored in serialized form. This is generally true with a distributed topology. A standalone cache with no persistent backup generally does not perform serialization.

If PDX is not enabled, Java serialization will be used. In this case, your domain classes and all nontransient properties must implement java.io.Serializable. This is not a requirement for PDX. Additionally, PDX is highly configurable and may be customized to optimize or enhance serialization to satisfy your application requirements.

Example 14-17 shows how to set up a GemFire repository to use PDX.

Example 14-17. Configuring a MappingPdxSerializer

<gfe:cache pdx-serializer="mapping-pdx-serializer" />

<bean id="mapping-pdx-serializer" 
      class="org.springframework.data.gemfire.mapping.MappingPdxSerializer" />

The MappingPdxSerializer is automatically wired with the default mapping context used by the repositories. One limitation to note is that each cache instance can have only one PDX serializer, so if you’re using PDX for repositories, it is advisable to set up a dedicated cache node (i.e., don’t use the same process to host nonrepository regions).

Continuous Query Support

A very powerful feature of GemFire is its support for continuous queries (CQ), which provides a query-driven event notification capability. In traditional distributed applications, data consumers that depend on updates made by other processes in near-real time have to implement some type of polling scheme. This is not particularly efficient or scalable. Alternatively, using a publish-subscribe messaging system, the application, upon receiving an event, typically has to access related data stored in a disk-based data store. Continuous queries provide an extremely efficient alternative. Using CQ, the client application registers a query that is executed periodically on cache servers. The client also provides a callback that gets invoked whenever a region event affects the state of the query’s result set. Note that CQ requires a client/server configuration.

Spring Data GemFire provides a ContinuousQueryListenerContainer, which supports a programming model based on Spring’s DefaultMessageListenerContainer for JMS-message-driven POJOs. To configure CQ, create a CQLC using the namespace, and register a listener for each continuous query (Example 14-18). Notice that the pool must have subscription-enabled set to true, as CQ uses GemFire’s subscription mechanism.

Example 14-18. Configuring a ContinuousQueryListenerContainer

<gfe:client-cache pool-name="client-pool" />

<gfe:pool id="client-pool" subscription-enabled="true">
  <gfe:server host="localhost" port="40404" />
</gfe:pool>
     
<gfe:client-region id="Person" pool-name="client-pool" />
  
<gfe:cq-listener-container>
  <gfe:listener ref="cqListener" query="select * from /Person" />
</gfe:cq-listener-container> 
    
<bean id="cqListener" class="org.springframework.data.gemfire.examples.CQListener" />

Now, implement the listener as shown in Example 14-19.

Example 14-19. Continuous query listener implementation

public class CQListener {
  
  private static Log log = LogFactory.getLog(CQListener.class);
   
  public void handleEvent(CqEvent event) {
     log.info("Received event " + event);
  }
}

The handleEvent() method will be invoked whenever any process makes a change in the range of the query. Notice that CQListener does not need to implement any interface, nor is there anything special about the method name. The continuous query container is smart enough to automatically invoke a method that has a single CqEvent parameter. If there is more than one, declare the method name in the listener configuration.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.179.220