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 possible to use 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.git
-
Enable Ignite Spring Boot and Spring Data extensions by adding the following artifacts to the
pom.xml
file<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
IgniteConfig
class in thecom.gridgain.training.spring
package with the following code. This class returns an instance of Ignite started by Spring Boot:@Configuration public class IgniteConfig { @Bean(name = "igniteInstance") public Ignite igniteInstance(Ignite ignite) { return ignite; } }
-
Update the
Application
class by tagging it with@EnableIgniteRepositories
annotation. -
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
IgniteConfig
by adding anIgniteConfigurer
that tells Spring Boot to start an Ignite client node instead of a server node:@Bean public IgniteConfigurer configurer() { return igniteConfiguration -> { igniteConfiguration.setClientMode(true); }; }
-
Add the
ServerNodeStartup
class (in thecom.gridgain.training.spring
package) that will be used to start a separate application/process for an Ignite server node.public class ServerNodeStartup { public static void main(String[] args) { Ignition.start(); } }
-
Start the Spring Boot application and the
ServerNodeStartup
application, and confirm the client node can connect to the server.
-
Open the
world.sql
script in the project'sconfig
folder and add theVALUE_TYPE
property to theCREATE TABLE Country
statement.
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_TYPE
property to theCREATE TABLE City
statement. 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_TYPE
property to theCREATE TABLE City
statement. 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 ignite
-
Load the database:
!run config/world.sql
-
Create the
CountryRepository
class (in thecom.gridgain.training.spring
package):@RepositoryConfig (cacheName = "Country") @Repository public interface CountryRepository extends IgniteRepository<Country, String> { }
-
Add a method that returns countries with a population bigger than provided one:
public List<Country> findByPopulationGreaterThanOrderByPopulationDesc(int population);
-
Add a test in ApplicationTests (in the
src/test
folder) that validates that the method returns a non-empty result:@Test void countryRepositoryWorks() { System.out.println("count=" + countryRepository.findByPopulationGreaterThanOrderByPopulationDesc(100_000_000).size()); }
Add following line after ApplicationTests class declaration:
@Autowired CountryRepository countryRepository;
-
Create the
CityRepository
class (in thecom.gridgain.training.spring
package) :@RepositoryConfig(cacheName = "City") @Repository public interface CityRepository extends IgniteRepository<City, CityKey> { }
-
Add a query that returns a complete key-value pair:
public Cache.Entry<CityKey, City> findById(int id);
-
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 ?") public List<List<?>> findTopXMostPopulatedCities(int limit);
-
Create a test in ApplicationTests to validate the methods respond properly:
@Test void cityRepositoryWorks() { System.out.println("city = " + cityRepository.findById(34)); System.out.println("top 5 = " + cityRepository.findTopXMostPopulatedCities(5)); }
Add following line after ApplicationTests class declaration:
@Autowired CityRepository cityRepository;
REST APIs are exposed using a thin client in the branch ThinClientREST. By starting ignite and loading the data (as mentioned in the above steps), this branch can be directly used for the REST APIs. The steps/code given below create a thick client.
-
Create a REST Controller for the application by creating a new class named
WorldDatabaseController
(in thecom.gridgain.training.spring
package) with the following contents:@RestController public class WorldDatabaseController { @Autowired CityRepository cityRepository; }
-
Add a method that returns top X most populated cities:
@GetMapping("/api/mostPopulated") public List<List<?>> getMostPopulatedCities(@RequestParam(value = "limit", required = false) Integer limit) { return cityRepository.findTopXMostPopulatedCities(limit); }
-
Restart the
Application
and then test the controller method either in Postman or your browser:
-
Create a new java package named
com.gridgain.training.thinclient
. -
Add the
IgniteThinClient
class to thecom.gridgain.training.thinclient
package that performs a join query on the City & Country tables
@SpringBootApplication
public class IgniteThinClient implements ApplicationRunner {
@Autowired
private IgniteClient client;
private static final String 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 ?";
@Override
public void run(ApplicationArguments args) throws Exception {
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(new SqlFieldsQuery(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
ServerNodeStartup
application 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
ThinClientApplication
class (in thecom.gridgain.training.thinclient
package)that bootstraps the Thin Client Application.
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, IgniteAutoConfiguration.class}, scanBasePackages = "com.gridgain.training.thinclient")
public class ThinClientApplication {
public static void main(String[] args) {
SpringApplication.run(ThinClientApplication.class);
}
@Bean
IgniteClientConfigurer configurer() {
return cfg -> {
cfg.setAddresses("127.0.0.1:10800");
cfg.setSendBufferSize(64*1024);
};
}
}
-
Stop the
Application
application (if it is currently running). If you do not, you will receive an error about a port conflict. -
Run the
ThinClientApplication
class/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
@SpringBootApplication
line. The result should like the line below.
@SpringBootApplication (scanBasePackages = "com.gridgain.training.spring", exclude = {IgniteClientAutoConfiguration.class})