:toc: :icons: font :source-highlighter: prettify :project_id: gs-accessing-data-gemfire :apache-geode-name: Apache Geode
This guide walks you through the process of building an application the https://geode.apache.org/[{apache-geode-name}] data management system.
== What You Will build
You will use https://spring.io/projects/spring-data-geode[Spring Data for {apache-geode-name}] to store and retrieve POJOs.
[[setup]] == What You Need
:java_version: 1.8 include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/main/prereq_editor_jdk_buildtools.adoc[]
[[scratch]] == Starting with Spring Initializr
For all Spring applications, you should start with the https://start.spring.io[Spring Initializr]. Spring Initializr offers a fast way to pull in all the dependencies you need for an application and does a lot of the set up for you. This example needs Spring for Apache Geode dependency.
You can use this https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.5.5&packaging=jar&jvmVersion=11&groupId=com.example&artifactId=accessing-data-gemfire&name=accessing-data-gemfire&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.accessing-data-gemfire&dependencies=geode[pre-initialized project] and click Generate to download a ZIP file. This project is configured to fit the examples in this tutorial.
To manually initialize the project:
. In a web browser, navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you. . Choose either Gradle or Maven and the language you want to use. This guide assumes that you chose Java. . Click Dependencies and select Spring for Apache Geode. . Click Generate. . Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.
NOTE: If your IDE has the Spring Initializr integration, you can complete this process from your IDE.
NOTE: You can also fork the project from Github and open it in your IDE or other editor.
[[initial]] == Define a Simple Entity
{apache-geode-name} is an In-Memory Data Grid (IMDG) that maps data to regions. You can configure
distributed regions that partition and replicate data across multiple nodes in a cluster. However, in this guide,
we use a LOCAL
region so that you need not set up anything extra, such as an entire cluster of servers.
{apache-geode-name} is a key/value store, and a region implements the java.util.concurrent.ConcurrentMap
interface. Though
you can treat a region as a java.util.Map
, it is quite a bit more sophisticated than just a simple Java Map
,
given that data is distributed, replicated, and generally managed inside the region.
In this example, you store Person
objects in {apache-geode-name} (a region) by using only a few annotations.
src/main/java/hello/Person.java
[source,java,tabsize=2]Here you have a Person
class with two fields: name
and age
. You also have a single persistent constructor to
populate the entities when creating a new instance. The class uses https://projectlombok.org/[Project Lombok]
to simplify the implementation.
Notice that this class is annotated with @Region("People")
. When {apache-geode-name} stores an instance of this class,
a new entry is created inside the People
region. This class also marks the name
field with @Id
. This signifies
the identifier used to identify and track the Person
data inside {apache-geode-name}. Essentially, the @Id
annotated
field (such as name
) is the key, and the Person
instance is the value in the key/value entry. There is no automated
key generation in {apache-geode-name}, so you must set the ID (the name
) prior to persisting the entity to {apache-geode-name}.
The next important piece is the person's age. Later in this guide, we use it to fashion some queries.
The overridden toString()
method prints out the person's name and age.
[[queries]] == Create Simple Queries
Spring Data for {apache-geode-name} focuses on storing and accessing data in {apache-geode-name} using Spring. It also inherits powerful functionality from the Spring Data Commons project, such as the ability to derive queries. Essentially, you need not learn the query language of {apache-geode-name} (OQL). You can write a handful of methods, and the framework writes the queries for you.
To see how this works, create an interface that queries Person
objects stored in {apache-geode-name}:
src/main/java/hello/PersonRepository.java
[source,java,tabsize=2]PersonRepository
extends the CrudRepository
interface from Spring Data Commons and specifies types for
the generic type parameters for both the value and the ID (key) with which the Repository works (Person
and String
, respectively). This interface comes with many operations, including basic CRUD
(create, read, update, delete) and simple query data access operations (such a findById(..)
).
You can define other queries as needed by declaring their method signature. In this case, we add findByName
,
which essentially searches for objects of type Person
and finds one that matches on name
.
You also have:
findByAgeGreaterThan
: To find people above a certain agefindByAgeLessThan
: To find people below a certain agefindByAgeGreaterThanAndAgeLessThan
: To find people in a certain age rangeLet's wire this up and see what it looks like!
== Create an Application Class
The following example creates an application class with all the components:
src/main/java/hello/Application.java
[source,java,tabsize=2]In the configuration, you need to add the @EnableGemfireRepositories
annotation.
@EnableGemfireRepositories
scans the current package for any interfaces that extend one
of Spring Data's repository interfaces. You can use its basePackageClasses = MyRepository.class
to safely tell
Spring Data for {apache-geode-name} to scan a different root package by type for application-specific Repository
extensions.A {apache-geode-name} cache containing one or more regions is required to store all the data. For that, you use one
of Spring Data for {apache-geode-name}'s convenient configuration-based annotations: @ClientCacheApplication
,
@PeerCacheApplication
, or @CacheServerApplication
.
{apache-geode-name} supports different cache topologies, such as client/server, peer-to-peer (p2p), and even WAN arrangements. In p2p, a peer cache instance is embedded in the application, and your application would have the ability to participate in a cluster as a peer cache member. However, your application is subject to all the constraints of being a peer member in the cluster, so this is not as commonly used as, say, the client/server topology.
In our case, we use @ClientCacheApplication
to create a "client
" cache instance, which has the ability to
connect to and communicate with a cluster of servers. However, to keep things simple, the client stores
data locally by using a LOCAL
client region, without the need to setup or run any servers.
Now, remember how you tagged Person
to be stored in a region called People
by using the SDG mapping annotation,
@Region("People")
? You define that region here by using the ClientRegionFactoryBean<String, Person>
bean definition.
You need to inject an instance of the cache you just defined while also naming it People
.
NOTE: A {apache-geode-name} cache instance (whether a peer or client) is just a container for regions, which store your data. You can think of the cache as a schema in an RDBMS and regions as the tables. However, a cache also performs other administrative functions to control and manage all your Regions.
NOTE: The types are <String, Person>
, matching the key type (String
) with the value type (Person
).
The public static void main
method uses Spring Boot's SpringApplication.run()
to launch the application
and invoke the ApplicationRunner
(another bean definition) that performs the data access operations on {apache-geode-name}
using the application's Spring Data repository.
The application autowires an instance of PersonRepository
that you just defined. Spring Data for {apache-geode-name}
dynamically creates a concrete class that implements this interface and plugs in the needed query code to meet
the interface's obligations. This repository instance is used by the run()
method to demonstrate
the functionality.
== Store and fetch data
In this guide, you create three local Person
objects: Alice, Baby Bob, and Teen Carol. Initially,
they only exist in memory. After creating them, you have to save them to {apache-geode-name}.
Now you can run several queries. The first looks up everyone by name. Then you can run a handful of queries to find adults, babies, and teens, all by using the age attribute. With logging turned on, you can see the queries Spring Data for {apache-geode-name} writes on your behalf.
TIP: To see the {apache-geode-name} OQL queries that are generated by SDG, change the @ClientCacheApplication
annotation logLevel
attribute to config
.
Because the query methods (such as findByName
) are annotated with SDG's @Trace
annotation, this turns on {apache-geode-name}'s OQL query tracing (query-level logging), which shows you the generated OQL,
execution time, whether any {apache-geode-name} indexes were used by the query to gather the results, and the number of rows
returned by the query.
You should see something like this (with other content, such as queries):
.... Before linking up with {apache-geode-name}... Alice is 40 years old. Baby Bob is 1 years old. Teen Carol is 13 years old. Lookup each person by name... Alice is 40 years old. Baby Bob is 1 years old. Teen Carol is 13 years old. Adults (over 18): Alice is 40 years old. Babies (less than 5): Baby Bob is 1 years old. Teens (between 12 and 20): Teen Carol is 13 years old. ....
== Summary
Congratulations! You set up an {apache-geode-name} cache client, stored simple entities, and developed quick queries.
== See Also
The following guides may also be helpful:
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/main/footer.adoc[]