Interacting with the Space


In this part of the tutorial we will demonstrate how to create a space and how you can interact with it. We will also demonstrate how you can improve your space search performance by using indexes and returning partial results.

Creating a Space

Let’s create a space called ‘xapTutorialSpace’ that is co-located within an application. This type of space is called embedded space.

Here is an example how you start an embedded space:

// Create the Space
GigaSpace gigaSpace = new GigaSpaceConfigurer(new EmbeddedSpaceConfigurer("xapTutorialSpace")).gigaSpace();

This space we just created can also be accessed remotely from another JVM by connecting with a SpaceProxyConfigurer. In order to do so you would use the following code:

GigaSpace gigaSpace = new GigaSpaceConfigurer(new SpaceProxyConfigurer("xapTutorialSpace")).gigaSpace();

You can configure the Space connection with several options.

When a client connects to a space, a proxy is created that holds a connection which implements the space API. All client interaction is performed through this proxy.

With XAP you can also create a Local Cache and a Local View.

  • Local Cache : This client side cache maintains any object used by the application. The cache data is loaded on demand (lazily), based on the client application’s read operations.
  • Local View : This client side cache maintains a specific subset of the data. The subset is predefined by the user. The cache is populated when the client application is started. In both cases, updates are performed (objects are added/updated/removed) on the master space, the master space then propagates the changes to all relevant local views and caches.

These two scenarios are only applicable for remote clients.

The Space Object

XAP supports two types of objects that can interact with the Space, POJOs and Documents.

POJO

Any POJO can be used to interact with the space as long it follows the Java Beans convention. The POJO needs to implement a default constructor, setters and getters for every property you want to store in the Space.

@SpaceClass
public class User {
  private Long id;
  private String name;
  private Double balance;
  private Double creditLimit;
  private EAccountStatus status;
  private Address address;
  private String[] comment;
  private Map<String, String> contacts;
  private List<Group> groups;
  private List<Integer> ratings;

  public User() {
  }

  @SpaceRouting
  @SpaceId(autoGenerate = false)
  public Long getId() {
	return id;
  }
}

The SpaceId

The space generates a unique identifier (UID) for every object in one of the following ways:

  • When a space object has no SpaceId attribute declared, the space auto-generates a UID for the object.
  • When a space object has an attribute which is declared as SpaceId and marked as auto-generate=false, the UID is generated based on the value of the ID attribute the user is setting.
  • When a space object has an attribute which is declared as SpaceId and marked as auto-generate=true, the UID is generated by the space and placed back into the attribute using the relevant setter method. In this case, the attribute must be of java.lang.String type.

Compound SpaceId

You might need to construct a space id that will be comprised from a user defined class rather than using a Numeric or String type field. In such a case your user defined class used as the SpaceId data type must implement the toString , hashCode and equals methods. The compound ID class must implement a toString method that return a unique String for each ID.

Defining Routing

Partitioning is used when the total number of objects is too big to be stored in a single space. In this case we will divide the data into several partitions. By designating an attribute on the space class as a partitioning key, the space proxy will then know to which partition a particular instance of the space class belongs to. The space proxy uses the entry’s routing attribute hash code to determine the corresponding partition for it.

The routing attribute can be explicitly set using the @SpaceRouting annotation for POJO entries or via the SpaceTypeDescriptorBuilder for document entries. If the routing attribute is not explicitly set, the space id attribute is used for routing. If the space id attribute is not defined, the first indexed attribute (alphabetically) is used for routing, otherwise the first attribute (alphabetically) is used for routing.

Space Document

The GigaSpaces document API exposes the space as Document Store. A document, which is represented by the class SpaceDocument, is essentially a collection of key-value pairs, where the keys are strings and the values are primitives, String, Date, other documents, or collections thereof. Unlike POJOs, which force users to design a fixed data schema (in the form of a class definition) and adhere to it, a document is much more dynamic, users can add and remove properties at runtime as necessary. A Document always belongs to a certain type, represented by the class SpaceTypeDescriptor.

To create a document we use a Map for its properties. The SpaceDocument object is instantiated by using the type name and properties. XAP provides a special implementation of a Hash Map called DocumentProperties that provides a fluent API.

Here is an example how you can create a SpaceDocument:

public SpaceDocument createDocumemt() {
     DocumentProperties properties = new DocumentProperties()
       .setProperty("CatalogNumber", "av-9876")
       .setProperty("Category", "Aviation")
       .setProperty("Name", "Jet Propelled Pogo Stick")
       .setProperty("Price", 19.99f)
       .setProperty("Tags",
            new String[] { "New", "Cool", "Pogo", "Jet" })
       .setProperty("Features",
            new DocumentProperties()
              .setProperty("Manufacturer", "Acme")
              .setProperty("RequiresAssembly", true)
              .setProperty("NumberOfParts", 42));

       return new SpaceDocument("Product", properties);
}

In order to use the SpaceDocument, we need to register its schema first with the space:

public void registerProductType(GigaSpace space) {
     // Create type descriptor:
     SpaceTypeDescriptor typeDescriptor = new SpaceTypeDescriptorBuilder(
		"Product").idProperty("CatalogNumber")
		.routingProperty("Category")
		.addPropertyIndex("Name", SpaceIndexType.BASIC)
		.addPropertyIndex("Price", SpaceIndexType.EXTENDED).create();
     // Register type:
     space.getTypeManager().registerTypeDescriptor(typeDescriptor);
}

Only properties with special roles like ID and Routing are part of the schema definition. These meta model settings cannot be changed without restarting the space or dropping the type, clearing all its instances and reintroducing it again.

It is possible to write a POJO to the space and read it back as a document, and vice versa. This scenario is useful when you want to read or modify POJO objects without loading the concrete java classes.

Interacting with the Space

All space operations are relevant to both the POJO and Document.

Writing an object to space:

When writing an object to the space, the object is created in space if it does not exist. If it already exists in space it will be updated. This is the default behavior of the write operation.

public void writeUser() {
     User user = new User();
     user.setId(new Long(1));
     user.setName("John Smith");
     user.setStatus(EAccountStatus.ACTIVE);

     // Write the user to the space
     space.write(user);
}

It is also possible to write multiple objects in one operation to the space (batch mode). This can vastly improve the performance if you need to load many objects at once into the space.

Here is an example on how you write multiple objects to the space:

public void writeUsers() {
     User[] users = new User[2];
     users[0] = new User();
     users[0].setId(new Long(1));
     users[0].setName("John Doe");
     users[0].setStatus(EAccountStatus.ACTIVE);

     users[1] = new User();
     users[1].setId(new Long(2));
     users[1].setName("John Doe");
     users[1].setStatus(EAccountStatus.ACTIVE);

     space.writeMultiple(users);
}

There are several options to override the default behavior of the write operation. You can change the lifetime of an object by supplying a LEASE to the operation. You can also change the modifier on the operation to change the behavior.

Here is an example:

public void writeOnlyWithLease() {
     User user = new User();
     user.setId(new Long(1));
     user.setName("John Smith");
     user.setStatus(EAccountStatus.ACTIVE);
     space.write(user, 0, 10000, WriteModifiers.WRITE_ONLY);
}

In this example, we are writing an object to the space with zero delay, 10 seconds to live and write only if the object does not already exist in the space. If the object already exists, an exception will be thrown.

Updating an object in space

When you want to update only a couple of attributes on an object in space, you can use the change operation and update specific fields or even nested fields or modify collections and maps without having to supply the entire collection or map for the operation. With the following change operation example it is not necessary to read the object first from the space to update it. The Change API reduces a normal two step operation to a one step operation. This operation can vastly improve performance when you have an object with many attributes and you only need to update one or a couple of attributes.

public void ChangeSet() {
      User user = new User();
      user.setId(new Long(1));
      user.setName("John Doe");
      user.setStatus(EAccountStatus.ACTIVE);
      space.write(user);

      IdQuery<User> idQuery = new IdQuery<User>(User.class, new Long(1));
      ChangeResult<User> changeResult = space.change(idQuery,
		new ChangeSet().set("status", EAccountStatus.BLOCKED));

     if (changeResult.getNumberOfChangedEntries() == 0) {
	    System.out.println("Entry does not exist");
     }
}

There are several other change operations available; ‘increment’, ‘decrement’, ‘addToCollection’, ‘removeFromCollection’ etc.

See also:

The Change API

Querying the Space

Now we are ready to query the space. XAP provides several ways to perform queries against the space:

  • Query by ID
  • Query by Template
  • Query by SQL

Query by ID

This is the simplest and fasted way to retrieve objects from the space.

Here is an example of a query by id:

public User findUserById() {
     return space.readById(User.class, new Long(1));
}

You can also perform a bulk read for multiple Id’s

public User[] findUsersByIds() {
     ReadByIdsResult<User> result = space.readByIds(User.class, new Long[] {1l, 2l, 3l });
     return result.getResultsArray();
}

Query by Template

Template matching (match by example) is a simple way to query the space. The template is a POJO of the desired entry type, and the attributes which are set on the template (i.e. not null) are matched against the respective attributes of entries of the same type in the space. Attributes with null values are ignored (not matched).

The following examples assume the default constructor of the User class initializes all its attributes to null.

Read an entry of type User where the name is ‘John Doe’:

public User findUserByTemplate() {
     User user = new User();
     user.setName("John Doe");
     return space.read(user);
}

You can also perform a bulk read with templates. In the example below will read all users that have a status of ACTIVE:

public User[] findUsersByTemplate() {
     User user = new User();
     user.setStatus(EAccountStatus.ACTIVE);
     return space.readMultiple(user);
}
Template Matching support inheritance relationships, so that entries of a sub-class are visible in the context of the super class, but not the other way around.

SQL Query

The SQLQuery class is used to query the space with an SQL-like syntax. The query statement includes only the WHERE statement part. An SQLQuery is composed from the class of entry to query and an expression in SQL syntax.

public User[] sqlFindUsersByName() {
     SQLQuery<User> query = new SQLQuery<User>(User.class,"name = 'John Doe'");
     return space.readMultiple(query);
}

public User[] sqlFindUsersByNameAndCreditLimit() {
     SQLQuery<User> query = new SQLQuery<User>(User.class,"name = 'John Doe' AND creditLimit > 1000");
     return space.readMultiple(query);
}

public User[] sqlFindUsersByNameAndIds() {
     SQLQuery<User> query = new SQLQuery<User>(User.class,"name = 'John Doe' AND id IN(1L,3L,5L)");
     return space.readMultiple(query);
}
See also:

SQL Query

Parameterized Queries

You can separate the values for the SQL criteria expression by placing a ‘?’ symbol instead of the actual value in the expression. When executing the query, the conditions that includes ‘?’ are replaced with the corresponding parameter values supplied via the setParameter method.

For example:

public User[] sqlParameterFindUsersByName() {
     SQLQuery<User> query = new SQLQuery<User>(User.class, "name = ?").setParameter(1, "John Doe");
     return space.readMultiple(query);
}

public User[] sqlParameterFindUsersByNameAndCreditLimit() {
     SQLQuery<User> query = new SQLQuery<User>(User.class,"name = ? AND creditLimit > ?");
     query.setParameter(1, "John Doe");
     query.setParameter(2, new Double(1000));
     return space.readMultiple(query);
}

Nested property queries

Many times a class has embedded classes as attributes. You can query for attributes within the embedded classes. Matching a nested attribute is done by specifying a Path which describes how to obtain its value. For example, our user class has an embedded attribute of an Address that has a zipCode attribute.

Here is an example how you can query the space for all users that have a zip code of ‘12345’.

public User[] sqlFindUsersByZipCode() {
     SQLQuery<User> query = new SQLQuery<User>(User.class,"address.zipCode = 12345");
     return space.readMultiple(query);
}

Nested Collections

It is possible to query embedded collections. Our user class has a collection groups that he belongs to. We can query the space for all users that belong to a certain group:

public User[] findUsersByGroup() {
   SQLQuery<User> sqlQuery = new SQLQuery<User>(User.class,"groups[*].id = 1L");
   return space.readMultiple(sqlQuery);
}

There are several additional query options available. For example you can query Nested Maps by key,query with Regular Expression, Enum attributes and others.

Query returning partial results

In some cases when querying the space for objects only specific attributes of an objects are required and not the entire object (delta read). For that purpose the Projection API can be used where you can specify which attributes are of interest and the space will only populate these attributes with the actual data when the result is returned back to the user. This approach reduces network overhead and can vastly improve performance.

In this example below we are just interested in the name attribute of the user object:

public User[] findUsersByNameAndProjection() {
     SQLQuery<User> query = new SQLQuery<User>(User.class,"name = ?");
     query.setParameter(1, "John Doe");
     query.setProjections("name");

     return space.readMultiple(query);
}

Document Queries

You can also query the space for documents. Just like the POJO queries, you can use query by ID, template and SQLQuery.

Here are some examples how you can query the space for documents:

public SpaceDocument readProductById(GigaSpace gigaSpace) {
    return gigaSpace.readById(new IdQuery<SpaceDocument>("Product", "hw-1234"));
}

public SpaceDocument readProductByTemplate() {
     SpaceDocument template = new SpaceDocument("Product");
     template.setProperty("Name", "Jet Propelled Pogo Stick");
     return space.read(template);
}

public SpaceDocument[] readProductsBySQL() {
      SQLQuery<SpaceDocument> query = new SQLQuery<SpaceDocument>("Product","Price > ?");
      query.setParameter(1, 19.99f);
      return space.readMultiple(query);
}
See also:

The Document API

Geospatial Query

Spatial queries make use of geometry data types such as points, circles and polygons and these queries consider the spatial relationship between these geometries.

Suppose we want to write an application to locate nearby gas stations. First, we create a GasStation class which includes the location and address of the gas station:

And here is the corresponding java class:

import org.openspaces.spatial.shapes.Point;

import com.gigaspaces.annotation.pojo.SpaceClass;
import com.gigaspaces.annotation.pojo.SpaceId;

@SpaceClass
public class GasStation {

	private Long id;

	private Point location;

	public Point getLocation() {
		return location;
	}

	public void setLocation(Point location) {
		this.location = location;
	}
	
	@SpaceId
	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}
}

Let’s write some gas stations into the Space:

    GigaSpace gigaSpace = new GigaSpaceConfigurer(new EmbeddedSpaceConfigurer("geoSpace")).gigaSpace();

    GasStation gs = new GasStation();
    gs.setId(new Long(1));
    gs.setLocation(ShapeFactory.point(10.0d, 10.0d));

    gigaSpace.write(gs);		

Next, we can query for a gas station within a certain radius of our location:

    Point p = ShapeFactory.point(7.5d, 7.5d);

    SQLQuery<GasStation> query = new SQLQuery<GasStation>(GasStation.class, "location spatial:within ?")
				.setParameter(1, ShapeFactory.circle(p, 4.5d));
    GasStation station = gigaSpace.read(query);

    if (station != null) {
        System.out.println("Found a GasStation :" + station);
    }
}

Shapes

XAP supports the following shapes:

Shape Description
Point A point, denoted by X and Y coordinates.
LineString A finite sequence of one or more consecutive line segments.
Circle A circle, denoted by a point and a radius.
Rectangle A rectangle aligned with the axis (for non-aligned rectangles use Polygon).
Polygon A finite sequence of consecutive line segments which denotes a bounded area.

Queries

Spatial queries are available through the spatial: extension to the SQL query syntax. The following operations are supported:

Query Description
shape1 spatial:intersects shape2 The intersection between shape1 and shape 2 is not empty (i.e. some or all of shape1 overlaps some or all of shape2).
shape1 spatial:within shape2 shape1 is within (contained in) shape2, boundaries inclusive.
shape1 spatial:contains shape2 shape1 contains shape2, boundaries inclusive.
See also:

Geospatial Queries

Removing Objects from Space

To remove objects from a space you can use the take or the clear operation.

Take operation

The take operation returns an object and removes it from the space. XAP provides several options for the take operation:

  • Take by ID
  • Take by template
  • Take by SQLQuery
  • Take multiple

Here are some examples:

public User takeUserById() {
   return space.takeById(User.class, 1L);
}

public User takeUserByTemplate() {
   User template = new User();
   template.setName("John Doe");
   return space.take(template);
}

public User[] takeUsersBySQL() {
     SQLQuery<User> query = new SQLQuery<User>(User.class,"status = ?");
     query.setParameter(1, EAccountStatus.BLOCKED);
   return space.takeMultiple(query);
}

Clear operation

The clear operation removes objects from a space without returning them. Here are some examples:

public void clearUserByTemplate() {
     User template = new User();
     space.clear(template);
}

public void clearUserBySQL() {
     SQLQuery<User> query = new SQLQuery<User>(User.class, "name = ?");
     query.setParameter(1, "John Doe");
     space.clear(query);
}

// clear all objects in space
public void clearAllObjectInSpace() {
     space.clear(null);
}

Aggregation

The Aggregators allow you to perform the entire aggregation activity at the space side avoiding any data retrieval back to the client side. Only the result of each aggregation activity performed with each partition is returned back to the client side where all the results are reduced and returned to the client application.

import static org.openspaces.extensions.QueryExtension.*;
...
SQLQuery<Employee> query = new SQLQuery<Employee>(Employee.class,"country=? OR country=? ");
query.setParameter(1, "UK");
query.setParameter(2, "U.S.A");

// retrieve the maximum value stored in the field "age"
Number maxAgeInSpace = max(space, query, "age");
/// retrieve the minimum value stored in the field "age"
Number minAgeInSpace = min(space, query, "age");
// Sum the "age" field on all space objects.
Number combinedAgeInSpace = sum(space, query, "age");
// Sum's the "age" field on all space objects then divides by the number of space objects.
Double averageAge = average(space, query, "age");
// Retrieve the space object with the highest value for the field "age".
Person oldestPersonInSpace = maxEntry(space, query, "age");
/// Retrieve the space object with the lowest value for the field "age".
Person youngestPersonInSpace = minEntry(space, query, "age");

XAP also supports, Compound, Embedded Fields and Group Aggregation.

See also:

Aggregators

Indexing

To improve performance, it is possible to index one or more attributes for an object. The space maintains additional data for indexed attributes, which shortens the time required to determine a match resulting in improved performance. However, indexes consume more resources and impacts the write operations performance.

Inheritance

By default, an attribute’s index is inherited in sub classes (i.e. if an attribute is indexed in a super class, it is also indexed in a sub class). If you need to change the index type of an attribute in a subclass you can override the attribute and annotate it with @SpaceIndex using the requested index type (to disable indexing use NONE).

Basic Index

There are two basic index types provide:

  • BASIC index - this speeds up equality matching (equal to/not equal to).
  • EXTENDED index - this speeds up comparison matching (bigger than/less than).

Here is an example how you can define indexes:

@SpaceClass
public class User {

	private Long id;
	private String name;
	private Double balance;
	private Double creditLimit;
	private EAccountStatus status;
	private Address address;
	private Map<String, String> contacts;

	public User() {
	}

	@SpaceId(autoGenerate = false)
	@SpaceRouting
	public Long getId() {
		return id;
	}

	@SpaceIndex(type = SpaceIndexType.BASIC)
	public String getName() {
		return name;
	}

	@SpaceIndex(type = SpaceIndexType.EXTENDED)
	public Double getCreditLimit() {
		return creditLimit;
	}
}

Compound Indexing

A Compound Index is a space index composed from several attributes or nested attributes. Each attribute of a compound index is called a segment and each segment is described by its path.

Here is an example of a compound index:

@CompoundSpaceIndexes({ @CompoundSpaceIndex(paths = { "name", "creditLimit" }) })
@SpaceClass
public class User {
     private Long id;
     private String name;
     private Double balance;
     private Double creditLimit;
     private EAccountStatus status;
     private Address address;
     private String[] comment;
}

// Here is a query that will use this index
SQLQuery<User> query = new SQLQuery<User>(User.class,"name = 'John Doe' AND creditLimit > 1000");

There are several additional indexing options available. For example you can index nested attributes, Nested Maps, Collections, nested attributes within a Collection, free text search and others.

See also:

Indexing

Best Practice

When you code your space classes make sure:
  • there are indexes for all relevant attributes including nested attributes you use for queries

  • numeric attribute queried with between / greater / less than should have an extended index.

  • compound indexes should be used for attributes queried using AND query

  • space classes have empty no arg constructor

  • all nested classes are serializable

  • do not use int, long, etc. integer attributes, instead use Long.

  • when possible use writeMultiple.

  • use projection for read/readMultiple

  • use clear for data removal and not take or takeMultiple

  • no huge collections with many items

  • use change api instead of update , especially if collections are used.

Other Data Access API’s

XAP provides a JDBC Driver, JPA API, MAP and Memcached API’s.

Spring Integration

All XAP components can be wired and configured with the application using corresponding Spring Beans.

The GigaSpaces Spring Integration supports:
  • Spring Automatic Transaction Demarcation
  • Spring Data
  • Spring JMS
  • Spring JPA
  • Spring Hibernate
  • Spring Remoting
  • String Batch
  • Spring Security
  • Mule

Lets look at a Spring configuration file that represents the creation of an embedded space:

<?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:context="http://www.springframework.org/schema/context"
	xmlns:os-core="http://www.openspaces.org/schema/core" xmlns:os-events="http://www.openspaces.org/schema/events"
	xmlns:os-remoting="http://www.openspaces.org/schema/remoting"
	xmlns:os-sla="http://www.openspaces.org/schema/sla"
	xsi:schemaLocation="
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
   http://www.openspaces.org/schema/core http://www.openspaces.org/schema/12.2
/core/openspaces-core.xsd">

	<!-- Scan the packages for annotations / -->
	<context:component-scan base-package="xap" />

	<!-- Enables to configure Spring beans through annotations -->
	<context:annotation-config />

	<!-- A bean representing a space (an IJSpace implementation) -->
	<os-core:embedded-space id="space" space-name="tutorialSpace" />

	<!-- Define the GigaSpace instance that the application will use to access the space -->
	<os-core:giga-space id="xapTutorialSpace" space="space"/>
</beans>

And here is the code to access the Spring bean within your application:

public void findSpace()  {
    FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext(
	"classpath:/spring/application-context.xml");

    GigaSpace space = (GigaSpace) context.getBean("xapTutorialSpace");
}
In the following parts of this tutorial we will introduce you the different schemas that support the XAP Spring integration. We will also use the Annotations to configure and inject components.