该指南将引导你使用 Pivotal GemFire 的数据服务集来构建应用。
我们将使用功能强大的 Spring Data for Pivotal GemFire 库来存储和检索 POJO。
像大多数的 Spring 入门指南一样,你可以从头开始并完成每个步骤,也可以绕过你已经熟悉的基本设置步骤。如论哪种方式,你最终都有可以工作的代码。
git clone https://github.com/spring-guides/gs-accessing-data-gemfire.git
gs-accessing-data-gemfire/initial
目录;待一切就绪后,可以检查一下 gs-accessing-data-gemfire/complete
目录中的代码。
首先,我们搭建一个基本的构建脚本。在使用 Spring 构建应用时,可以使用任何喜欢的构建系统,但此处包含使用 Gradle 和 Maven 所需的代码。如果我们都不熟悉,请参阅使用 Gradle 构建 Java 项目或使用 Maven 构建 Java 项目。
在我们选择的项目目录中,创建以下自目录结构;例如,在 *nix 系统上使用 mkdir -p src/main/java/hello
:
└── src
└── main
└── java
└── hello
以下是初始化的 Gradle 构建文件。
build.gradle
buildscript {
repositories {
mavenCentral()
maven { url "https://repo.spring.io/libs-release" }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.2.1.RELEASE")
}
}
plugins {
id "io.spring.dependency-management" version "1.0.5.RELEASE"
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 1.8
targetCompatibility = 1.8
bootJar {
baseName = 'gs-accessing-data-gemfire'
version = '0.1.0'
}
repositories {
mavenCentral()
maven { url "https://repo.spring.io/libs-release" }
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web") {
exclude group: "org.springframework.boot", module: "spring-boot-starter-logging"
}
compile("org.springframework.data:spring-data-gemfire")
compile("org.projectlombok:lombok")
runtime("org.springframework.shell:spring-shell:1.2.0.RELEASE")
}
Spring Boot gradle 插件提供了许多方便的功能:
public static void main()
方法以将其标记为可运行类;
首先,我们搭建一个基本的构建脚本。使用 Spring 构建应用时,可以使用任何喜欢的构建系统,但此处包含使用 Maven 所需的代码。如果你不熟悉 Maven,请参阅使用 Maven 构建 Java 项目。
在我们选择的项目目录中,创建以下自目录结构;例如,在 *nix 系统上使用 mkdir -p src/main/java/hello
:
└── src
└── main
└── java
└── hello
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<groupId>org.springframework</groupId>
<artifactId>gs-accessing-data-gemfire</artifactId>
<version>0.1.0</version>
<properties>
<spring-shell.version>1.2.0.RELEASE</spring-shell.version>
</properties>
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-gemfire</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell</artifactId>
<version>${spring-shell.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Spring Boot Maven 插件提供了许多方便的功能:
public static void main()
方法以将其标记为可运行类;
Pivotal GemFire 是一个内存数据网格(IMDG),可将数据映射到区域。可以配置分布式区域,以在集群中的多个节点之间分区和复制数据。但是,在该指南中,我们将使用 LOCAL
,因此我们无需做其他准备,例如整个服务器集群等操作。
Pivotal GemFire 是一个键/值存储,而 Region 则实现 java.util.concurrent.ConcurrentMap
接口。尽管我们可以将 Region 当作 java.util.Map
对待,但要比在区域内分发,复制和管理数据的简单 Java Map
复杂得多。
在该示例中,仅使用一些注解将 Person
对象存储在 Pivotal GemFire(一个 Region)中。
src/main/java/hello/Person.java
package hello;
import java.io.Serializable;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.gemfire.mapping.annotation.Region;
import lombok.Getter;
@Region(value = "People")
public class Person implements Serializable {
@Id
@Getter
private final String name;
@Getter
private final int age;
@PersistenceConstructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return String.format("%s is %d years old", getName(), getAge());
}
}
这里,我们有一个 Person
类,其中包含两个字段,即 name
和 age
。我们还有一个持久的构造函数,用于在创建新实例时填充实体。该类使用 Project Lombok 简化实现。
请注意,该类由 @Region("People")
标注。当 Pivotal GemFire 存储该类的实例时,将在 “People” 区域内创建一个新条目。该类还使用 @Id
标记 name
字段。这表示用于识别和跟踪 Pivotal GemFire 中的 Person
数据的标识符。本质上,@Id
标注的字段(例如 name
)是键,Person
实例是键/值条目中的值。Pivotal GemFire 中没有自动生成密钥的功能,因此我们必须先设置 id(即 name
),然后再将实体持久保存到 Pivotal GemFire 中。
下一个重要的片段是人的年龄。在该指南之后,我们将使用它来塑造一些查询。
覆盖的 toString()
方法将打印出该人的姓名和年龄。
Pivotal GemFire 的 Spring Data 专注于使用 Spring 的 Pivotal GemFire 中存储和访问数据。它还从 Spring Data Commons 项目继承了很强大的功能,例如导出查询的功能。本质上,我们不必学习 Pivotal GemFire(OQL)的查询语言;我们只需要写一些方法,框架即可为我们编写查询。
要查看其工作原理,请创建一个查询存储在 Pivotaol GemFire 中 Person
对象的接口。
src/main/java/hello/PersonRepository.java
package hello;
import org.springframework.data.gemfire.repository.query.annotation.Trace;
import org.springframework.data.repository.CrudRepository;
public interface PersonRepository extends CrudRepository<Person, String> {
@Trace
Person findByName(String name);
@Trace
Iterable<Person> findByAgeGreaterThan(int age);
@Trace
Iterable<Person> findByAgeLessThan(int age);
@Trace
Iterable<Person> findByAgeGreaterThanAndAgeLessThan(int greaterThanAge, int lessThanAge);
}
PersonRepository
从 Spring Data Commons 扩展了 CrudRepository
接口,并为 Repository 使用的值和 id(key)的通用类型参数指定了类型,分别是 Person
和 String
。该接口开箱即用,具有许多操作,包括基本的 CRUD(CREATE、READ、UPDATE、DELETE)和简单的查询(例如 findById(..)
)数据访问操作。
我们可以根据需要定义其他查询,只需声明它们的方法签名即可。在这种情况下,我们将添加 findByName
,它实际上的搜索类型为 Person
的对象并找到与 name
匹配的对象。
以及:
findByAgeGreaterThan
查找超过一定年龄的人;findByAgeLess
查找一定年龄以下的人;findByAgeGreaterThanAndAgeLessThan
查找特定年龄段的人。我们来看一下它的效果!
这里,我们将创建一个拥有所有组件的 Application 类。
src/main/java/hello/Application.java
package hello;
import static java.util.Arrays.asList;
import static java.util.stream.StreamSupport.stream;
import java.io.IOException;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories;
@SpringBootApplication
@ClientCacheApplication(name = "AccessingDataGemFireApplication", logLevel = "error")
@EnableEntityDefinedRegions(basePackageClasses = Person.class,
clientRegionShortcut = ClientRegionShortcut.LOCAL)
@EnableGemfireRepositories
public class Application {
public static void main(String[] args) throws IOException {
SpringApplication.run(Application.class, args);
}
@Bean
ApplicationRunner run(PersonRepository personRepository) {
return args -> {
Person alice = new Person("Adult Alice", 40);
Person bob = new Person("Baby Bob", 1);
Person carol = new Person("Teen Carol", 13);
System.out.println("Before accessing data in Pivotal GemFire...");
asList(alice, bob, carol).forEach(person -> System.out.println("\t" + person));
System.out.println("Saving Alice, Bob and Carol to Pivotal GemFire...");
personRepository.save(alice);
personRepository.save(bob);
personRepository.save(carol);
System.out.println("Lookup each person by name...");
asList(alice.getName(), bob.getName(), carol.getName())
.forEach(name -> System.out.println("\t" + personRepository.findByName(name)));
System.out.println("Query adults (over 18):");
stream(personRepository.findByAgeGreaterThan(18).spliterator(), false)
.forEach(person -> System.out.println("\t" + person));
System.out.println("Query babies (less than 5):");
stream(personRepository.findByAgeLessThan(5).spliterator(), false)
.forEach(person -> System.out.println("\t" + person));
System.out.println("Query teens (between 12 and 20):");
stream(personRepository.findByAgeGreaterThanAndAgeLessThan(12, 20).spliterator(), false)
.forEach(person -> System.out.println("\t" + person));
};
}
}
在配置中,我们需要添加 @EnableGemfireRepositories
注解。
默认情况下,@EnableGemfireRepositories
将在当前包中扫描任何扩展了 Spring Data 的 Repository 接口之一的接口。使用它的 basePackageClasses = MyRepository.class
来安全地告诉 Spring Data for Pivotal GemFire 以按类型扫描不同的根包,并查找特定于应用的 Repository 扩展。
需要包含一或多个区域的 Pivotal GemFire 缓存来存储所有数据。为此,我们可以使用 Spring Data for Pivotal GemFire 的一种基于配置的便捷注解:@ClientCacheApplication
、@PerCacheApplication
或 @CacheServerApplication
。
Pivotal GemFire 支持不同的缓存拓扑,例如客户端/服务器,点对点(p2p)甚至 WAN 准备。在 p2p 中,某一点缓存实例嵌入在应用中,我们的应用将能够以某一点缓存成员的身份参与集群。但是,我们的应用要成为集群中的某一点成员,要手到所有限制,因此,该用法不像客户端/服务器拓扑那样普遍。
在我们的例子中,我们将使用 @ClientCacheApplication
创建一个 “客户端” 缓存实例,该实例具有连接到服务器集群并与之通信的能力。但是,为简单起见,客户端将只使用 LOCAL
客户端区域在本地存储数据,而无需搭建或运行任何服务器。
Spring 推荐生产的企业版 Pivotal GemFire,我们可以在其中跨集群中的多个节点创建分布式缓存和区域。另外,我们也可以使用开源版本 Apache Geode,并从 Apache Geode 社区获得支持。
现在,还记得如何使用 SDG 映射注解 @Region("People")
注解用于存储在 “People” 区域中的 Person
吗?我们可以使用 ClientRegionFactoryBean
bean 定义在此定义 Region。我们需要注入刚刚定义的缓存实例,同时将其命名为 “People”。
Pivotal GemFire 缓存实例(某一个点或客户端)指示用于存储数据的区域的容器。我们可以将高速缓存视为 RDBMS 中的架构,将区域视为表。但是,缓存还执行其他管理功能来控制和管理我们的所有区域。
类型为
,将键类型(String
)与值类型(Perosn
)匹配。
public static void main()
方法使用 Spring Boot 的 SpringApplication.run()
启动应用,并调用 ApplicationRunner
(另一个 bean 定义),其使用应用的 Spring Data Repository 在 Picotal GemFire 上执行数据访问操作。
该应用会自动关联我们刚定义的 PersonRepository
实例。Spring Data for Pivotal GemFire 将动态创建一个具体类,该类实现该接口并插入所需的查询代码以满足接口的义务。run()
方法使用该 Repository 实例来演示功能。
该指南中,我们将创建三个本地的 Person
对象,Alice、Baby Bob 和 Teen Carol。最初,它们仅存在于内存中。创建它们之后,我必须将它们保存到 Pivotal GemFire。
现在,我们运行几个查询。第一个按名称查找每个人。然后,我们将执行一些查询,,以使用年龄属性查找成人、婴儿和青少年。打开日志记录后,我们可以看到 Spring Data for Pivotal GemFire 为我们编写的查询。
将
@ClientCacheApplication
注解的logLevel
属性更改为 “config” 以查看由 SDG 生成的 Pivotal GemFire OQL 查询语句。由于查询方法(例如findByName
)已使用 SDG 的@Trace
注解进行标注,因此将打开 Pivotal GemFire 的 OQL 查询跟踪(查询级别日志记录),从而向我们显示生成的 OQL,执行时间以及查询是否使用了任何 Pivotal GemFire Indexes 来收集结果,以及查询返回的行数。
我们可以结合 Gradle 或 Maven 来从命令行运行该应用。我们还可以构建一个包含所有必须依赖项、类以及资源的可执行 JAR 文件,然后运行该文件。在整个开发生命周期中,跨环境等等情况下,构建可执行 JAR 可以轻松地将服务作为应用进行发布、版本化以及部署。
如果使用 Gradle,则可以借助 ./gradlew bootRun
来运行应用。或通过借助 ./gradlew build
来构建 JAR 文件,然后运行 JAR 文件,如下所示:
java -jar build/libs/gs-accessing-data-gemfire-0.1.0.jar
如果使用 Maven,则可以借助 ./mvnw spring-boot:run
来运行该用。或可以借助 ./mvnw clean package
来构建 JAR 文件,然后运行 JAR 文件,如下所示:
java -jar target/gs-accessing-data-gemfire-0.1.0.jar
我们还可以构建一个经典的 WAR 文件。
我们应该看到类似以下的内容(以及其他类似查询语句的内容):
Before linking up with GemFire...
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.
恭喜你!我们搭建了 Pivotal GemFire 缓存客户端,存储了简单的实体,并开发了快速查询语句。
以下指南也可能会有所帮助:
想看指南的其他内容?请访问该指南的所属专栏:《Spring 官方指南》