This project template is used throughout a two-hour training session for Java developers and architects who want to explore the best practices and nuances of using Spring Boot and Spring Data with Apache Ignite. During that instructor-led training, you build a RESTful web service that uses Apache Ignite as an in-memory database. The service is a Spring Boot application that interacts with the Ignite cluster via Spring Data repository abstractions.
Check the schedule a join one of our upcoming sessions. All the sessions are delivered by seasoned Ignite experts and committers.
- GIT command line or GitHub Desktop (https://desktop.github.com/)
- Java Developer Kit, version 8 or later
- Apache Maven 3.6.x
- Your favorite IDE, such as IntelliJ IDEA, or Eclipse, or a simple text editor.
- Postman REST tool (https://www.postman.com/) or a web browser
Note
Although it is be possible to later versions of the JDK by following the instructions at https://ignite.apache.org/docs/latest/quick-start/java#running-ignite-with-java-11-or-later, we strongly suggest that you only use JDK 8 (1.8).
Open a terminal window and clone the project to your dev environment:
git clone https://github.com/GridGain-Demos/spring-data-training.gitEnable Ignite Spring Boot and Spring Data extensions by adding the following artifacts to the
pom.xmlfile<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.ignite</groupId> <artifactId>ignite-spring-data-2.2-ext</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>org.apache.ignite</groupId> <artifactId>ignite-spring-boot-autoconfigure-ext</artifactId> <version>1.0.0</version> </dependency>
The following property has already been added to the pom.xml to select a version of H2 supported by Ignite so there is no need to take any action. Just remember that this property needs to set in the pom.xml to insure proper function of Ignite.
<properties> <h2.version>1.4.197</h2.version> </properties>
Add the
IgniteConfigclass in thecom.gridgain.training.springpackage with the following code. This class returns an instance of Ignite started by Spring Boot:@ConfigurationpublicclassIgniteConfig { @Bean(name = "igniteInstance") publicIgniteigniteInstance(Igniteignite) { returnignite; } }
Update the
Applicationclass by tagging it with@EnableIgniteRepositoriesannotation.Start the application and confirm Spring Boot started an Ignite server node instance. You should see logging output something like that below.
The key part is the line in the center that has a timestamp followed by "Topology Snapshot". This indicates that the Ignite cluster was started and has one server and zero clients as a part of it.>>> Local ports: TCP:10800 TCP:11211 TCP:47100 UDP:47400 TCP:47500 >>> +-----------------------------------------------------------------------+ [15:50:20] Topology snapshot [ver=1, locNode=297d311a, servers=1, clients=0, state=ACTIVE, CPUs=12, offheap=7.2GB, heap=8.0GB] [15:50:20] ^-- Baseline [id=0, size=1, online=1, offline=0] 2025-02-12 15:50:20.886 INFO 11298 --- [ main] o.a.i.i.m.d.GridDiscoveryManager : Topology snapshot [ver=1, locNode=297d311a, servers=1
Update the
IgniteConfigby adding anIgniteConfigurerthat tells Spring Boot to start an Ignite client node instead of a server node:@BeanpublicIgniteConfigurerconfigurer() { returnigniteConfiguration -> { igniteConfiguration.setClientMode(true); }; }
Add the
ServerNodeStartupclass (in thecom.gridgain.training.springpackage) that will be used to start a separate application/process for an Ignite server node.publicclassServerNodeStartup { publicstaticvoidmain(String[] args) { Ignition.start(); } }
Start the Spring Boot application and the
ServerNodeStartupapplication, and confirm the client node can connect to the server.
Open the
world.sqlscript in the project'sconfigfolder and add theVALUE_TYPEproperty to theCREATE TABLE Countrystatement.
Be sure to add it inside the "s in the WITH statement and to use a comma to separate the values in the string.VALUE_TYPE=com.gridgain.training.spring.model.Country
The resulting line should look like this:
) WITH "template=partitioned, backups=1, CACHE_NAME=Country,VALUE_TYPE=com.gridgain.training.spring.model.Country";Add the following
VALUE_TYPEproperty to theCREATE TABLE Citystatement. Again, do this within the "s and use a comma to separate the values.VALUE_TYPE=com.gridgain.training.spring.model.City
Add the following
KEY_TYPEproperty to theCREATE TABLE Citystatement. Once again, do this within the "s and use a comma to separate the values.KEY_TYPE=com.gridgain.training.spring.model.CityKey
The resulting line should look like this:
) WITH "template=partitioned, backups=1, affinityKey=CountryCode, CACHE_NAME=City,VALUE_TYPE=com.gridgain.training.spring.model.City,KEY_TYPE=com.gridgain.training.spring.model.CityKey";Build a shaded package for the app:
mvn clean package -DskipTests=true
Start an SQLLine process:
java -cp libs/app.jar sqlline.SqlLine
Connect to the cluster:
!connect jdbc:ignite:thin://127.0.0.1/ ignite igniteLoad the database:
!run config/world.sql
Create the
CountryRepositoryclass (in thecom.gridgain.training.springpackage):@RepositoryConfig (cacheName = "Country") @RepositorypublicinterfaceCountryRepositoryextendsIgniteRepository<Country, String> { }
Add a method that returns countries with a population bigger than provided one:
publicList<Country> findByPopulationGreaterThanOrderByPopulationDesc(intpopulation);
Add a test in ApplicationTests (in the
src/testfolder) that validates that the method returns a non-empty result:@TestvoidcountryRepositoryWorks() { System.out.println("count=" + countryRepository.findByPopulationGreaterThanOrderByPopulationDesc(100_000_000).size()); }
Add following line after ApplicationTests class declaration:
@AutowiredCountryRepositorycountryRepository;
Create the
CityRepositoryclass (in thecom.gridgain.training.springpackage) :@RepositoryConfig(cacheName = "City") @RepositorypublicinterfaceCityRepositoryextendsIgniteRepository<City, CityKey> { }
Add a query that returns a complete key-value pair:
publicCache.Entry<CityKey, City> findById(intid);
Add a direct SQL query that joins two tables:
@Query("SELECT city.name, MAX(city.population), country.name FROM country " + "JOIN city ON city.countrycode = country.code " + "GROUP BY city.name, country.name, city.population " + "ORDER BY city.population DESC LIMIT ?") publicList<List<?>> findTopXMostPopulatedCities(intlimit);
Create a test in ApplicationTests to validate the methods respond properly:
@TestvoidcityRepositoryWorks() { System.out.println("city = " + cityRepository.findById(34)); System.out.println("top 5 = " + cityRepository.findTopXMostPopulatedCities(5)); }
Add following line after ApplicationTests class declaration:
@AutowiredCityRepositorycityRepository;
Create a REST Controller for the application by creating a new class named
WorldDatabaseController(in thecom.gridgain.training.springpackage) with the following contents:@RestControllerpublicclassWorldDatabaseController { @AutowiredCityRepositorycityRepository; }
Add a method that returns top X most populated cities:
@GetMapping("/api/mostPopulated") publicList<List<?>> getMostPopulatedCities(@RequestParam(value = "limit", required = false) Integerlimit) { returncityRepository.findTopXMostPopulatedCities(limit); }
Restart the
Applicationand then test the controller method either in Postman or your browser:
Create a new java package named
com.gridgain.training.thinclient.Add the
IgniteThinClientclass to thecom.gridgain.training.thinclientpackage that performs a join query on the City & Country tables
@SpringBootApplicationpublicclassIgniteThinClientimplementsApplicationRunner {
@AutowiredprivateIgniteClientclient;
privatestaticfinalStringQUERY = "SELECT city.name, MAX(city.population), country.name FROM country JOIN city ON city.countrycode = country.code GROUP BY city.name, country.name, city.population ORDER BY city.population DESC LIMIT ?";
@Overridepublicvoidrun(ApplicationArgumentsargs) throwsException {
System.out.println("ServiceWithIgniteClient.run");
System.out.println("Cache names existing in cluster: " + client.cacheNames());
ClientCache<CityKey, City> cityCache = client.cache("City");
FieldsQueryCursor<List<?>> cursor = cityCache.query(newSqlFieldsQuery(QUERY).setArgs(3));
System.out.printf("%15s %12s %10s\n", "City", "Country", "Population");
System.out.printf("%15s %12s %10s\n", "===============", "============", "==========");
cursor.forEach((row) -> {
System.out.printf("%15s %12s %10d\n", row.get(0), row.get(2), row.get(1));
});
}
}- Add the following to the pom.xml:
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-spring-boot-thin-client-autoconfigure-ext</artifactId>
<version>1.0.0</version>
</dependency>- When maven loads the changes to the POM file, you will likely need to restart the the
ServerNodeStartupapplication and reload your data. You should not need to restart sqlline. Just reissue the connect and run commands:
!connect jdbc:ignite:thin://127.0.0.1/ ignite ignite
!run config/world.sql- Add the
ThinClientApplicationclass (in thecom.gridgain.training.thinclientpackage)that bootstraps the Thin Client Application.
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, IgniteAutoConfiguration.class}, scanBasePackages = "com.gridgain.training.thinclient")
publicclassThinClientApplication {
publicstaticvoidmain(String[] args) {
SpringApplication.run(ThinClientApplication.class);
}
@BeanIgniteClientConfigurerconfigurer() {
returncfg -> {
cfg.setAddresses("127.0.0.1:10800");
cfg.setSendBufferSize(64*1024);
};
}
}Stop the
Applicationapplication (if it is currently running). If you do not, you will receive an error about a port conflict.Run the
ThinClientApplicationclass/application, and confirm the client node can connect to the server & run the query.
Notes
You can not run both the thin client and the "Application" at the same time since they will both attempt to run on port 8080.
To be able to run the Application once you have added the thin client code, you will have to modify the class definition in the Application class. Simply remove the "//" from the
@SpringBootApplicationline. The result should like the line below.
@SpringBootApplication (scanBasePackages = "com.gridgain.training.spring", exclude = {IgniteClientAutoConfiguration.class})