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
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>
-
Add the following property to the pom.xml to select a version of H2 supported by Ignite:
<properties> <h2.version>1.4.197</h2.version> </properties>
-
Add the
IgniteConfig
class that 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.
-
Update the
IgniteConfig
by adding anIgniteConfigurer
that requires Spring Boot to start an Ignite client node:@Bean public IgniteConfigurer configurer() { return igniteConfiguration -> { igniteConfiguration.setClientMode(true); }; }
-
Add an
ServerNodeStartup
class that will be 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
ServerNodeStartupClass
application, and confirm the client node can connect to the server.
-
Open the
world.sql
script and add theVALUE_TYPE
property to theCREATE TABLE Country
statement:VALUE_TYPE=com.gridgain.training.spring.model.Country
-
Add the following
VALUE_TYPE
property to theCREATE TABLE City
statementVALUE_TYPE=com.gridgain.training.spring.model.City
-
Add the following
KEY_TYPE
property to theCREATE TABLE City
statementKEY_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:@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 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:@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;
-
Create a REST Controller for the application:
@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); }
-
Test the method in Postman or your browser:
- Add the
IgniteThinClient
class 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>
- Add the
ThinClientApplication
class that boostraps the Thin Client Application.
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, IgniteAutoConfiguration.class})
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);
};
}
}
- Run the
ThinClientApplication
class/application, and confirm the client node can connect to the server & run the query.