Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ public Reader(BasqueNamesRepository repository, RemoteCacheManager remoteCacheMa
this.repository = repository;
random = new Random();
this.remoteCacheManager = remoteCacheManager;
// Upload the generated schema in the server
// Upload the generated schema in the server.
// Since 16.2, schemas are auto-discovered and uploaded by the Spring Boot starter.
// To disable auto-upload, set infinispan.remote.use-schema-registration=false
remoteCacheManager.administration().schemas().createOrUpdate(new BasquesNamesSchemaBuilderImpl());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# tag::config[]
infinispan.remote.server-list=127.0.0.1:11222
infinispan.remote.reactive=true

# Protobuf schemas are automatically discovered and uploaded to the server at startup.
# To disable this behavior, uncomment the following property:
# infinispan.remote.use-schema-registration=false
# end::config[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
= Spring Boot Automatic Protobuf Schema Registration
:summary: Build a Spring Boot REST app with automatic Protobuf schema discovery and registration on a remote Infinispan Server
:mode: integration
:topics: spring-boot,protostream,schema
:keywords: spring-boot,protobuf,protostream,schema,auto-registration,rest,full-text,indexed
:duration: 10
:source-dir: integrations/spring-boot/cache-remote-schema-auto-registration

== What You Will Learn

How to use the Infinispan Spring Boot starter with automatic Protobuf schema registration.
Starting with Infinispan 16.2, the starter automatically discovers `GeneratedSchema` implementations on the classpath, registers them as serialization context initializers, and uploads the schemas to the Infinispan Server at startup.
No manual `schemas().createOrUpdate()` call is needed.

This tutorial builds a REST application that stores indexed characters in a remote cache and performs full-text search queries.

== Prerequisites

* Java 17+
* Spring Boot
* Docker or Podman (optional)

You can start an Infinispan Server manually with Docker or Podman:

[source,shell]
----
docker run -it --rm -p 11222:11222 -e USER=admin -e PASS=password quay.io/infinispan/server:latest
----

TIP: You can replace `docker` with `podman` in the command above if you use Podman.

TIP: If no server is running on `localhost:11222`, the application automatically starts an Infinispan container using Testcontainers.

== Step 1: Add Dependencies

Add the Infinispan Spring Boot remote starter and the ProtoStream annotation processor:

[source,xml]
----
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-spring-boot4-starter-remote</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan.protostream</groupId>
<artifactId>protostream-processor</artifactId>
</dependency>
----

== Step 2: Configure the Connection and Cache

In `application.properties`, configure the connection, the marshaller, and the cache definition:

[source,properties]
----
include::src/main/resources/application.properties[tag=config]
----

Key points:

* `infinispan.remote.connection-uri` provides the connection string including credentials.
* `infinispan.remote.cache.characters.configuration-uri` points to a classpath XML file that defines the cache.
* `infinispan.remote.cache.characters.marshaller` is set to `ProtoStreamMarshaller` because the default is `JavaSerializationMarshaller`.
* There is no manual schema registration code. The starter automatically discovers and uploads Protobuf schemas at startup.
* To disable auto schema upload, set `infinispan.remote.use-schema-registration=false`.

== Step 3: Define a Protobuf Entity

Create an indexed Protobuf record with `@Text` fields for full-text search:

[source,java]
----
include::src/main/java/tutorial/spring/infinispan/model/Character.java[tag=entity]
----

== Step 4: Define the Protobuf Schema

Create a `@ProtoSchema` interface that extends `GeneratedSchema`.
The starter will discover this interface automatically on the classpath, register it with the serialization context, and upload the generated `.proto` schema to the server:

[source,java]
----
include::src/main/java/tutorial/spring/infinispan/model/AppSchema.java[tag=schema]
----

== Step 5: Query the Cache

Inject the `RemoteCacheManager` and perform full-text queries using Ickle:

[source,java]
----
include::src/main/java/tutorial/spring/infinispan/service/CharacterSearch.java[tag=service]
----

== Step 6: Expose REST Endpoints

Create a Spring `@RestController` that exposes the search service:

[source,java]
----
include::src/main/java/tutorial/spring/infinispan/CharactersResource.java[tag=resource]
----

== Step 7: Run the Tutorial

[source,bash]
----
mvn spring-boot:run
----

Then query the REST endpoints:

[source,bash]
----
curl http://localhost:8080/characters/0
curl "http://localhost:8080/characters/query?term=Felix"
----

== How Auto Schema Registration Works

The Infinispan Spring Boot starter (16.2+) automatically:

1. Scans your application packages for `GeneratedSchema` implementations using Spring's classpath scanning.
2. Registers them as `SerializationContextInitializer` instances with the `RemoteCacheManager`.
3. Uploads the `.proto` schemas to the Infinispan Server via `RemoteSchemasAdmin.createOrUpdate()`.

This is controlled by the `infinispan.remote.use-schema-registration` property, which defaults to `true`.

NOTE: The auto-discovery scans packages registered with Spring Boot's `AutoConfigurationPackages` and skips internal Infinispan packages (`org.infinispan.*`).
Make sure your application uses a different base package so the scanner picks up your schema.

== What's Next

* link:/tutorials/integrations-spring-boot-cache-remote/[Spring Boot remote caching] for caching with `@Cacheable` and `@CacheEvict`
* link:/tutorials/integrations-quarkus-infinispan-client-example/[Quarkus Infinispan Client] for the same pattern with Quarkus
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<relativePath>../../../pom.xml</relativePath>
<version>16.2.1</version>
<groupId>org.infinispan.tutorial.simple</groupId>
<artifactId>infinispan-simple-tutorials</artifactId>
</parent>

<artifactId>infinispan-spring-boot-cache-remote-schema-auto-registration</artifactId>
<name>Infinispan Simple Tutorials: Spring Boot Cache Remote Schema Auto Registration</name>

<dependencyManagement>
<dependencies>
<!-- override spring boot dependency of infinispan -->
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-bom</artifactId>
<version>${version.infinispan}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${version.spring.boot4}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${version.junit5}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-spring-boot4-starter-remote</artifactId>
</dependency>

<dependency>
<groupId>org.infinispan.protostream</groupId>
<artifactId>protostream-processor</artifactId>
<version>${version.protostream}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>testcontainers-infinispan</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan.tutorial.simple</groupId>
<artifactId>connect-to-infinispan-server</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${version.spring.boot4}</version>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package tutorial.spring.infinispan;

import org.infinispan.tutorial.simple.connect.TutorialsConnectorHelper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CharactersApp {

public static void main(String[] args) {
TutorialsConnectorHelper.startInfinispanContainer();
if (TutorialsConnectorHelper.isContainerStarted()) {
String connectionUri = String.format("hotrod://%s:%s@127.0.0.1:%d",
TutorialsConnectorHelper.USER,
TutorialsConnectorHelper.PASSWORD,
TutorialsConnectorHelper.INFINISPAN_CONTAINER.getFirstMappedPort());
SpringApplication.run(CharactersApp.class,
"--infinispan.remote.connection-uri=" + connectionUri);
} else {
SpringApplication.run(CharactersApp.class, args);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package tutorial.spring.infinispan;

import java.util.Collections;
import java.util.Set;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import tutorial.spring.infinispan.model.Character;
import tutorial.spring.infinispan.service.CharacterSearch;

// tag::resource[]
@RestController
@RequestMapping("/characters")
public class CharactersResource {

private final CharacterSearch searchService;

public CharactersResource(CharacterSearch searchService) {
this.searchService = searchService;
}

@GetMapping("/{id}")
public Character byId(@PathVariable("id") String id) {
Character character = searchService.getById(id);
if (character == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
"Character with id of " + id + " does not exist.");
}
return character;
}

@GetMapping("/query")
public Set<String> searchCharacter(@RequestParam(value = "term", required = false) String term) {
if (term == null) {
return Collections.emptySet();
}
return searchService.search(term);
}
}
// end::resource[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package tutorial.spring.infinispan.model;

import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.ProtoSchema;

// tag::schema[]
@ProtoSchema(schemaPackageName = "tutorial",
includeClasses = {Character.class, Archetype.class},
dependsOn = {
org.infinispan.protostream.types.java.CommonTypes.class
})
public interface AppSchema extends GeneratedSchema {
}
// end::schema[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package tutorial.spring.infinispan.model;

import org.infinispan.protostream.annotations.Proto;

@Proto
public enum Archetype {
HERO,
MENTOR,
HERALD,
SHADOW,
ALLY,
TRICKSTER,
SHAPESHIFTER
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package tutorial.spring.infinispan.model;

import java.util.UUID;

import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.api.annotations.indexing.Text;
import org.infinispan.protostream.annotations.Proto;

// tag::entity[]
@Proto
@Indexed
public record Character(UUID id, @Text String name, @Text String bio, Archetype archetype) {

}
// end::entity[]
Loading