XAP

Using Spring Data with GigaSpaces

This topic describes how to use Spring Data with GigaSpaces. It explains how to define query methods; you can use a specific query syntax as the method name, and the GigaSpaces repository proxy derives these methods into GigaSpaces queries.

For a full explanation of this mechanism, see the Spring Data Reference documentation.

After covering the basic usage of Spring Data with GigaSpaces, more advanced features are also described, such as Projection API, Change API, transactions, and Document storage.

Basic Usage

Defining Query Methods

The following code snippet is an example of a repository declaration with different methods:

@SpaceDocumentName(PersonDocument.TYPE_NAME)
public interface PersonDocumentRepository extends GigaspacesDocumentRepository<PersonDocument, String> {
    // you can define simple queries
    @Query("name = ?")
    List<PersonDocument> findByName(String name);
   
    // you can build complex queries
    @Query("name = ? and age = ?")
    List<PersonDocument> findByNameAndAge(String name, Integer age);
    // you can query embedded properties
    @Query("spouse.name = ?")
    List<PersonDocument> findBySpouseName(String name);
    // you can query any properties, even if they are not present in you wrapper
    @Query("customField = ?")
    List<PersonDocument> findByCustomField(String value);
    // you can perform sorting using SQLQuery syntax
    @Query("age = ? order by id asc")
    List<PersonDocument> findByAgeSortedById(Integer age);
}

As you can see, different keywords can be used and combined to create desired conditions.

For a complete list of supported keywords, see Spring Data GigaSpaces Appendix.

The process of deriving query methods into GigaSpaces queries depends in large part on the query lookup strategy you choose for the repository. Spring Data GigaSpaces supports all common strategies.

The default strategy enables both deriving queries from method names and overriding them with custom defined queries. There are several ways to specify custom queries for a method.

@Query Annotation

The following example shows how to implement the @Query annotation on the method:

public interface PersonRepository extends GigaspacesDocumentRepository<Person, String> {
   @Query("name = ? order by name asc")
   List<Person> findByNameOrdered(String name);
}

The syntax used for @Query is similar to SQL queries.

For a full list of SQL query features, see the SQL Query API topic in the documentation website.

Importing Named Queries from an External Resource

You can import named queries from an external resource, For example, if you have a named-queries.properties file in the classpath with the following content:

Person.findByNameOrdered=name = ? order by name asc

The query strings defined in the file are applied to methods with same names in PersonRepository if you target named-queries.properties in the configuration. In an XML-based configuration, the named-queries-location attribute can be used as shown in the following example:

<?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:gigaspaces-data="http://www.springframework.org/schema/data/gigaspaces"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:os-core="http://www.openspaces.org/schema/core"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/data/gigaspaces http://www.springframework.org/schema/data/gigaspaces/spring-data-gigaspaces-15.0.xsd
          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
          http://www.openspaces.org/schema/core http://www.openspaces.org/schema/15.0/core/openspaces-core.xsd">
   <gigaspaces-data:repositories base-package="com.yourcompany.foo.bar" named-queries-location="classpath:named-queries.properties"/>
   <!-- other configuration omitted -->
</beans>

Similarly, the namedQueriesLocation annotation field can be used in a Java-based configuration, as shown here:

@Configuration
@EnableGigaspacesRepositories(value = "com.yourcompany.foo.bar", namedQueriesLocation = "classpath:named-queries.properties")
public class ContextConfiguration { // bean definitions omitted
}

Custom Methods

Custom methods can be added to repository interfaces. Spring Data allows you to provide custom repository code and still utilize basic CRUDClosed Create, Read, Update, Delete. These terms describe the four essential operations for creating and managing persistent data elements, mainly in relational and NoSQL databases. features and query method functionality. To extend your repository, first define a separate interface with custom methods:

public interface PersonRepositoryCustom {
String customMethod();
}
Then you add an implementation for the defined interface: 
java 
public class PersonRepositoryCustomImpl implements PersonRepositoryCustom { 
public String customMethod() {
// your custom implementation
}
}

Spring Data recognizes an Impl suffix by default to look for custom method implementations.

The implementation itself doesn't depend on Spring Data, so you can inject other beans or property values using standard dependency injection. For example, you can inject GigaSpaces and use it directly in your custom methods.

Next, apply the interface with custom methods to your repository declaration as shown below:

public interface PersonRepository extends GigaspacesRepository<Person, String>, PersonRepositoryCustom {
// query methods declarations are omitted
}

This combines basic CRUD methods and your custom functionality, and makes it available to the client application.

Spring Data looks for implementations of custom methods among all classes located under the base-package attribute in XML or basePackages in Java configuration. It searches for <custom interface name><suffix> classes, where the suffix is Impl by default. If your project conventions tell you to use another suffix for the implementations, you can specify it using the following syntax.

For XML configuration, use the repository-impl-postfix attribute:

<gigaspaces-data:repositories
base-package="com.yourcompany.foo.bar"
repository-impl-postfix="FooBar"/>

For Java configuration, use repositoryImplementationPostfix:

@Configuration
@EnableGigaspacesRepositories(value = "com.yourcompany.foo.bar", repositoryImplementationPostfix = "FooBar")
public class ContextConfiguration {
// bean definitions omitted
}

Another option is to manually put the implementation into the context and use a proper name for it.

In XML configuration it looks like this:

<gigaspaces-data:repositories base-package="com.yourcompany.foo.bar"/>
<bean id="personRepositoryImpl" class="...">
<!-- further configuration -->
</bean>

And similarly in Java-based configuration:

@Configuration
@EnableGigaspacesRepositories("com.yourcompany.foo.bar")
public class ContextConfiguration {
@Bean
public AnyClassHere personRepositoryImpl() {
// further configuration
}
// other bean definitions omitted
}

Advanced Usage

This section describes some of the more advanced features available through Spring Data GigaSpaces.

Projection API

Spring Data GigaSpaces supports Projection, which allows reading only certain properties for the objects (delta read). This approach reduces network overhead, memory consumption and CPU overhead due to decreased serialization time.

The GigaspacesRepository interface provides basic find methods extended with the Projection argument. The following example demonstrates how the findOne method can be used to select only the name field from Person:

@Service
public class PersonServiceImpl implements PersonService {
@Autowired
private PersonRepository repository;
public List<String> getAllNames() {
Iterable<Person> personList = repository.findAll(Projection.projections("name"));
// result processing ommited
}
}

If you are using Querydsl support, you can apply projection using QueryDslProjection. This approach helps prevent runtime errors when the POJOClosed Plain Old Java Object. A regular Java object with no special restrictions other than those forced by the Java Language Specification and does not require any classpath. field is renamed but the projection fields aren't, because they are just strings.

You can also supply your query methods with Projection by adding an additional argument to the method declaration:

public interface PersonRepository extends GigaspacesRepository<Person, String> {
List<Person> findByName(String name, Projection projection);
}

For more information about projection, see the Projection topic in the Querying the Space Using GigaSpaces API section of the documentation website.

Querydsl Support

The Querydsl framework lets you write type-safe queries in Java instead of using query strings. This provides several advantages, such as code completion in your IDEClosed Integrated Development Environment. A software application that helps programmers develop software code efficiently. It increases developer productivity by combining capabilities such as software editing, building, testing, and packaging in an easy-to-use application. Example: DBeaver., and accessing domain types and properties in a type-safe manner (which reduces the probability of query syntax errors during runtime).

For more information about Querydsl, see the Querydsl website.

You need to perform several steps in order to start using Querydsl support with the GigaSpaces repositories.

To start using Querydsl support:

  1. Use the repository as a GigaspacesQueryDslPredicateExecutor along with GigaspacesRepository:

    public interface PersonRepository extends GigaspacesRepository<Person, String>, GigaspacesQueryDslPredicateExecutor<Person> {
    }

    You define the type of data to be accessed with Querydsl.

  2. Add the source processor to your Maven build (the pom.xml file) using the Maven Annotation Processing Tool plugin. This configuration calls GigaspacespQueryDslAnnotationProcessor before compiling your project sources. It looks for POJOs marked with @SpaceClass annotation and generates Q... classes for them that allow you to build up Querydsl Predicates.

    <project>
    <build>
    <plugins>
    ...
    <plugin>
    <groupId>com.mysema.maven</groupId>
    <artifactId>apt-maven-plugin</artifactId>
    <version>1.1.3</version>
    <executions>
    <execution>
    <goals>
    <goal>process</goal>
    </goals>
    <configuration>
    <outputDirectory>target/generated-sources/java</outputDirectory>
    <processor>org.springframework.data.gigaspaces.querydsl.GigaspacesQueryDslAnnotationProcessor</processor>
    </configuration>
    </execution>
    </executions>
    </plugin>
    ...
    </plugins>
    </build>
    </project>
  3. Before using these classes, you have to call this processor with process-sources Maven goal, or just call install if you are already using it:

    mvn clean process-sources
    mvn clean install
  4. Query your repository using Querydsl Predicates:

    @Service
    public class PersonServiceImpl implements PersonService {
    @Autowired
    private PersonRepository repository;
    public Iterable<Person> getByAge(Integer minAge, Integer maxAge) {
    return repository.findAll(
    QPerson.person.name.isNotNull().and(QPerson.person.age.between(minAge, maxAge))
    );
    }
    }

For a full list of supported Predicate methods, see the Spring Data GigaSpaces Appendix.

Change API

Spring Data GigaSpaces supports the GigaSpaces Change API, which allows updating existing objects in a Space by specifying only the required change instead of passing the entire updated object. This reduces network traffic between the client and the Space. It also can prevent having to read the existing object prior to the change operation, because the change operation can specify how to change the existing property without knowing its current value.

There are two ways you can use Change API within the GigaSpaces repositories.

Calling the Native Change API

The first option is to call the native Change API by accessing space() in XGigaspacesRepository. For this option, the GigaSpace.change methods can be used along with ChangeSet class .

For a full explanation and code examples, see the Change API section of the documentation website.

Using the Change API with Qerysdl Syntax

The second option is to use GigaspacesQueryDslPredicateExecutor.change method built in Querydsl style. It accepts the QChangeSet argument, which is just a ChangeSet with Querydsl syntax:

@Service
public class PersonServiceImpl implements PersonService {
@Autowired
private PersonRepository repository;
public void increaseAgeByName(String name) {
repository.change(
QPerson.person.name.eq(name),
QChangeSet.changeSet().increment(QPerson.person.age, 1)
);
}
}

The Querydsl Support section above explains how to use APIs like the Change API with Querydsl syntax.

For a full list of supported Change API methods, see the Spring Data GigaSpaces Appendix.

Take Operations

Spring Data GigaSpaces supports take operations, which work in the same way as querying the Space, but returned objects are deleted from storage. With this approach there is no need for additional operations when you implement a pattern where consumers or workers are receiving tasks or messages.

A basic take operation can be performed using object IDs with take(...) methods in the GigaspacesRepository interface. More advanced querying is available in Querydsl style using the GigaspacesQueryDslPredicateExecutor interface, which accepts Predicate to retrieve one or multiple objects that match the query:

@Service
public class PersonServiceImpl implements PersonService {
@Autowired
private PersonRepository repository;
public Person takeByName(String name) {
return repository.takeOne(QPerson.person.name.eq(name));
}
}

Lease Time

Spring Data GigaSpaces supports defining a lease time for new objects in the repository, which limits the time an object lives in a Space. To use this feature, you can specify the lease time (in any time units) when saving with save(...) methods. These overloaded methods return a special LeaseContext object that allows you to track, renew and cancel the lease.

The idea behind a lease is fairly simple:

  • When creating a resource, the requestor creates the resource with a limited life span.
  • The grantor of the resource gives access for some period of time that is no longer than requested.
  • The period of time that is actually granted is returned to the requestor as part of the Lease object.
  • A holder of a lease can request that a Lease be renewed, or cancel the Lease at any time.
  • Successfully renewing a lease extends the time period during which the lease is in effect.
  • Canceling the lease drops the lease immediately.

For more information about leasing, see the Lease Time topic in the developer section of the documentation website.

Transactions

Spring Data GigaSpaces supports declarative Spring transactions based on OpenSpaces transaction managers. In order to apply transactional behavior, the transaction manager must be provided as a reference when constructing the GigaSpace bean. For example (using the distributed transaction manager):

<?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:gigaspaces-data="http://www.springframework.org/schema/data/gigaspaces"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:os-core="http://www.openspaces.org/schema/core" xmlns:tx="http://www.springframework.org/schema/tx"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/data/gigaspaces http://www.springframework.org/schema/data/gigaspaces/spring-data-gigaspaces.xsd
          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
          http://www.openspaces.org/schema/core http://www.openspaces.org/schema/15.0/core/openspaces-core.xsd
          http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

   <context:annotation-config/>
   <context:component-scan base-package="org.springframework.data.gigaspaces.examples.advanced.transaction"/>
   <tx:annotation-driven transaction-manager="transactionManager"/>

   <!-- Enables initialization for Gigaspaces Repositories:
       all interfaces that extend the GigaspacesRepository will be initialized in the context
       in this example only PersonRepository is initialized this way
   -->
   <gigaspaces-data:repositories base-package="org.springframework.data.gigaspaces.examples.advanced.transaction"/>

   <os-core:embedded-space id="space" name="space"/>

   <os-core:giga-space id="gigaSpace" space="space" tx-manager="transactionManager"/>

   <os-core:distributed-tx-manager id="transactionManager"/>

</beans>

Now your service layer methods can be marked as @Transactional:

@Service
public class PersonServiceImpl implements PersonService {
@Autowired
private PersonRepository personRepository;
@Transactional
public void transactionalMethod(Person person) {
...
}
}

For more information about transactions, see the Transactions section of the documentation website.

Document Storage Support

GigaSpaces's Document API exposes the Space as a Document Store. A document, which is represented by the class SpaceDocument, is a collection of key-value pairs, where the keys are strings and the values are primitives, String, Date, other documents, or collections thereof. Most importantly, the Space is aware of the internal structure of a document, and so can index document properties at any nesting level and expose rich query semantics for retrieving documents.

When using Spring Data GigaSpaces you can declare one or more of your repositories to be a Document Repository. To do so, you must first add a schema definition of the document type to the Space configuration in context:

<os-core:embedded-space id="space" name="space">
<os-core:space-type type-name="Person">
<os-core:id property="id"/>
<os-core:routingClosed The mechanism that is in charge of routing the objects into and out of the corresponding partitions. The routing is based on a designated attribute inside the objects that are written to the Space, called the Routing Index. property="age"/>
<os-core:basic-index path="name"/>
<os-core:extended-index path="birthday"/>
</os-core:space-type>
<!-- other document types declarations -->
</os-core:embedded-space>

Then, extend the GigaspacesDocumentRepository interface (instead of the usual GigaspacesRepository) and annotate it with @SpaceDocumentName to wire it to the document descriptor declared above:

@SpaceDocumentName("Person")
public interface PersonDocumentRepository extends GigaspacesDocumentRepository<SpaceDocument, String> {
}

If you don't mark your Document Repository with a @SpaceDocumentName annotation, context configuration will fail.

Now PersonDocumentRepository has basic CRUD operations available for SpaceDocument entities.

For more information about available document storage features, see the Document API topic on the documentation website.

While documents allow using a dynamic schema, they require give up Java's type-safety for working with typeless key-value pairs. Spring Data GigaSpaces supports extending the SpaceDocument class to provide a type-safe wrapper for documents that is much easier to code with, while maintaining the dynamic schema. For example, you can declare a PersonDocument wrapper:

public class PersonDocument extends SpaceDocument {
public static final String TYPE_NAME = "Person";
public static final String PROPERTY_ID = "id";
public static final String PROPERTY_AGE = "age";
// other properties omitted
public PersonDocument() {
super(TYPE_NAME);
}
public String getId() {
return super.getProperty(PROPERTY_ID);
}
public PersonDocument setId(String id) {
super.setProperty(PROPERTY_ID, id);
return this;
}
public Integer getAge() {
return super.getProperty(PROPERTY_AGE);
}
public PersonDocument setAge(Integer age) {
super.setProperty(PROPERTY_AGE, age);
return this;
}
// other properties accessors are omitted
}

Wrapper classes must have a parameter-less constructor.

To work with objects of a PersonDocument class instead of SpaceDocument, Space configuration must contain the declaration of the wrapper class

<os-core:embedded-space id="space" name="space">
<os-core:space-type type-name="Person">
<os-core:id property="id"/>
<os-core:routing property="age"/>
<os-core:basic-index path="name"/>
<os-core:extended-index path="birthday"/>
<os-core:document-class>com.yourcompany.foo.bar.PersonDocument</os-core:document-class>
</os-core:space-type>
<!-- other document types declarations -->
</os-core:embedded-space>

Now you can declare your Document Repository with the following syntax:

@SpaceDocumentName(PersonDocument.TYPE_NAME)
public interface PersonDocumentRepository extends GigaspacesDocumentRepository<PersonDocument, String> {
}

The domain class of PersonDocumentRepository is now set to PersonDocument instead of SpaceDocument. Additionally, the type name for PersonDocument is reused in the @SpaceDocumentName annotation for the repository.

For more information about Space Documents, see the Extended Document topic in the Space Document section of the documentation website.

You can supply your Document Repository with query methods. Due to dynamic nature of SpaceDocument, there is no way for Spring Data to automatically derive query method names into queries. The only possible way to declare a method is to use the @Query annotation or load queries from external resources. The following is an example of a Document Repository supplied with search and sorting methods:

@SpaceDocumentName(PersonDocument.TYPE_NAME)
public interface PersonDocumentRepository extends GigaspacesDocumentRepository<PersonDocument, String&gt; {
// you can define simple queries
@Query("name = ?")
List<PersonDocument&gt; findByName(String name);
// you can build complex queries
@Query("name = ? and age = ?")
List<PersonDocument&gt; findByNameAndAge(String name, Integer age);
// you can query embedded properties
@Query("spouse.name = ?")
List<PersonDocument&gt; findBySpouseName(String name);
// you can query any properties, even if they are not present in you wrapper
@Query("customField = ?")
List<PersonDocument&gt; findByCustomField(String value);
// you can perform sorting using SQLQuery syntax
@Query("age = ? order by id asc")
List<PersonDocument&gt; findByAgeSortedById(Integer age);
}

You don't have to declare document properties to use them in queries, which allows dynamically adding and removing the properties.

Document Repositories don't support Querydsl syntax due to the dynamic nature of SpaceDocument properties.