spring-data-examples Documentation

Spring Data Example Projects

5,424 stars Java Java 43 files ยท ~16,911 tokens
RAW Doc

Technical Documentation: spring-projects/spring-data-examples

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Overview (`multi-store/README.md`)

Spring Data - Multi-store example

This sample shows a project working with multiple Spring Data modules and how the repository auto-detection has become stricter with the Evans release train.

If you run ApplicationConfigurationTest you should see the following output:

text
โ€ฆ DEBUG โ€ฆ - Multiple Spring Data modules found, entering strict repository configuration mode!
โ€ฆ 
โ€ฆ DEBUG โ€ฆ - Spring Data JPA - Could not safely identify store assignment for repository candidate interface example.springdata.multistore.shop.OrderRepository.
โ€ฆ DEBUG โ€ฆ - Spring Data JPA - Registering repository: customerRepository - Interface: example.springdata.multistore.customer.CustomerRepository - Factory: org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean
โ€ฆ DEBUG โ€ฆ - Multiple Spring Data modules found, entering strict repository configuration mode!
โ€ฆ
โ€ฆ DEBUG โ€ฆ - Spring Data MongoDB - Could not safely identify store assignment for repository candidate interface example.springdata.multistore.customer.CustomerRepository.
โ€ฆ DEBUG โ€ฆ - Spring Data MongoDB - Registering repository: orderRepository - Interface: example.springdata.multistore.shop.OrderRepository - Factory: org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean

As you can see, Spring Data detects the fact that the application runs on multiple Spring Data modules. This triggers the strict configuration mode in which only repository interfaces will be detected that can be uniquely assigned to the module currently scanned. By default this assignment is detected by inspecting the managed domain type for store specific annotations (e.g. @Entity for JPA or @Document for MongoDB).


Overview (`cassandra/kotlin/README.md`)

Spring Data Cassandra - Kotlin examples

This project contains samples of Kotlin-specific features of Spring Data (Cassandra).

Value defaulting on entity construction

Kotlin allows defaulting for constructor- and method arguments.
Defaulting allows usage of substitute values if a field in the document is absent or simply null.
Spring Data inspects objects whether they are Kotlin types and uses the appropriate constructor.

kotlin
@Table
data class Person(@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED) val firstname: String? = "", val lastname: String = "White")

operations.cqlOperations.execute(QueryBuilder.insertInto("person").value("firstname", "Walter"))

val person = operations.query<Person>()
				.matching(query(where("firstname").isEqualTo("Walter")))
				.firstValue()!!

assertThat(person.lastname).isEqualTo("White")

Kotlin Extensions

Spring Data exposes methods accepting a target type to either query for or to project results values on.
Kotlin represents classes with its own type, KClass which can be an obstacle when attempting to obtain a Java Class type.

Spring Data ships with extensions that add overloads for methods accepting a type parameter by either leveraging generics or accepting KClass directly.

kotlin
operations.getTableName<Person>()
 
operations.getTableName(Person::class)

Nullability

Declaring repository interfaces using Kotlin allows expressing nullability constraints on arguments and return types. Spring Data evaluates nullability of arguments and return types and reacts to these. Passing null to a non-nullable argument raises an IllegalArgumentException, as you're already used to from Kotlin. Spring Data helps you also to prevent null in query results. If you wish to return a nullable result, use Kotlin's nullability marker ?. To prevent null results, declare the return type of a query method as non-nullable. In the case a query yields no result, a non-nullable query method throws EmptyResultDataAccessException.

kotlin
interface PersonRepository : CrudRepository<Person, String> {

	/**
	 * Query method declaring a nullable return type that allows to return null values.
	 */
	fun findOneOrNoneByFirstname(firstname: String): Person?

	/**
	 * Query method declaring a nullable argument.
	 */
	fun findNullableByFirstname(firstname: String?): Person?

	/**
	 * Query method requiring a result. Throws [org.springframework.dao.EmptyResultDataAccessException] if no result is found.
	 */
	fun findOneByFirstname(firstname: String): Person
}

Overview (`cassandra/reactive/README.md`)

Spring Data Cassandra 2.0 - Reactive examples

This project contains samples of reactive data access features with Spring Data (Cassandra).

Reactive Template API usage with `ReactiveCassandraTemplate`

The main reactive Template API class is ReactiveCassandraTemplate, ideally used through its interface ReactiveCassandraOperations. It defines a basic set of reactive data access operations using Project Reactor Mono and Flux reactive types.

java
template.insert(Flux.just(new Person("Walter", "White", 50),
				new Person("Skyler", "White", 45),
				new Person("Saul", "Goodman", 42),
				new Person("Jesse", "Pinkman", 27)));

Flux<Person> flux = template.select(select()
                                         .from("person")
                                         .where(eq("lastname", "White")), Person.class);

The test cases in ReactiveCassandraTemplateIntegrationTest show basic Template API usage.
Reactive data access reads and converts individual elements while processing the stream.

Reactive Repository support

Spring Data Cassandra provides reactive repository support with Project Reactor and RxJava 1 reactive types. The reactive API supports reactive type conversion between reactive types.

java
public interface ReactivePersonRepository extends ReactiveCrudRepository<Person, String> {

	Flux<Person> findByLastname(String lastname);

	@Query("SELECT * FROM person WHERE firstname = ?0 and lastname  = ?1")
	Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);

	// Accept parameter inside a reactive type for deferred execution
	Flux<Person> findByLastname(Mono<String> lastname);

	Mono<Person> findByFirstnameAndLastname(Mono<String> firstname, String lastname);
}
java
public interface RxJava1PersonRepository extends RxJava1CrudRepository<Person, String> {

	Observable<Person> findByLastname(String lastname);

	@Query("SELECT * FROM person WHERE firstname = ?0 and lastname  = ?1")
	Single<Person> findByFirstnameAndLastname(String firstname, String lastname);

	// Accept parameter inside a reactive type for deferred execution
	Observable<Person> findByLastname(Single<String> lastname);

	Single<Person> findByFirstnameAndLastname(Single<String> firstname, String lastname);
}

Overview (`cassandra/vector-search/README.md`)

Spring Data for Apache Cassandra - Vector Search Example

This project
contains Vector Search
with Spring Data for Apache Cassandra.

Vector Support

The Spring Data Vector type can be used in repository query methods.
Domain type properties of managed domain types are required to use a numeric array representation for embeddings.

java
@Table
public class Comment {

    @Id
    private String id;

    private String country;
    private String description;

    @SaiIndexed
    @VectorType(dimensions = 5)
    private Vector embedding;

    // ...
}


public interface CommentRepository extends Repository<Comment, String> {

    SearchResults<Comment> searchTop10ByEmbeddingNear(Vector embedding, ScoringFunction function);
}

This example contains a test class to illustrate vector search with a Repository in CassandraVectorSearchTest.


Overview (`couchbase/example/README.md`)

Spring Data Couchbase - Examples

This project contains samples of data access features with Spring Data (Couchbase).

Prerequisites

The examples require a running Couchbase Server with the travel sample bucket imported. We assume you're running Couchbase 6.5 and we have updated the application.properties class accordingly to adapt RBAC authentication.

For more information, see the official documentation.


Overview (`couchbase/reactive/README.md`)

Spring Data Couchbase 4.0 - Reactive examples

This project contains samples of reactive data access features with Spring Data (Couchbase).

Reactive Repository support

Spring Data Couchbase provides reactive repository support with Project Reactor:

java
public interface ReactiveAirlineRepository extends ReactiveCrudRepository<Airline, String> {

    Mono<Airline> findByIata(String code);

    Flux<Airline> findAllBy();
}

For more information, see the official documentation.


Overview (`couchbase/transactions/README.md`)

Getting Started

Running the Sample

  • $> docker run -d --name db -p 8091-8097:8091-8097 -p 11210:11210 -p 11207:11207 -p 18091-18095:18091-18095 -p 18096:18096 -p 18097:18097 couchbase
  • Create new cluster via http://localhost:8091/
  • Create new bucket named travel-sample.

Reference Documentation

For further reference, please consider the following sections:


Overview (`elasticsearch/example/README.md`)

Spring Data Elasticsearch - Examples

Requirements:

To use local elasticsearch cluster:

  • install elasticsearch
  • Adapt 'application.properties' to point to your elasticsearch cluster

Overview (`elasticsearch/reactive/README.md`)

Spring Data Elasticsearch - Reactive Examples

The ReactiveElasticsearchClient is a client based on WebClient.
It uses the request/response objects provided by the Elasticsearch core project.
Calls are directly operated on the reactive stack, not wrapping async (thread pool bound) responses into reactive types.

java
@SpringBootApplication
class ApplicationConfiguration {}

The ReactiveElasticsearchClient can be used with the ReactiveElasticsearchOperations and ReactiveElasticsearchRepository.

java
@Autowired ReactiveElasticsearchOperations operations;

// ...

CriteriaQuery query = new CriteriaQuery("keywords").contains("java");

Flux<Conference> result = operations.find(query, Conference.class);
java
interface ConferenceRepository extends ReactiveCrudRepository<Conference, String> {
 
    Flux<Conference> findAllByKeywordsContains(String keyword);
}

// ...

@Autowired ConferenceRepository repository;

// ...

Flux<Conference> result = repository.findAllByKeywordsContains("java");

Requirements:

Running Elasticsearch

bash
$ cd elasticsearch
$ ./bin/elasticsearch

Overview (`jpa/jpa21/README.md`)

Spring Data JPA - JPA 2.1 example

This project contains samples of JPA 2.1 specific features of Spring Data JPA.

Support for declarative Fetch Graphs customization

You can customize the loading of entity associations via EntityGraphs. JPA 2.1 provides the NamedEntityGraph annotation
that allows you to define fetching behavior in a flexible way.

In Spring Data JPA we support to specify which fetch-graph to use for a repository query method via the EntityGraph annotation.

You can refer to a fetch graph by name like in the following example.

java
@EntityGraph("product-with-tags")
Product findOneById(Long id);

We also offer an alternative and more concise way to declaratively specify a fetch graph for a repository query method in an
ad-hoc manner:

java
@EntityGraph(attributePaths = "tags")
Product getOneById(Long id);

By explicitly specifying which associations to fetch via the attributePaths attribute you don't need to specify a
NamedEntityGraph annotation on your entity :)

Support for stored procedure execution

You can execute stored procedures either predefined using the JPA 2.1 mapping annotations or dynamically let the stored procedure definition be derived from the repository method name.

Stored procedure declaration in the database (schema.sql):

sql
DROP procedure IF EXISTS plus1inout
/;
CREATE procedure plus1inout (IN arg int, OUT res int)
BEGIN ATOMIC
    set res = arg + 1;
END
/;

JPA 2.1 stored procedure declaration:

java
@Entity
@NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = {
  @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
  @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
public class User {

    @Id @GeneratedValue//
    private Long id;
}

Spring Data JPA repository declaration to execute procedures:

java
public interface UserRepository extends CrudRepository<User, Long> {

  // Explicitly mapped to named stored procedure {@code User.plus1} in the {@link EntityManager}.
  // By default, we would've tried to find a procedure declaration named User.plus1BackedByOtherNamedStoredProcedure
  @Procedure(name = "User.plus1")
  Integer plus1BackedByOtherNamedStoredProcedure(@Param("arg") Integer arg);

  // Directly map the method to the stored procedure in the database (to avoid the annotation madness on your domain classes).
  @Procedure
  Integer plus1inout(Integer arg);
}

Calling UserRepository.plus1BackedByOtherNamedStoredProcedure(โ€ฆ) will execute the stored procedure plus1inout using the meta-data declared on the User domain class.

UserRepository.plus1inout(โ€ฆ) will derive stored procedure metadata from the repository and default to positional parameter binding and expect a single output parameter of the backing stored procedure.

Support for custom SqlResultSetMapping with ConstructorResult

Sometimes, e.g. for analytics, it is handy to be able to return a different entity result type from a Repository query method than the base Repository entity type or an interface based projection.

In those cases one can leverage JPAs SqlResultSetMapping feature to map the columns of the result of a query to different fields.

JPA 2.1 introduced the new SqlResultSetMapping type ConstructorResult which allows to map columns of a result set row to a constructor invocation
which can be nicely used in combination with Value Objects.

This example shows how to define a custom SqlResultSetMapping for the result of an analytical native query that reports the usage summary for a set of Subscriptions.

SqlResultSetMapping definition on the Subscription entity class:

java
@Entity
@NoArgsConstructor
@SqlResultSetMapping(
  name = "subscriptionSummary",
  classes = @ConstructorResult(
    targetClass = SubscriptionSummary.class,
    columns = {
      @ColumnResult(name = "productName", type = String.class),
      @ColumnResult(name = "subscriptions", type = long.class)
}))
@NamedNativeQuery(
  name = "Subscription.findAllSubscriptionSummaries",
  query = "select product_name as productName, count(user_id) as subscriptions from subscription group by product_name order by productName",
  resultSetMapping = "subscriptionSummary"
)
@Data
public class Subscription {
  โ€ฆ
}

SubscriptionSummary is modelled as a value object:

java
@Value
public class SubscriptionSummary {

  String product;
  Long usageCount;
}

The SubscriptionRepository declares the custom query method findAllSubscriptionSummaries which is backed by the named native query declared on the Subscription entity.

java
interface SubscriptionRepository extends CrudRepository<Subscription,Long> {

  @Query(nativeQuery = true)
  List<SubscriptionSummary> findAllSubscriptionSummaries();
}

Overview (`jpa/multiple-datasources/README.md`)

Spring Data JPA - Multiple datasources example

This project contains an example of how to set up Spring Data to work with two independent databases. Note, that this example is not about JTA transactions spanning multiple databases but rather just interacting with separate databases through a cleanly separated domain model and Spring Data repositories.

Interesting points to look at

To connect to two databases we need to manually configure a DataSource, EntityManagerFactory and JpaTransactionManager. This is done in CustomerConfig and OrderConfig. DataInitializer then simulates a Spring component interacting with those databases in separate methods. Application.init() orchestrates calls to those methods.


Overview (`jpa/query-by-example/README.md`)

Spring Data JPA - Query-by-Example (QBE) example

This project contains samples of Query-by-Example of Spring Data JPA.

Support for Query-by-Example

Query by Example (QBE) is a user-friendly querying technique with a simple interface. It
allows dynamic query creation and does not require to write queries containing field
names. In fact, Query by Example does not require to write queries using JPA-QL at all.

An Example takes a data object (usually the entity object or a subtype of it) and a
specification how to match properties. You can use Query by Example with JPA Repositories.

java
public interface PersonRepository extends CrudRepository<Person, String>, QueryByExampleExecutor<Person> {
}
java
Example<Person> example = Example.of(new Person("Jon", "Snow"));
repo.findAll(example);


ExampleMatcher matcher = ExampleMatcher.matching().
    .withMatcher("firstname", endsWith())
    .withMatcher("lastname", startsWith().ignoreCase());

Example<Person> example = Example.of(new Person("Jon", "Snow"), matcher); 
repo.count(example);

This example contains a test class to illustrate Query-by-Example with a Repository
in UserRepositoryIntegrationTests.


Overview (`jpa/vector-search/README.md`)

Spring Data JPA - Vector Search Example

This project contains Vector Search with Spring Data JPA and the hibernate-vector module.

Vector Support

The Spring Data Vector type can be used in repository query methods.
Domain type properties of managed domain types are required to use a numeric array representation for embeddings.

java
@Entity
@Table(name = "jpa_comment")
public class Comment {

    @Id
    @GeneratedValue private Long id;

    private String country;
    private String description;

    @JdbcTypeCode(SqlTypes.VECTOR)
    @Array(length = 5)
    private float[] embedding;

    // ...
}


public interface CommentRepository extends Repository<Comment, String> {

    SearchResults<Comment> searchTop10ByCountryAndEmbeddingNear(String country, Vector vector, Score distance);
}

This example contains a test class to illustrate vector search with a Repository in JpaVectorSearchTest.


Overview (`ldap/example/README.md`)

Spring Data LDAP - Example

This project contains samples of Spring Data (LDAP).

java
public interface PersonRepository extends CrudRepository<Person, Name> {

  List<Person> findByUid(String uid);

  List<Person> findByLastnameStartsWith(String prefix);
}

The test cases in PersonRepositoryIntegrationTests show basic interaction to search, create and modify objects stored in an LDAP repository.


Overview (`mongodb/aggregation/README.md`)

Spring Data MongoDB - Aggregations

This project contains samples for using MongoDB Aggregations
showing both the programmatic and declarative approach for integrating an Aggregation Pipeline into a repository.

Programmatic

The programmatic approach uses a custom repository implementation along with the Aggregation Framework.

java
class OrderRepositoryImpl implements OrderRepositoryCustom {

	private MongoOperations operations;
	
	// ...

	@Override
	public Invoice getInvoiceFor(Order order) {

		AggregationResults<Invoice> results = operations.aggregate(newAggregation(Order.class, 
			match(where("id").is(order.getId())), 
			unwind("items"), 
			project("id", "customerId", "items") 
				.andExpression("'$items.price' * '$items.quantity'").as("lineTotal"), 
			group("id") 
				.sum("lineTotal").as("netAmount") 
				.addToSet("items").as("items"), 
			project("id", "items", "netAmount") 
				.and("orderId").previousOperation() 
				.andExpression("netAmount * [0]", taxRate).as("taxAmount") 
				.andExpression("netAmount * (1 + [0])", taxRate).as("totalAmount") 
		), Invoice.class);

		return results.getUniqueMappedResult();
	}
}

Declarative

The declarative approach allows to define an Aggregation Pipeline via the @Aggregation annotation.

java
public interface OrderRepository extends CrudRepository<Order, String>, OrderRepositoryCustom {

	@Aggregation("{ $group : { _id : $customerId, total : { $sum : 1 } } }")
	List<OrdersPerCustomer> totalOrdersPerCustomer(Sort sort);

	@Aggregation(pipeline = { "{ $match : { customerId : ?0 } }", "{ $count : total }" })
	Long totalOrdersForCustomer(String customerId);
}

Overview (`mongodb/change-streams/README.md`)

Spring Data MongoDB - Change Streams Example

This project contains usage samples for consuming MongoDB 3.6 Change Streams using the imperative as well as the reactive MongoDB Java drivers.

Imperative Style

Change stream events can be consumed using a MessageListener registered within a MessageListenerContainer. The container takes care of running the task in a separate Thread pushing events to the MessageListener.

java
@Configuration
class Config {

	@Bean
	MessageListenerContainer messageListenerContainer(MongoTemplate template) {
		return new DefaultMessageListenerContainer(template);
	}
}

Once the MessageListenerContainer is in place MessageListeners can be registered.

java
MessageListener<ChangeStreamDocument<Document>, Person> messageListener = (message) -> {
	System.out.println("Hello " + message.getBody().getFirstname());
};

ChangeStreamRequest<Person> request = ChangeStreamRequest.builder()
	.collection("person")
	.filter(newAggregation(match(where("operationType").is("insert"))))
	.publishTo(messageListener)
	.build();

Subscription subscription = messageListenerContainer.register(request, Person.class);

// ...

Reactive Style

Change stream events be directly consumed via a Flux connected to the change stream.

java
Flux changeStream = reactiveTemplate
	.changeStream(newAggregation(match(where("operationType").is("insert"))),
				Person.class, ChangeStreamOptions.empty(), "person");
				
changeStream.doOnNext(event -> System.out.println("Hello " + event.getBody().getFirstname()))
	.subscribe();

Overview (`mongodb/example/README.md`)

Spring Data MongoDB - Example

This project contains examples for:

  • Derived query method and geospatial queries using a Repository interface.
  • Usage of property-specific converters.
  • EntityCallback API usage for before convert/save interaction.
  • Result projections for DTOs and interface types.
  • Bulk write to multiple collections.
  • Query metadata.
  • Unwrapping entities into the parent document.

Overview (`mongodb/fluent-api/README.md`)

Spring Data MongoDB 2.0 - FluentMongoOperations Example

This project contains usage samples of FluentMongoOperations.

Fluent API

FluentMongoOperations provides a stripped down, more focused API alternative for classic MongoOperations.
The main entry points are typical tasks for finding and manipulating Documents on a domain type base, while operations like creating indexes are left out.

For convenience classic MongoOperations extend FluentMongoOperations, however for most cases it might be sufficient to just work with the reduced interface.
To get started just inject FluentMongoOperations.

The entry point methods of FluentMongoOperations provide you with an immutable fluent API allowing only valid next steps while constructing the operation to execute. This allows to set up operations once and keep those in memory for multiple executions.

Query

Looking at the following query(SWCharacter.class).

SWCharacter is used for mapping the query properties to the actual document field names. As the SWCharacter defines @Field("firstname") name; the where clause of the query is mapped to the MongoDB representation as { firstname : "luke" }.

Using as(Jedi.class) switches return type mapping from SWCharacter to Jedi which allows to map resulting documents to a different type than the one used for query mapping.

So far no actual query execution has been invoked. Calling on of the terminating methods like one(), first(), all(),... triggers the query.

java
mongoOps.query(SWCharacter.class)
  .inCollection("star-wars")
  .as(Jedi.class)
  .matching(query(where("name").is("luke")))
  .one();

Different stages in the command assembly process allow to seamlessly switch to different API paths. Using near instead of matching switches to the path for geo queries requiring the presence of a NearQuery while altering the command result type from List to GeoResults and limiting terminating operations to just all().

java
NearQuery alderaanWithin3Parsecs = NearQuery.near(-73.9667, 40.78)
  .maxDistance(new Distance(3, MILES))
  .spherical(true);

GeoResults<Jedi> results = mongoOps.query(SWCharacter.class)
  .as(Jedi.class)
  .near(alderaanWithin3Parsecs)
  .all();

Update

Looking at the following update(Jedi.class).

Jedi already defines the collection via the @Document annotation, so there is no need to explicitly specify a collection name via inCollection(String). The Jedi domain type is also used for query and update mapping.

So far no actual query execution has been invoked. Calling on of the terminating methods like all(), upsert(), findAndModify(), etc. triggers the update.

java
mongoOps.update(Jedi.class)
  .matching(query(where("lastname").is("windu")))
  .apply(update("name", "mence"))
  .upsert();

Overview (`mongodb/geo-json/README.md`)

Spring Data MongoDB - GeoJSON examples

This project contains samples of GeoJSON specific features of Spring Data (MongoDB).

Support for GeoJSON types in domain classes

Using GeoJSON types in domain classes is straight forward. The org.springframework.data.mongodb.core.geo package contains types like GeoJsonPoint or GeoJsonPolygon which are extensions to the existing org.springframework.data.geo types.
Find more information in the MongoDB manual on GeoJSON support to learn about requirements and restrictions.

java
public class Store {

	String id;

	/**
	 * location is stored in GeoJSON format.
	 * {
	 *   "type" : "Point",
	 *   "coordinates" : [ x, y ]
	 * }
	 */
	GeoJsonPoint location;
}

Support for GeoJSON types in repository methods

Using GeoJson types as repository query parameters forces usage of the $geometry operator when creating the query.

java
public interface StoreRepository extends CrudRepository<Store, String> {

	List<Store> findByLocationWithin(Polygon polygon);
}
java
/* {
 *   "location": {
 *     "$geoWithin": {
 *       "$geometry": {
 *         "type": "Polygon",
 *         "coordinates": [
 *           [
 *             [-73.992514,40.758934],
 *             [-73.961138,40.760348],
 *             [-73.991658,40.730006],
 *             [-73.992514,40.758934]
 *           ]
 *         ]
 *       }
 *     }
 *   }
 * }
 */
repo.findByLocationWithin(
  new GeoJsonPolygon(
    new Point(-73.992514, 40.758934),
    new Point(-73.961138, 40.760348),
    new Point(-73.991658, 40.730006),
    new Point(-73.992514, 40.758934)));

/* {
 *   "location" : {
 *     "$geoWithin" : {
 *        "$polygon" : [ [ -73.992514, 40.758934 ] , [ -73.961138, 40.760348 ] , [ -73.991658, 40.730006 ] ]
 *     }
 *   }
 * }
 */
repo.findByLocationWithin(
  new Polygon(
    new Point(-73.992514, 40.758934),
    new Point(-73.961138, 40.760348),
    new Point(-73.991658, 40.730006));

Overview (`mongodb/gridfs/README.md`)

Spring Data MongoDB - GridFS example

This project contains an example of GridFS specific features of Spring Data (MongoDB).

Support for storing a file

Using GridFsOperations to store a file.

java
InputStream is = ...
gridFsOperations.store(is, "myFile1.txt");

Support for query a file by name

Using GridFsOperations to query a GridFSDBFile by its filename.

java
GridFSDBFile gridFsFile = gridFsOperations.findOne(query(whereFilename().is("myFile1.txt")));

Support for storing a file with metadata

Using GridFsOperations to store a file with additional metadata..

java
InputStream is = ...
Customer customerMetaData = new Customer("Hardy", "Lang");
gridFsOperations.store(is, "myCustomerFile.txt", customerMetaData);

Support for query a file by metadata

Using GridFsOperations to query a GridFSDBFile by metadata.

java
GridFSDBFile gridFsFile = gridFsOperations.findOne(query(whereMetaData("firstName").is("Hardy")));

Overview (`mongodb/kotlin/README.md`)

Spring Data MongoDB - Kotlin examples

This project contains samples of Kotlin-specific features of Spring Data (MongoDB).

Value defaulting on entity construction

Kotlin allows defaulting for constructor- and method arguments.
Defaulting allows usage of substitute values if a field in the document is absent or simply null.
Spring Data inspects objects whether they are Kotlin types and uses the appropriate constructor.

kotlin
data class Person (@Id val id: String?, val firstname: String? = "Walter", val lastname: String)

operations.insert<Document>().inCollection("person").one(Document("lastname", "White"))

val walter = operations.findOne<Document>(query(where("lastname").isEqualTo("White")), "person")

assertThat(walter.firstname).isEqualTo("Walter")

Kotlin Extensions

Spring Data exposes methods accepting a target type to either query for or to project results values on.
Kotlin represents classes with its own type, KClass which can be an obstacle when attempting to obtain a Java Class type.

Spring Data ships with extensions that add overloads for methods accepting a type parameter by either leveraging generics or accepting KClass directly.

kotlin
operations.getCollectionName<Person>()
 
operations.getCollectionName(Person::class)

Nullability

Declaring repository interfaces using Kotlin allows expressing nullability constraints on arguments and return types. Spring Data evaluates nullability of arguments and return types and reacts to these. Passing null to a non-nullable argument raises an IllegalArgumentException, as you're already used to from Kotlin. Spring Data helps you also to prevent null in query results. If you wish to return a nullable result, use Kotlin's nullability marker ?. To prevent null results, declare the return type of a query method as non-nullable. In the case a query yields no result, a non-nullable query method throws EmptyResultDataAccessException.

kotlin
interface PersonRepository : CrudRepository<Person, String> {

	/**
	 * Query method declaring a nullable return type that allows to return null values.
	 */
	fun findOneOrNoneByFirstname(firstname: String): Person?

	/**
	 * Query method declaring a nullable argument.
	 */
	fun findNullableByFirstname(firstname: String?): Person?

	/**
	 * Query method requiring a result. Throws [org.springframework.dao.EmptyResultDataAccessException] if no result is found.
	 */
	fun findOneByFirstname(firstname: String): Person
}

Type-Safe Kotlin Mongo Query DSL

Using the Criteria extensions allows to write type-safe queries via an idiomatic API.

kotlin
operations.find<Person>(Query(Person::firstname isEqualTo "Tyrion"))

Coroutines and Flow support

kotlin
runBlocking {
	operations.find<Person>(Query(where("firstname").isEqualTo("Tyrion"))).awaitSingle()
}
kotlin
runBlocking {
	operations.findAll<Person>().asFlow().toList()
}

Overview (`mongodb/linking/README.md`)

Spring Data MongoDB - Linking Example

This project contains examples for linking Documents using:

  • @DBRef
  • @DocumentReference

DBRef

Use MongoDB native dbref for storing associations.

java
class Manager {
	
    @DBRef
    private List<Employee> employees;

    // ...
}

DocumentReference

Link documents via their id.

java
class Manager {
	
    @DocumentReference
    private List<Employee> employees;

    // ...
}

Link documents via a (non id) property.

java
class Manager {
	
    @DocumentReference(lookup = "{ 'name' : ?#{#target} }")
    private List<Employee> employees;

    // ...
}

Link documents JPA style.

java
class Manager {

    @ReadOnlyProperty
    @DocumentReference(lookup="{'managerId':?#{#self._id} }")
    private List<Employee> employees;

    // ...
}

For more information please refer to the Reference Documentation.


Overview (`mongodb/query-by-example/README.md`)

Spring Data MongoDB - Query-by-Example (QBE) example

This project contains samples of Query-by-Example of Spring Data MongoDB.

Support for Query-by-Example

Query by Example (QBE) is a user-friendly querying technique with a simple interface. It
allows dynamic query creation and does not require to write queries containing field
names. In fact, Query by Example does not require to write queries using BSON at all.

An Example takes a data object (usually the entity object or a subtype of it) and a
specification how to match properties. You can use Query by Example with MongoOperations
and Repositories.

java
interface PersonRepository extends CrudRepository<Person, String>, QueryByExampleExecutor<Person> {
}
java
Example<Person> example = Example.of(new Person("Jon", "Snow"));
repo.findAll(example);


ExampleMatcher matcher = ExampleMatcher.matching().
    .withMatcher("firstname", endsWith())
    .withMatcher("lastname", startsWith().ignoreCase());

Example<Person> example = Example.of(new Person("Jon", "Snow"), matcher); 
repo.count(example);

This example contains two test classes to illustrate Query-by-Example with MongoOperations in MongoOperationsIntegrationTests and the usage with a Repository in UserRepositoryIntegrationTests.


Overview (`mongodb/querydsl/README.md`)

Spring Data MongoDB - Querydsl example

This project contains samples of Querydsl usage in Spring Data MongoDB.

Querydsl is a framework which enables the construction of fluent, type-safe queries for multiple backends including MongoDB.
Spring Data integrates with Querydsl via QuerydslPredicateExecutor and its reactive counterpart ReactiveQuerydslPredicateExecutor.

NOTE: You may have to run mvn compile to generate the required Q classes first.

Imperative

java
interface CustomerQuerydslRepository 
        extends CrudRepository<Customer, String>, QuerydslPredicateExecutor<Customer> { }

@Autowired CustomerQuerydslRepository repository;

// ...

List<Customer> result = repository.findAll(QCustomer.customer.lastname.eq("Matthews"));

Reactive

java
interface ReactiveCustomerQuerydslRepository
		extends ReactiveCrudRepository<Customer, String>, ReactiveQuerydslPredicateExecutor<Customer> { }
		
@Autowired ReactiveCustomerQuerydslRepository repository;

// ...

Flux<Customer> result = repository.findAll(QCustomer.customer.lastname.eq("Matthews"));

Overview (`mongodb/reactive/README.md`)

Spring Data MongoDB 2.0 - Reactive examples

This project contains samples of reactive data access features with Spring Data (MongoDB).

Prerequisites

MongoDB requires the Reactive Streams driver to provide reactive data access.
The Reactive Streams driver maintains its own connections. Using Spring Data MongoDB Reactive support
together with blocking Spring Data MongoDB data access will open multiple connections to your MongoDB servers.

Reactive Template API usage with `ReactiveMongoTemplate`

The main reactive Template API class is ReactiveMongoTemplate, ideally used through its interface ReactiveMongoOperations. It defines a basic set of reactive data access operations using Project Reactor Mono and Flux reactive types.

java
template.insertAll(Arrays.asList(new Person("Walter", "White", 50),
				new Person("Skyler", "White", 45),
				new Person("Saul", "Goodman", 42),
				new Person("Jesse", "Pinkman", 27)));

Flux<Person> flux = template.find(Query.query(Criteria.where("lastname").is("White")), Person.class);

The test cases in ReactiveMongoTemplateIntegrationTest show basic Template API usage.
Reactive data access reads and converts individual elements while processing the stream.

Reactive Repository support

Spring Data MongoDB provides reactive repository support with Project Reactor and RxJava 2 reactive types. The reactive API supports reactive type conversion between reactive types.

java
public interface ReactivePersonRepository extends ReactiveCrudRepository<Person, String> {

	Flux<Person> findByLastname(String lastname);

	@Query("{ 'firstname': ?0, 'lastname': ?1}")
	Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);

	// Accept parameter inside a reactive type for deferred execution
	Flux<Person> findByLastname(Mono<String> lastname);

	Mono<Person> findByFirstnameAndLastname(Mono<String> firstname, String lastname);

	@Tailable // Use a tailable cursor
	Flux<Person> findWithTailableCursorBy();
}
java
public interface RxJava2PersonRepository extends RxJava2CrudRepository<Person, String> {

	Flowable<Person> findByLastname(String lastname);

	@Query("{ 'firstname': ?0, 'lastname': ?1}")
	Maybe<Person> findByFirstnameAndLastname(String firstname, String lastname);

	// Accept parameter inside a reactive type for deferred execution
	Flowable<Person> findByLastname(Single<String> lastname);

	Maybe<Person> findByFirstnameAndLastname(Single<String> firstname, String lastname);

	@Tailable // Use a tailable cursor
	Flowable<Person> findWithTailableCursorBy();
}

Overview (`mongodb/repository-metrics/README.md`)

Spring Data MongoDB 3.1 - Repository Metrics

Configure a RepositoryMethodInvocationListener to capture invocation metrics on Repository interfaces.

java
@Configuration(proxyBeanMethods = false)
class RepositoryMetricsConfiguration {

	@Bean
	public RepoMetricsPostProcessor repoMetricsPostProcessor() {
		return new RepoMetricsPostProcessor();
	}

	static class RepoMetricsPostProcessor implements BeanPostProcessor {

		@Override
		public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

			if (bean instanceof RepositoryFactoryBeanSupport<?, ?, ?> repositoryFactoryBean) {

				repositoryFactoryBean
						.addRepositoryFactoryCustomizer(repositoryFactory -> {
							repositoryFactory
									.addInvocationListener(System.out::println);  // register the invocation listener
						});
			}

			return bean;
		}
	}
}

Overview (`mongodb/schema-validation/README.md`)

Spring Data MongoDB 2.1 - Schema & Validation Example

MongoDB (as of version 3.2) supports validating documents against a given structure described by a query.

json
{
    "name" : {
        "$exists" : true,
        "$ne" : null,
        "$type" : 2
    },
    "age" : {
        "$exists" : true,
        "$ne" : null,
        "$type" : 16,
        "$gte" : 0,
        "$lte" : 125
    }
}

The structure can be built from Criteria objects in the same way as they are used for defining queries.

java
Validator.criteria(where("name").exists(true).ne(null).type(2)
	.and("age").exists(true).ne(null).type(16).gte(0).lte(125));

MongoDB 3.6 supports collections that validate documents against a provided JSON Schema that
complies with the JSON schema specification (draft 4).

json
{
  "type": "object",
  "required": [ "name", "age" ],
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1
    },
    "age": {
      "type": "int",
      "minimum" : 0,
      "exclusiveMinimum" : false,
      "maximum" : 125,
      "exclusiveMaximum" : false
    }
  }
}

The MongoJsonSchema and its builder allow fluent schema definition via the Java API.

java
MongoJsonSchema schema = MongoJsonSchema.builder() //
    .required("name", "age") //
    .properties( //
        string("name").minLength(1), //
        int32("age").gte(0).lte(125) //
    ).build();

The schema can be used for various functionality: Set up Document validation for a collection:

java
template.createCollection(Jedi.class, CollectionOptions.empty().validator(Validator.schema(schema)));

and to query the store for documents matching a given blueprint:

java
template.find(query(matchingDocumentStructure(schema)), Jedi.class);

Overview (`mongodb/text-search/README.md`)

Spring Data MongoDB - Text Search Examples

This project contains samples of text search specific features of Spring Data Mongodb.

Support for Text Index

Define text index structures manually (like below) or use @TextIndexed to mark content to be indexed for full text search.

java
TextIndexDefinition textIndex = new TextIndexDefinitionBuilder()
  .onField("title", 3F)
  .onField("content", 2F)
  .onField("categories")
  .build();

template.indexOps(BlogPost.class).ensureIndex(textIndex);

Support for full text repository queries

Use derived finder methods to search for terms and phrases.

java
interface BlogPostRepository extends CrudRepository<BlogPost, String> {

    // page through results for full text query
	Page<BlogPost> findBy(TextCriteria criteria, Pageable page);

    // find all matching documents and sort by relevance
	List<BlogPost> findAllByOrderByScoreDesc(TextCriteria criteria);
}

Overview (`mongodb/transactions/README.md`)

Spring Data MongoDB - Transaction Sample

This project contains samples for upcoming MongoDB 4.0 transactions.

Running the Sample

The sample uses multiple embedded MongoDB processes in a MongoDB replica set.
It contains test for both the synchronous and the reactive transaction support in the sync / reactive packages.

You may run the examples directly from your IDE or use maven on the command line.

INFO: The operations to download the required MongoDB binaries and spin up the cluster can take some time. Please
be patient.

Sync Transactions

MongoTransactionManager is the gateway to the well known Spring transaction support. It lets applications use
the managed transaction features of Spring.
The MongoTransactionManager binds a ClientSession to the thread. MongoTemplate detects the session and operates
on these resources which are associated with the transaction accordingly. MongoTemplate can also participate in
other, ongoing transactions.

java
@Configuration
static class Config extends AbstractMongoConfiguration {

	@Bean
	MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
		return new MongoTransactionManager(dbFactory);
	}

	// ...
}

@Component
public class TransitionService {

    @Transactional
    public void run(Integer id) {
    
    	Process process = lookup(id);
    
    	if (!State.CREATED.equals(process.getState())) {
    		return;
    	}
    
    	start(process);
    	verify(process);
    	finish(process);
    }
}

Programmatic Reactive transactions

ReactiveMongoTemplate offers dedicated methods (like inTransaction()) for operating within a transaction without having to worry about the
commit/abort actions depending on the operations outcome.

NOTE: Please note that you cannot preform meta operations, like collection creation within a transaction.

java
@Service
public class ReactiveTransitionService {
	
    public Mono<Integer> run(Integer id) {

		return template.inTransaction().execute(action -> {

			return lookup(id) //
			        .filter(State.CREATED::equals)
			        .flatMap(process -> start(action, process))
			        .flatMap(this::verify)
			        .flatMap(process -> finish(action, process));

		}).next().map(Process::getId);
	}
}

Declarative Reactive transactions

ReactiveMongoTransactionManager is the gateway to the reactive Spring transaction support. It lets applications use
the managed transaction features of Spring.
The ReactiveMongoTransactionManager adds the ClientSession to the reactor.util.context.Context. ReactiveMongoTemplate detects the session and operates
on these resources which are associated with the transaction accordingly.

java
@EnableTransactionManagement
class Config extends AbstractReactiveMongoConfiguration {

	@Bean
	ReactiveTransactionManager transactionManager(ReactiveMongoDatabaseFactory factory) {
		return new ReactiveMongoTransactionManager(factory);
	}
	
	// ...
}


@Service
class ReactiveManagedTransitionService {

	@Transactional
	public Mono<Integer> run(Integer id) {

		return lookup(id)
				.flatMap(process -> start(template, process))
				.flatMap(it -> verify(it)) //
				.flatMap(process -> finish(template, process))
				.map(Process::getId);
	}
}

Overview (`mongodb/vector-search/README.md`)

Spring Data MongoDB - Vector Search Example

This project
contains Vector Search
with Spring Data MongoDB.

Vector Support

The Spring Data Vector type can be used in repository query methods.
Domain type properties of managed domain types are required to use a numeric array representation for embeddings.

java
@Document
public class Comment {

    @Id
    private ObjectId id;

    private String country;
    private String description;

    private Vector embedding;

    // ...
}


public interface CommentRepository extends Repository<Comment, String> {

    @VectorSearch(indexName = "cosine-index", searchType = VectorSearchOperation.SearchType.ANN)
    SearchResults<Comment> searchTop10ByCountryAndEmbeddingNear(String country, Vector vector, Score distance);
}

This example contains a test class to illustrate vector search with a Repository in MongoDBVectorSearchTest.


Overview (`redis/cluster-sentinel/README.md`)

Spring Data Redis Example

This example was named a bit misleadingly as cluster-sentinel, a mix of Redis Cluster and Redis Sentinel.

Find dedicated examples here: Redis Cluster and Redis Sentinel.


Overview (`redis/cluster/readme.md`)

Spring Data Redis - Cluster Examples

This project contains Redis 3 Cluster specific features of Spring Data Redis.

To run the code in this sample a running cluster environment is required. Please refer to the redis cluster-tutorial for detailed information or check the Cluster Setup section below.

Support for Cluster

Cluster Support uses the same building blocks as the non-clustered counterpart. We use application.properties to point to an initial set of known cluster nodes which will be picked up by the auto-configuration.

properties
spring.redis.cluster.nodes[0]=127.0.0.1:30001
spring.redis.cluster.nodes[1]=127.0.0.1:30002
spring.redis.cluster.nodes[2]=127.0.0.1:30003

INFORMATION: The tests flush the db of all known instances during the JUnit setup phase to allow inspecting data directly on the cluster nodes after a test is run.

Cluster Setup

To quickly set up a cluster of 6 nodes (3 master | 3 slave) go to the redis/utils/create-cluster directory.

bash
redis/utils/create-cluster $ ./create-cluster start
  Starting 30001
  Starting 30002
  Starting 30003
  Starting 30004
  Starting 30005
  Starting 30006

On first initialization cluster nodes need to form the cluster by joining and assigning slot allocations.
INFO: This has to be done only once.

bash
redis/utils/create-cluster $ ./create-cluster create
  >>> Creating cluster
  >>> Performing hash slots allocation on 6 nodes...
  Using 3 masters:
    127.0.0.1:30001
    127.0.0.1:30002
    127.0.0.1:30003
  Adding replica 127.0.0.1:30004 to 127.0.0.1:30001
  Adding replica 127.0.0.1:30005 to 127.0.0.1:30002
  Adding replica 127.0.0.1:30006 to 127.0.0.1:30003

  M: 10696916f57e58c5edce34127b23ca7af1b669a0 127.0.0.1:30001
     slots:0-5460 (5461 slots) master
  M: 5b0e1b4cc87175326ba79d00ecfc6f5dbdb424a7 127.0.0.1:30002
     slots:5461-10922 (5462 slots) master
  M: 5f3e978fb40b1d9c910d904ea19a0494b78668aa 127.0.0.1:30003
     slots:10923-16383 (5461 slots) master
  S: d1717c418d03db93183ce2d791ba6f48be5cf028 127.0.0.1:30004
     replicates 10696916f57e58c5edce34127b23ca7af1b669a0
  S: c7dfcdb9cd1105e4251de51c4ade54de59bb063c 127.0.0.1:30005
     replicates 5b0e1b4cc87175326ba79d00ecfc6f5dbdb424a7
  S: 3219785a9145717f30648a27a2dd07359e9dd46f 127.0.0.1:30006
     replicates 5f3e978fb40b1d9c910d904ea19a0494b78668aa

  Can I set the above configuration? (type 'yes' to accept): yes

  [OK] All nodes agree about slots configuration.
       >>> Check for open slots...
       >>> Check slots coverage...
  [OK] All 16384 slots covered.

It is now possible to connect to the cluster using the redis-cli.

bash
redis/src $ ./redis-cli -c -p 30001
  127.0.0.1:30001> cluster nodes

  106969... 127.0.0.1:30001 myself,master - 0 0 1 connected 0-5460
  5b0e1b... 127.0.0.1:30002 master - 0 1450765112345 2 connected 5461-10922
  5f3e97... 127.0.0.1:30003 master - 0 1450765112345 3 connected 10923-16383
  d1717c... 127.0.0.1:30004 slave 106969... 0 1450765112345 4 connected
  c7dfcd... 127.0.0.1:30005 slave 5b0e1b... 0 1450765113050 5 connected
  321978... 127.0.0.1:30006 slave 5f3e97... 0 1450765113050 6 connected

To shut down the cluster use the create-cluster stop command.

bash
redis/utils/create-cluster $ ./create-cluster stop

Overview (`redis/example/README.md`)

Spring Data Redis - Examples

This project contains samples of specific features of Spring Data Redis.


Overview (`redis/pubsub-listener/README.md`)

Spring Data Redis - Annotated Listener Example

This project demonstrates declarative Redis Pub/Sub listeners.

Features

  • @RedisListener on component methods to subscribe to Redis channels
  • @EnableRedisListeners to activate the listener infrastructure
  • Receiving string messages on a channel
  • Receiving JSON payloads deserialized to domain types (e.g. Person)

Requirements

  • Docker (for running integration tests with Testcontainers).

Running the tests

Integration tests publish messages to Redis channels and assert that the annotated listeners receive and process them. They use Testcontainers to start a Redis container, so Docker must be running.

bash
cd redis/listener
mvn test

Overview (`redis/pubsub/README.md`)

Spring Data Redis Pub/Sub Example

This project contains samples of specific features of Spring Data Redis.


Overview (`redis/reactive/README.md`)

Spring Data Redis 2.0 - Reactive examples

This project contains samples of reactive data access features with Spring Data (Redis).

Prerequisites

This project contains samples of specific features of Spring Data Redis using reactive infrastructure.


Overview (`redis/repositories/README.md`)

Spring Data Redis - Repository Examples

This project contains examples for Spring Data specific repository abstraction on top of Redis.

Repository Support

Redis Repository support allows to convert, store, retrieve and index entities within Redis native data structures. To do, besides the HASH containing the actual properties several Secondary Index structures are set up and maintained.

java
@RedisHash("persons")
class Person {

  @Id String id;

  @Indexed String firstname;
  @Indexed String lastname;

  Gender gender;
  Address address;

  @Reference List<Person> children;
}

The above entity would for example then be stored in a Redis HASH with key persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d.

properties
_class=example.springdata.redis.domain.Person                <1>
id=9b0ed8ee-14be-46ec-b5fa-79570aadb91d
firstname=eddard                                             <2>
lastname=stark
gender=MALE
address.city=winterfell                                      <3>
address.country=the north
children.[0]=persons:41436096-aabe-42fa-bd5a-9a517fbf0260    <4>
children.[1]=persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53
children.[2]=persons:440b24c6-ede2-495a-b765-2d8b8d6e3995
text
<1> The _class attribute is used to store the actual type and is required for object/hash conversion.
<2> Values are also included in Secondary Index when annotated with @Indexed.
<3> Complex types are flattened out and embedded into the HASH as long as there is no explicit Converter registered or a @Reference annotation present.
<4> Using @Reference stores only the key of a referenced object without embedding values like in <3>.

Additionally indexes are created for firstname, lastname and address.city containing the id of the actual entity.

bash
redis/src $ ./redis-cli keys *
   1) "persons"                                             <1>
   2) "persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d"        <2>
   3) "persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d:idx"    <3>
   4) "persons:41436096-aabe-42fa-bd5a-9a517fbf0260"
   5) "persons:41436096-aabe-42fa-bd5a-9a517fbf0260:idx"
   6) "persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53"
   7) "persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53:idx"
   8) "persons:440b24c6-ede2-495a-b765-2d8b8d6e3995"
   9) "persons:440b24c6-ede2-495a-b765-2d8b8d6e3995:idx"
  10) "persons:firstname:eddard"                            <4>
  11) "persons:firstname:robb"
  12) "persons:firstname:sansa"
  13) "persons:firstname:arya"
  14) "persons:lastname:stark"                              <5>
  15) "persons:address.city:winterfell"                     <6>
text
<1> SET holding all ids known in the keyspace "persons".
<2> HASH holding property values for id "9b0ed8ee-14be-46ec-b5fa-79570aadb91d" in keyspace "persons".
<3> SET holding index information for CRUD operations.
<4> SET used for indexing entities with "firstname" equal to "eddard" within keyspace "persons".
<5> SET used for indexing entities with "lastname" equal to "stark"  within keyspace "persons".
<6> SET used for indexing entities with "address.city" equal to "winterfell"  within keyspace "persons".

Configuration

The below configuration uses Lettuce to connect to Redis on its default port. Please note the usage of @EnableRedisRepositories to create Repository instances.

java
@Configuration
@EnableRedisRepositories
class AppConfig {

  @Bean
  RedisConnectionFactory connectionFactory() {
    return new LettuceConnectionFactory();
  }

  @Bean
  RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {

    RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
    template.setConnectionFactory(connectionFactory);

    return template;
  }
}

Having the infrastructure in place you can go on declaring and using the Repository interface.

java
interface PersonRepository extends CrudRepository<Person, String> {

  List<Person> findByLastname(String lastname);

  Page<Person> findByLastname(String lastname, Pageable page);

  List<Person> findByFirstnameAndLastname(String firstname, String lastname);

  List<Person> findByFirstnameOrLastname(String firstname, String lastname);

  List<Person> findByAddress_City(String city);
}

One Word of Caution

Maintaining complex types and index structures stands and falls with its usage. Please consider the following:

  • Nested document structures increase object <> hash conversion overhead.
  • Consider having only those indexes you really need instead of indexing each and every property.
  • Pagination is a costly operation since the total number of elements is calculated before returning just a slice of data.
  • Pagination gives no guarantee on information as elements might be added, moved or removed.

Overview (`redis/sentinel/README.md`)

Spring Data Redis - Sentinel Examples

This project contains samples of Sentinel specific features of Spring Data Redis.

Support for Sentinel

java
@Configuration
public class RedisSentinelApplicationConfig {

	static final RedisSentinelConfiguration SENTINEL_CONFIG = new RedisSentinelConfiguration().master("mymaster") //
			.sentinel("localhost", 26379) //
			.sentinel("localhost", 26380) //
			.sentinel("localhost", 26381);

	@Bean
	public RedisConnectionFactory connectionFactory() {
		return new LettuceConnectionFactory(sentinelConfig(), LettuceClientConfiguration.defaultConfiguration());
	}

	@Bean
	public RedisSentinelConfiguration sentinelConfig() {
		return SENTINEL_CONFIG;
	}

}

Overview (`redis/streams/README.md`)

Spring Data Redis - Streams Examples

The Redis Stream is a new data type introduced with Redis 5.0 modelling log data structure.
Spring Data Redis supports Redis Streams via both the imperative and the reactive API.

Imperative API

Basic Usage

java
@Autowired 
RedisTemplate template;

StringRecord record = StreamRecords.string(โ€ฆ)
    .withStreamKey("my-stream");
RecordId id = template.streamOps().add(record);

List<...> records = template.streamOps().read(count(2), from(id));

ContinuousRead Read

java
@Autowired 
RedisConnectionFactory factory;

StreamListener<String, MapRecord<โ€ฆ> listener = 
  (msg) -> {
    // ...
  };

StreamMessageListenerContainer container = StreamMessageListenerContainer.create(factory));

container.receive(StreamOffset.fromStart("my-stream"), listener);

Reactive API

Basic Usage

java
@Autowired 
ReactiveRedisTemplate template;

StringRecord record = StreamRecords.string(โ€ฆ)
    .withStreamKey("my-stream");
Mono<RecordId> id = template.streamOps().add(record);

Flux<...> records = template.streamOps().read(count(2), from(id));

ContinuousRead Read

java
@Autowired 
ReactiveRedisConnectionFactory factory;

StreamReceiver receiver = StreamReceiver.create(factory));

container.receive(StreamOffset.fromStart("my-stream"))
    .doOnNext((msg) -> {
    	// ...
    })
    .subscribe();

Overview (`rest/starbucks/README.md`)

Spring Data REST - Starbucks example

This sample app exposes 10843 Starbucks coffee shops via a RESTful API that allows to access the stores in a hypermedia based way and exposes a resource to execute geolocation search for coffee shops.

Quickstart

  1. Install MongoDB (http://www.mongodb.org/downloads, unzip, run mkdir data, run bin/mongod --dbpath=data)
  2. Build and run the app (mvn spring-boot:run)
  3. Access the root resource (curl http://localhost:8080/api) and traverse hyperlinks.
  4. Or access the location search directly (e.g. http://localhost:8080/api/stores/search/findByAddressLocationNear?location=40.740337,-73.995146&distance=0.5miles)

Web UI

The application provides a custom web UI using the exposed REST API to display the search result on a Google Map. Point you browser to http://localhost:8080. The UI is rendered using Thymeleaf, driven by the StoresController. A tiny JavaScript progressively enhances the view by picking up and enhancing a URI template rendered into the view (``).

The API itself can be discovered using the HAL browser pulled in through the corresponding Spring Data REST module (spring-data-rest-hal-browser). It's exposed at the API root at http://localhost:8080/api.

Technologies used

  • Spring Data REST & Spring Data MongoDB
  • MongoDB
  • Spring Batch (to read the CSV file containing the store data and pipe it into MongoDB)
  • Spring Boot

Overview (`web/example/README.md`)

Spring Data - web support example

This example shows some of the Spring Data integration features with Spring MVC.

  1. See how we plug into Spring MVC to create Pageable instances from request parameters in UserController.users(โ€ฆ).
  2. See how interfaces can be used to bind request payloads in UserController.UserForm. Spring Data creates a Map-backed proxy for you to easily create form-backing objects.

Overview (`web/projection/README.md`)

Spring Data Web - Projection example

This example shows how to use projection interfaces in combination with JSON Path or XPath expressions to bind payload data to an object instance.

The most interesting bit is the following projection interface declared in UserController:

java
@ProjectedPayload
public interface UserPayload {

	@XBRead("//firstname")
	@JsonPath("$..firstname")
	String getFirstname();

	@XBRead("//lastname")
	@JsonPath("$..lastname")
	String getLastname();
}

This type is used in UserController.index(โ€ฆ) and basically combines two modes of operation.

Binding request data via JSON Path expression

The @JsonPath annotations bind the values obtained by evaluating the expressions from the request. The sample is using a recursive property lookup for firstname and lastname. Using those expressions allows the payload that received to slightly changed and the code dealing with not having to be changed.

As an example using that on the server side can be found in UserControllerIntegrationTests. The tests send two different flavors of JSON to simulate a change in behavior of the client and the server can handle both representation formats without the need for a change.

This is also very useful on the client side which is simulated in UserControllerClientTests setting up a RestTemplate with the newly introduced HttpMessageConverters so that the projection interface can be used to access the payload. See how the test case accesses different HTTP resources, that simulate a change in the representation on the server side.

Support for XPath expressions

The JSON Path support is automatically activated on the server side if the Jayway JSON Path library is on the classpath. Support for XPath is activated if XMLBeam is on the classpath, generally works the same and actually offers quite some more sophisticated features.


Overview (`web/querydsl/README.md`)

Spring Data Web - Querydsl example

This example shows some of the Spring Data Querydsl integration features with Spring MVC.

Quickstart

  1. Build and run the app (mvn spring-boot:test-run)
  2. Access app directly via its UI (http://localhost:8080/).

Interesting bits

The most core piece of interest here is UserController and how it consumes Querydsl Predicate instances derived from the request:

java
@Controller
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
class UserController {

  private final UserRepository repository;

  @RequestMapping(value = "/", method = RequestMethod.GET)
  String index(Model model,
      @QuerydslPredicate(root = User.class) Predicate predicate, Pageable pageable,
      @RequestParam MultiValueMap<String, String> parameters) {

    โ€ฆ = userRepository.findAll(predicate, pageable);

  }
}

As you can see Predicate can be used as Spring MVC controller argument. It will automatically populate such a Predicate with values from the current request based on the type configured in @QuerydslPredicate. Explicit bindings can be configured by either explicitly defining one in the annotation, too. By default, we will inspect the domain types's repository for binding customizations. In this example, it looks like this:

java
public interface UserRepository
    extends CrudRepository<User, String>, QueryDslPredicateExecutor<User>, QuerydslBinderCustomizer<QUser> {

  @Override
  default public void customize(QuerydslBindings bindings, QUser root) {

    bindings.bind(String.class).first((StringPath path, String value) -> path.containsIgnoreCase(value));
    bindings.excluding(root.password);
  }
}

The repository extends QuerydslBinderCustomizer which exposes a QuerydslBindings instance for customization. It allows for property based (by using Querydsl's metamodel types) and type based customizations of the value binding. The example here defines a String-properties to be bound using the containsIgnoreCase(โ€ฆ) operator. For further information checkout the JavaDoc of QuerydslBindings.

Technologies used

  • Spring Data MongoDB, Spring MVC & Querydsl
  • MongoDB
  • Spring Batch (to read the CSV file containing the user data and pipe it into MongoDB)
  • Spring Boot

--- METRICS ---