[[gemfire-repositories]]
= Spring Data Geode Repositories

== Introduction

_Spring Data Geode_ provides support to use the _Spring Data Repository_ abstraction to easily persist entities
into Geode along with execute queries.  A general introduction to the _Repository programming model_ is provided
http://docs.spring.io/spring-data/data-commons/docs/current/reference/html/#repositories[here].

[[gemfire-repositories.spring-configuration]]
== Spring Configuration

To bootstrap _Spring Data Repositories_, you use the `<repositories/>` element from the _Spring Data Geode_
Data namespace:

.Bootstrap Spring Data Geode Repositories
====
[source,xml]
----
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:gfe-data="http://www.springframework.org/schema/data/geode"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/data/geode http://www.springframework.org/schema/data/geode/spring-data-geode.xsd>

  <gfe-data:repositories base-package="com.example.acme.repository"/>

</beans>
----
====

This configuration snippet looks for interfaces below the configured base package and creates Repository instances
for those interfaces backed by a `SimpleGemFireRepository`.

IMPORTANT: You must have your application domain classes correctly mapped to configured Regions
or the bootstrap process will fail otherwise.

[[gemfire-repositories.executing-queries]]
== Executing OQL Queries

_Spring Data Geode Repositories_ enable the definition of query methods to easily execute Geode OQL Queries
against the Region the managed entity is mapped to.

.Sample Repository
====
[source,java]
----
@Region("People")
public class Person { … }
----

[source,java]
----
public interface PersonRepository extends CrudRepository<Person, Long> {

  Person findByEmailAddress(String emailAddress);

  Collection<Person> findByFirstname(String firstname);

  @Query("SELECT * FROM /People p WHERE p.firstname = $1")
  Collection<Person> findByFirstnameAnnotated(String firstname);

  @Query("SELECT * FROM /People p WHERE p.firstname IN SET $1")
  Collection<Person> findByFirstnamesAnnotated(Collection<String> firstnames);
}
----
====

The first query method listed here will cause the following OQL query to be derived:
`SELECT x FROM /People x WHERE x.emailAddress = $1`.  The second query method works the same way except
it's returning all entities found whereas the first query method expects a single result to be found.

In case the supported keywords are not sufficient to expresss and declare your OQL query, or the method name
becomes too verbose, you can annotate the query methods with `@Query` as seen for methods 3 and 4.

[cols="1,2,2", options="header"]
.Supported keywords for query methods
|===
| Keyword
| Sample
| Logical result

| `GreaterThan`
| `findByAgeGreaterThan(int age)`
| `x.age > $1`

| `GreaterThanEqual`
| `findByAgeGreaterThanEqual(int age)`
| `x.age >= $1`

| `LessThan`
| `findByAgeLessThan(int age)`
| `x.age < $1`

| `LessThanEqual`
| `findByAgeLessThanEqual(int age)`
| `x.age <= $1`

| `IsNotNull`, `NotNull`
| `findByFirstnameNotNull()`
| `x.firstname =! NULL`

| `IsNull`, `Null`
| `findByFirstnameNull()`
| `x.firstname = NULL`

| `In`
| `findByFirstnameIn(Collection<String> x)`
| `x.firstname IN SET $1`

| `NotIn`
| `findByFirstnameNotIn(Collection<String> x)`
| `x.firstname NOT IN SET $1`

| `IgnoreCase`
| `findByFirstnameIgnoreCase(String firstName)`
| `x.firstname.equalsIgnoreCase($1)`

| (No keyword)
| `findByFirstname(String name)`
| `x.firstname = $1`

| `Like`
| `findByFirstnameLike(String name)`
| `x.firstname LIKE $1`

| `Not`
| `findByFirstnameNot(String name)`
| `x.firstname != $1`

| `IsTrue`, `True`
| `findByActiveIsTrue()`
| `x.active = true`

| `IsFalse`, `False`
| `findByActiveIsFalse()`
| `x.active = false`
|===

[[gemfire-repositories.oql-extensions]]
== OQL Query Extensions using Annotations

Many query languages, such as Apache Geode's OQL (Object Query Language), have extensions that are not directly
supported by _Spring Data Commons' Repository_ infrastructure.

One of _Spring Data Commons' Repository_ infrastructure goals is to function as the lowest common denominator
in order to maintain support for and portability across the widest array of data stores available and in use
for application development today.  Technically, this means developers can access multiple different data stores
supported by _Spring Data Commons_ within their applications by reusing their existing application-specific
Repository interfaces, a very convenient and powerful abstraction.

To support Geode's OQL Query language extensions and preserve portability across different data stores,
_Spring Data Geode_ adds support for OQL Query extensions using Java Annotations.  These Annotations will be ignored
by other _Spring Data Repository_ implementations (e.g. _Spring Data_ JPA or _Spring Data Redis_) that do not have
similar query language extensions.

For instance, many data stores will most likely not implement Geode's OQL `IMPORT` keyword.  By implementing `IMPORT`
as an Annotation (i.e. `@Import`) rather than as part of the query method signature (specifically, the method 'name'),
then this will not interfere with the parsing infrastructure when evaluating the query method name to construct
another data store language appropriate query.

Currently, the set of Geode OQL Query language extensions that are supported by _Spring Data Geode_ include:

[cols="1,2,2,2", options="header"]
.Supported Geode OQL extensions for Repository query methods
|===
| Keyword
| Annotation
| Description
| Arguments

| http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/query_index_hints.html#topic_cfb_mxn_jq[HINT]
| `@Hint`
| OQL Query Index Hints
| `String[]` (e.g. @Hint({ "IdIdx", "TxDateIdx" }))

| http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_select/the_import_statement.html#concept_2E9F15B2FE9041238B54736103396BF7[IMPORT]
| `@Import`
| Qualify application-specific types.
| `String` (e.g. @Import("org.example.app.domain.Type"))

| http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_select/the_select_statement.html#concept_85AE7D6B1E2941ED8BD2A8310A81753E__section_25D7055B33EC47B19B1B70264B39212F[LIMIT]
| `@Limit`
| Limit the returned query result set.
| `Integer` (e.g. @Limit(10); default is Integer.MAX_VALUE)

| http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_additional/query_debugging.html#concept_2D557E24AAB24044A3DB36B3124F6748[TRACE]
| `@Trace`
| Enable OQL Query specific debugging.
| NA
|===

As an example, suppose you have a `Customers` application domain class and corresponding Geode Region along with a
`CustomerRepository` and a query method to lookup `Customers` by last name, like so...

.Sample Customers Repository
====
[source,java]
----
package ...;

import org.springframework.data.annotation.Id;
import org.springframework.data.gemfire.mapping.annotation.Region;
...

@Region("Customers")
public class Customer ... {

  @Id
  private Long id;

  ...
}
----

[source,java]
----
package ...;

import org.springframework.data.gemfire.repository.GemfireRepository;
...

public interface CustomerRepository extends GemfireRepository<Customer, Long> {

  @Trace
  @Limit(10)
  @Hint("LastNameIdx")
  @Import("org.example.app.domain.Customer")
  List<Customer> findByLastName(String lastName);

  ...
}
----
====

This will result in the following OQL Query:

`<TRACE> <HINT 'LastNameIdx'> IMPORT org.example.app.domain.Customer; SELECT * FROM /Customers x WHERE x.lastName = $1 LIMIT 10`

_Spring Data Geode's Repository_ extension and support is careful not to create conflicting declarations when
the OQL Annotation extensions are used in combination with the `@Query` annotation.

As another example, suppose you have a raw `@Query` annotated query method defined in your `CustomerRepository`
like so...

.CustomerRepository
====
[source,java]
----
public interface CustomerRepository extends GemfireRepository<Customer, Long> {

  @Trace
  @Limit(10)
  @Hint("CustomerIdx")
  @Import("org.example.app.domain.Customer")
  @Query("<TRACE> <HINT 'ReputationIdx'> SELECT DISTINCT * FROM /Customers c WHERE c.reputation > $1 ORDER BY c.reputation DESC LIMIT 5")
  List<Customer> findDistinctCustomersByReputationGreaterThanOrderByReputationDesc(Integer reputation);
}
----
====

This query method results in the following OQL Query:

`IMPORT org.example.app.domain.Customer; <TRACE> <HINT 'ReputationIdx'> SELECT DISTINCT * FROM /Customers x
WHERE x.reputation > $1 ORDER BY c.reputation DESC LIMIT 5`

As you can see, the `@Limit(10)` annotation will +not+ override the `LIMIT` defined explicitly in the raw query.
As well, `@Hint("CustomerIdx")` annotation does +not+ override the `HINT` explicitly defined in the raw query.
Finally, the `@Trace` annotation is redundant and has no additional effect.

[NOTE]
====
The "ReputationIdx" Index is probably not the most sensible index given the number of Customers who will possibly have
the same value for their reputation, which will effectively reduce the effectiveness of the index.  Please choose
indexes and other optimizations wisely as an improper or poorly choosen index can have the opposite effect on your
performance given the overhead in maintaining the index.  The "ReputationIdx" was only used to serve the purpose
of the example.
====
