Copyright © 2016-2019

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.

1. Introduction

The MongoDB Datastore is the MongoDB reference implementation of the Holon Platform Datastore API.

See the Datastore documentation for further information about the Datastore API.

The MongoDB Datastore implementation uses the MongoDB Java Driver to perform data access and management operations on MongoDB databases.

1.1. Sources and contributions

The Holon Platform MongoDB Datastore module source code is available from the GitHub repository https://github.com/holon-platform/holon-datastore-mongo.

See the repository README file for information about:

  • The source code structure.

  • How to build the module artifacts from sources.

  • Where to find the code examples.

  • How to contribute to the module development.

2. Obtaining the artifacts

The Holon Platform uses Maven for projects build and configuration. All the platform artifacts are published in the Maven Central Repository, so there is no need to explicitly declare additional repositories in your project pom file.

At the top of each section of this documentation you will find the Maven coordinates (group id, artifact id and version) to obtain the artifact(s) as a dependency for your project.

A BOM (Bill Of Materials) pom is provided to import the available dependencies for a specific version in your projects. The Maven coordinates for the core BOM are the following:

Maven coordinates:

<groupId>com.holon-platform.mongo</groupId>
<artifactId>holon-datastore-mongo-bom</artifactId>
<version>5.5.0</version>

The BOM can be imported in a Maven project in the following way:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.holon-platform.mongo</groupId>
      <artifactId>holon-datastore-mongo-bom</artifactId>
      <version>5.5.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

2.1. Using the Platform BOM

The Holon Platform provides an overall Maven BOM (Bill of Materials) to easily obtain all the available platform artifacts.

3. MongoDB Datastore

The MongoDB Datastore API is available in three implementations:

3.1. MongoDB Datastore configuration

Regardless of the actual implementation, each MongoDB Datastore provides a set of configuration options, described below.

3.1.1. Database name

A MongoDB Datastore is always bound to a single MongoDB database, so the the database name configuration is required to build a MongoDB Datastore instance.

Each builder provides a database(String database) method to set the database name to which the MongoDB Datastore is bound.

Example:

MongoDatastore datastore = MongoDatastore.builder() (1)
    .client(getMongoClient()) (2)
    .database("my_db") (3)
    .build();
1 Obtain a builder to configure and create a new MongoDB Datastore (in this example, a synchronous MongoDatastore implementation)
2 Set the MongoDB client to use
3 Set the database name to use

3.1.2. Common MongoDB Datastore configuration options

A set of common configuration options are available from the MongoDB Datastore builder, regardless of the actual implementation used.

These common configuration options allows to:

  • Configure the default read preference to use.

  • Configure the default read concern to use.

  • Configure the default write concern to use.

  • Add a new bson Codec.

  • Add a new bson CodecProvider.

MongoDatastore datastore = MongoDatastore.builder().client(getMongoClient()).database("my_db")
    .readPreference(ReadPreference.primary()) (1)
    .readConcern(ReadConcern.MAJORITY) (2)
    .writeConcern(WriteConcern.UNACKNOWLEDGED) (3)
    .withCodec(getCustomCodec()) (4)
    .withCodecProvider(getCustomCodecProvider()) (5)
    .build();
1 Set the default read preference
2 Set the default read concern
3 Set the default write concern
4 Add a new bson Codec
5 Add a new bson CodecProvider

3.1.3. Enum codec strategy

By default, enumeration type document field values are mapped to a Java Enum class using a name based matching strategy.

An ordinal based matching strategy is also available, using the Enum ordinal position to map an enumeration type document field value to a Java Enum class.

The enumeration codec strategies are listed in the EnumCodecStrategy enumeration and the strategy to use can be configured using the MongoDB Datastore builder enumCodecStrategy method.

MongoDatastore datastore = MongoDatastore.builder().client(getMongoClient()).database("my_db")
    .enumCodecStrategy(EnumCodecStrategy.ORDINAL) (1)
    .build();
1 Set the enumeration codec strategy to ORDINAL

3.1.4. Common Datastore API configuration options

Every MongoDB Datastore builder, regardless of the actual implementation, extends the core Datastore builder API, which provides the common Datastore configuration settings listed below.

Builder method Arguments Description

dataContextId

The data context id String value

Set the data context id to which the Datastore is bound. Can be used, for example, to declare configuration properties for multiple Datastore instances.

traceEnabled

true or false

Whether to enable Datastore operations tracing. When enabled, the MongoDB Datastore will log the JSON representation of the documents involved in the performed datastore operations.

configuration

A DatastoreConfigProperties instance

Set the DatastoreConfigProperties type configuration property set instance to use in order to read the Datastore configuration properties. See the Datastore configuration documentation section for details. This configuration properties can be used as an alternative for the programmatic configuration performed with the previous builder methods.

Example:

MongoDatastore datastore = MongoDatastore.builder().client(getMongoClient()).database("my_db")
    .dataContextId("mydataContextId") (1)
    .traceEnabled(true) (2)
    .build();
1 Set a data context id for the Datastore
2 Activate operations tracing in log

The configuration properties can also be provided through an external configuration property source, using the properties provided by the DatastoreConfigProperties property set.

For example, supposing to have a properties file named datastore.properties like this:

holon.datastore.trace=true

We can use it as configuration property source to enable the Datastore tracing mode:

MongoDatastore datastore = MongoDatastore.builder().client(getMongoClient()).database("my_db")
    .configuration(DatastoreConfigProperties.builder().withPropertySource("datastore.properties").build()) (1)
    .build();
1 Use the datastore.properties file as configuration property source

3.2. Property model and MongoDB documents mapping

The Holon Platform PropertyBox type, used by the Datastore API to collect and handle the data model attributes values using the Property model, is mapped into a MongoDB BSON Document using the following conventions:

  • Each PropertyBox instance is mapped into a MongoDB BSON document, using the property set bound to the PropertyBox instance to determine the available document nodes names.

  • Each property name is mapped to a document node name. By default only the Path type properties are used for mapping, using the path name as node name.

  • Each property value available from the PropertyBox instance is mapped into a document node value, performing suitable data type convesions if required.

3.2.1. Nested documents

Nested MongoDB documents can be represented using either:

1. Nested property names using a dot notation:

When nested path names are included in the property set, either using the dot notation or parent path declarations, they will be mapped as nested document properties in the MongoDB BSON Document.

For example:

static final StringProperty NAME = StringProperty.create("name");
static final StringProperty STREET = StringProperty.create("address.street");
static final StringProperty CITY = StringProperty.create("address.city");

static final PropertySet<?> SUBJECT = PropertySet.of(NAME, STREET, CITY);

The SUBJECT property set will be mapped into a Document with:

  • A name String type node at root level.

  • A nested address document with the street and city String type nodes.

2. Nested PropertyBox type properties:

A PropertyBox type property can be used to represent a nested document. The nested document nodes will be the one represented by the property set bound to PropertyBox type property.

For this purpose, the PropertyBoxProperty type can be used, which makes available a builder to create a new PropertyBox type property providing the property set to use.

For example:

static final StringProperty NAME = StringProperty.create("name");

static final StringProperty STREET = StringProperty.create("street");
static final StringProperty CITY = StringProperty.create("city");
static final PropertyBoxProperty ADDRESS = PropertyBoxProperty.create("address", STREET, CITY);

static final PropertySet<?> SUBJECT = PropertySet.of(NAME, ADDRESS);

The SUBJECT property set will be mapped into a Document with the same schema of the previous example, i.e.:

  • A name String type node at root level.

  • A nested address document with the street and city String type nodes.

3.2.2. Multi value document nodes

Multi value (array) MongoDB Document nodes can be represented using a Collection type property.

The standard SetPathProperty and ListPathProperty types can be used to represent a Set or List type collection of values.

static final StringProperty NAME = StringProperty.create("name");

static final ListPathProperty<String> SKILLS = ListPathProperty.create("skills", String.class); (1)
static final SetPathProperty<EnumValue> QUALIFICATIONS = SetPathProperty.create("qualifications",
    EnumValue.class); (2)

static final PropertySet<?> SUBJECT = PropertySet.of(NAME, SKILLS, QUALIFICATIONS);
1 Will be mapped into an array type Document node value and represented as a Java List
2 Will be mapped into an array type Document node value and represented as a Java Set

3.3. Document id

The MongoDB Datastore supports three data types to represent a Document id and to map it to a property:

1: The standard org.bson.types.ObjectId BSON type:

static final PathProperty<ObjectId> ID = PathProperty.create("_id", ObjectId.class);

2: The String type:

static final StringProperty ID = StringProperty.create("_id");

1: The BigInteger type:

static final NumericProperty<BigInteger> ID = NumericProperty.bigIntegerType("_id");

The MongoDB Datastore will perform all the suitable conversions, if required, during the document mapping into and from a PropertyBox type.

Regarding the document id naming strategy, the following options can be used:

1: If a single property is declared as property set identifier and its type is one of the valid document id types (see before), it is used as document id. In this scenario, the property name is irrelevant and any path name can be used.

static final StringProperty ID = StringProperty.create("my_document_id");
static final StringProperty NAME = StringProperty.create("name");

static final PropertySet<?> SUBJECT = PropertySet.builderOf(ID, NAME).withIdentifier(ID).build(); (1)
1 The my_document_id named property will be used as document id, because it is explicitly declared as property set identifier

2: Otherwise, if a property with the default _id name is available in the propert set and its type is one of the valid document id types (see before), that property is used as document id.

static final StringProperty ID = StringProperty.create("_id");
static final StringProperty NAME = StringProperty.create("name");

static final PropertySet<?> SUBJECT = PropertySet.of(ID, NAME); (1)
1 The _id String type property will be used as document id, because it is named with the default _id name and its type is a valid document id type
When a document id property name different form the standard id is used, the MongoDB Datastore _save, insert and update operations will always include a _id document attribute anyway and its value will be synchonized with the property value declared as document id.

3.4. Database collections

For each Datastore operation, the target database collection name must be specified using the DataTarget representation, providing the collection name as data target name.

final static DataTarget<?> TARGET = DataTarget.named("my_collection"); (1)
1 Declare a DataTarget for the my_collection database collection name

3.5. Auto-generated ids

The MongoDB Datastore API supports the retrieving of the auto-generated document id values.

The auto-generated document id values can be obtained from the OperationResult object, returned by the Datastore data manipulation operations, through the getInsertedKeys() and related methods.

final PathProperty<ObjectId> ID = PathProperty.create("_id", ObjectId.class);

Datastore datastore = getMongoDatastore(); // build or obtain a MongoDB Datastore
PropertyBox value = buildPropertyBoxValue();

OperationResult result = datastore.insert(DataTarget.named("my_collection"), value); (1)

Optional<ObjectId> idValue = result.getInsertedKey(ID); (2)
idValue = result.getFirstInsertedKey(ObjectId.class); (3)
1 Perform a insert operation using the Datastore API
2 Get the document id value using the ID property, if available
3 Get the first auto-generated document id value

The default BRING_BACK_GENERATED_IDS WriteOption can be provided to the Datastore API operation to bring back any auto-generated document id value into the PropertyBox instance which was the subject of the operation.

The auto-generated document id value will be setted in the PropertyBox instance using the property which acts as property set identifier. See Document id.

final StringProperty ID = StringProperty.create("_id"); (1)
final PathProperty<String> TEXT = PathProperty.create("text", String.class);

Datastore datastore = getMongoDatastore(); // build or obtain a MongoDB Datastore

PropertyBox value = PropertyBox.builder(ID, TEXT).set(TEXT, "test").build(); (2)

datastore.insert(DataTarget.named("my_collection"), value, DefaultWriteOption.BRING_BACK_GENERATED_IDS); (3)

String idValue = value.getValue(ID); (4)
1 Document id property declaration using the standard _id name
2 Create the PropertyBox value to insert
3 Perform the insert operation, providing the BRING_BACK_GENERATED_IDS write option
4 The ID property value of the inserted PropertyBox is updated with the auto-generated document id value

3.6. Synchronous MongoDB Datastore

The holon-datastore-mongo-sync artifact provides the synchronous MongoDB Datastore API implementation.

Maven coordinates:

<groupId>com.holon-platform.mongo</groupId>
<artifactId>holon-datastore-mongo-sync</artifactId>
<version>5.5.0</version>

The MongoDatastore interface represents the synchronous MongoDB Datastore API implementation, extending the Datastore API.

A synchronous MongoDB Java driver is required in classpath in order for the datastore to work.

The MongoDatastore API, besides the standard Datastore API operations, provides a builder() method which can be used to create and configure a MongoDatastore API istance.

A com.mongodb.client.MongoClient reference is required for the MongoDatastore instance setup.

MongoDatastore datastore = MongoDatastore.builder() (1)
    .client(getMongoClient()) (2)
    .build();
1 Obtain a builder to configure and create a new MongoDatastore instance
2 Set the MongoDB client to use
If you want to reach the goal of a complete abstraction from the persistence store technology and the persistence model, the core Datastore API interface should be used instead of the specific MongoDatastore API by your application code. This way, the concrete Datastore API implementation may be replaced by a different one at any time, without any change to the codebase.

To reach the goal of a complete abstraction from the persistence store technology and the persistence model, the core Datastore API interface should be used by your application code, instead of the specific MongoDatastore API:

Datastore datastore = MongoDatastore.builder() (1)
    .client(getMongoClient()) (2)
    .build();
1 Obtain a builder to configure and create a new MongoDatastore instance and expose it as a Datastore API type
2 Set the MongoDB client to use

For example, given the following property model declaration:

final static StringProperty ID = StringProperty.create("_id"); (1)
final static StringProperty NAME = StringProperty.create("name"); (2)

final static PropertySet<?> SUBJECT = PropertySet.builderOf(ID, NAME).withIdentifier(ID).build(); (3)

final static DataTarget<?> TARGET = DataTarget.named("my_collection"); (4)
1 Document id property using the standard _id name and String as value type
2 A String type document property declation which maps the name document attribute
3 Property set declaration including the ID and NAME properties. The ID property is explicitly declared as the identifier property, even if in this case it would not have been necessary because the ID property is mapped to the standard _id document id attribute name.
4 Data target declation using the my_collection database collection name

Below an example of some datastore operations using the synchronous MongoDB Datastore API:

Datastore datastore = MongoDatastore.builder().client(getMongoClient()).database("test").build(); (1)

PropertyBox value = PropertyBox.builder(SUBJECT).set(NAME, "My name").build();

OperationResult result = datastore.save(TARGET, value); (2)
Optional<String> documentId = result.getInsertedKey(ID); (3)

result = datastore.insert(TARGET, value, DefaultWriteOption.BRING_BACK_GENERATED_IDS); (4)
documentId = value.getValueIfPresent(ID); (5)

value.setValue(NAME, "Updated name");
datastore.update(TARGET, value); (6)

datastore.delete(TARGET, value); (7)

long count = datastore.query(TARGET).filter(NAME.contains("fragment").or(NAME.startsWith("prefix"))).count(); (8)

Stream<PropertyBox> results = datastore.query(TARGET).filter(NAME.isNotNull()).sort(ID.desc()).stream(SUBJECT); (9)

Optional<String> id = datastore.query(TARGET).filter(NAME.eq("My name")).findOne(ID); (10)

result = datastore.bulkUpdate(TARGET).set(NAME, "Updated").filter(NAME.isNull()).execute(); (11)
long affected = result.getAffectedCount();

result = datastore.bulkDelete(TARGET).filter(NAME.endsWith("suffix")).execute(); (12)
affected = result.getAffectedCount();
1 Build a MongoDB Datastore bound to the test database name
2 Save (insert if not present or update otherwise) given PropertyBox value in the my_collection database collection name (using the TARGET declaration)
3 Get the auto-generated document id value, mapped to the ID property declaration
4 Insert given value using the BRING_BACK_GENERATED_IDS default write option
5 The auto-generated document id value will be setted in the provided PropertyBox value, bound to the ID property
6 Update given value
7 Delete the value
8 Perform a count query on the my_collection database collection, providing some filters
9 Perform a query on the my_collection database collection, providing a filter and a sort declaration and projecting the query results as a stream of PropertyBox values
10 Perform a query to obtain a single value of the ID property, if available
11 Perform a bulk update
12 Perform a bulk delete

3.7. Asynchronous MongoDB Datastore

The holon-datastore-mongo-async artifact provides the asynchronous MongoDB AsyncDatastore API implementation.

Maven coordinates:

<groupId>com.holon-platform.mongo</groupId>
<artifactId>holon-datastore-mongo-async</artifactId>
<version>5.5.0</version>

The AsyncMongoDatastore interface represents the asynchronous MongoDB Datastore API implementation, extending the AsyncDatastore API.

An asynchronous MongoDB Java driver is required in classpath in order for the datastore to work.

The AsyncMongoDatastore API, besides the standard AsyncDatastore API operations, provides a builder() method which can be used to create and configure an AsyncMongoDatastore API istance.

A com.mongodb.reactivestreams.client.MongoClient reference is required for the AsyncMongoDatastore instance setup.

AsyncMongoDatastore datastore = AsyncMongoDatastore.builder() (1)
    .client(getMongoClient()) (2)
    .build();
1 Obtain a builder to configure and create a new AsyncMongoDatastore instance
2 Set the MongoDB client to use
If you want to reach the goal of a complete abstraction from the persistence store technology and the persistence model, the AsyncDatastore API interface should be used instead of the specific AsyncMongoDatastore API by your application code. This way, the concrete AsyncDatastore API implementation may be replaced by a different one at any time, without any change to the codebase.

To reach the goal of a complete abstraction from the persistence store technology and the persistence model, the AsyncDatastore API interface should be used by your application code, instead of the specific AsyncMongoDatastore API:

AsyncDatastore datastore = AsyncMongoDatastore.builder() (1)
    .client(getMongoClient()) (2)
    .build();
1 Obtain a builder to configure and create a new AsyncMongoDatastore instance and expose it as a AsyncDatastore API type
2 Set the MongoDB client to use

The AsyncDatastore API uses the java.util.concurrent.CompletionStage interface to provide the asynchronous operation results.

Below an example of some datastore operations using the asynchronous MongoDB Datastore API:

AsyncDatastore datastore = AsyncMongoDatastore.builder().client(getMongoClient()).database("test").build(); (1)

PropertyBox value = PropertyBox.builder(SUBJECT).set(NAME, "My name").build();

datastore.save(TARGET, value) (2)
    .thenApply(result -> result.getInsertedKey(ID)).thenAccept(id -> {
      Optional<String> documentId = id; (3)
    });

datastore.insert(TARGET, value, DefaultWriteOption.BRING_BACK_GENERATED_IDS) (4)
    .thenApply(result -> value.getValueIfPresent(ID)).thenAccept(id -> {
      Optional<String> documentId = id; (5)
    });

value.setValue(NAME, "Updated name");
CompletionStage<OperationResult> result = datastore.update(TARGET, value); (6)

result = datastore.delete(TARGET, value); (7)

CompletionStage<Long> count = datastore.query(TARGET)
    .filter(NAME.contains("fragment").or(NAME.startsWith("prefix"))).count(); (8)

CompletionStage<Stream<PropertyBox>> results = datastore.query(TARGET).filter(NAME.isNotNull()).sort(ID.desc())
    .stream(SUBJECT); (9)

CompletionStage<Optional<String>> id = datastore.query(TARGET).filter(NAME.eq("My name")).findOne(ID); (10)

datastore.bulkUpdate(TARGET).set(NAME, "Updated").filter(NAME.isNull()).execute().thenAccept(r -> { (11)
  long affected = r.getAffectedCount();
});

datastore.bulkDelete(TARGET).filter(NAME.endsWith("suffix")).execute().thenAccept(r -> { (12)
  long affected = r.getAffectedCount();
});
1 Build a MongoDB AsyncDatastore bound to the test database name
2 Save (insert if not present or update otherwise) given PropertyBox value in the my_collection database collection name (using the TARGET declaration)
3 Get the auto-generated document id value, mapped to the ID property declaration
4 Insert given value using the BRING_BACK_GENERATED_IDS default write option
5 The auto-generated document id value will be setted in the provided PropertyBox value, bound to the ID property
6 Update given value
7 Delete the value
8 Perform a count query on the my_collection database collection, providing some filters
9 Perform a query on the my_collection database collection, providing a filter and a sort declaration and projecting the query results as a stream of PropertyBox values
10 Perform a query to obtain a single value of the ID property, if available
11 Perform a bulk update
12 Perform a bulk delete

3.8. Reactive MongoDB Datastore

The holon-datastore-mongo-reactor artifact provides the reactive MongoDB AsyncDatastore API implementation, using the Project Reactor Mono and Flux to provide and handle the datastore operations results.

Maven coordinates:

<groupId>com.holon-platform.mongo</groupId>
<artifactId>holon-datastore-mongo-reactor</artifactId>
<version>5.5.0</version>

The ReactiveMongoDatastore interface represents the asynchronous MongoDB Datastore API implementation, extending the ReactiveDatastore API.

A reactive MongoDB Java driver is required in classpath in order for the datastore to work.

The ReactiveMongoDatastore API, besides the standard ReactiveDatastore API operations, provides a builder() method which can be used to create and configure a ReactiveMongoDatastore API istance.

A com.mongodb.reactivestreams.client.MongoClient reference is required for the ReactiveMongoDatastore instance setup.

ReactiveMongoDatastore datastore = ReactiveMongoDatastore.builder() (1)
    .client(getMongoClient()) (2)
    .build();
1 Obtain a builder to configure and create a new ReactiveMongoDatastore instance
2 Set the MongoDB client to use
If you want to reach the goal of a complete abstraction from the persistence store technology and the persistence model, the ReactiveDatastore API interface should be used instead of the specific ReactiveMongoDatastore API by your application code. This way, the concrete ReactiveDatastore API implementation may be replaced by a different one at any time, without any change to the codebase.

To reach the goal of a complete abstraction from the persistence store technology and the persistence model, the ReactiveDatastore API interface should be used by your application code, instead of the specific ReactiveMongoDatastore API:

ReactiveDatastore datastore = ReactiveMongoDatastore.builder() (1)
    .client(getMongoClient()) (2)
    .build();
1 Obtain a builder to configure and create a new ReactiveMongoDatastore instance and expose it as a ReactiveDatastore API type
2 Set the MongoDB client to use

The ReactiveDatastore API uses the Project Reactor reactor.core.publisher.Mono and reactor.core.publisher.Flux objects to provide the operation results.

Below an example of some datastore operations using the reactive MongoDB Datastore API:

ReactiveDatastore datastore = ReactiveMongoDatastore.builder().client(getMongoClient()).database("test")
    .build(); (1)

PropertyBox value = PropertyBox.builder(SUBJECT).set(NAME, "My name").build();

datastore.save(TARGET, value) (2)
    .map(result -> result.getInsertedKey(ID)).doOnSuccess(id -> {
      Optional<String> documentId = id; (3)
    });

datastore.insert(TARGET, value, DefaultWriteOption.BRING_BACK_GENERATED_IDS) (4)
    .doOnSuccess(id -> {
      Optional<String> documentId = value.getValueIfPresent(ID); (5)
    });

value.setValue(NAME, "Updated name");
datastore.update(TARGET, value); (6)

datastore.delete(TARGET, value); (7)

Mono<Long> count = datastore.query(TARGET).filter(NAME.contains("fragment").or(NAME.startsWith("prefix")))
    .count(); (8)

Flux<PropertyBox> results = datastore.query(TARGET).filter(NAME.isNotNull()).sort(ID.desc()).stream(SUBJECT); (9)

Mono<String> id = datastore.query(TARGET).filter(NAME.eq("My name")).findOne(ID); (10)

Mono<OperationResult> result = datastore.bulkUpdate(TARGET).set(NAME, "Updated").filter(NAME.isNull())
    .execute(); (11)
result.doOnSuccess(r -> {
  long affected = r.getAffectedCount();
});

result = datastore.bulkDelete(TARGET).filter(NAME.endsWith("suffix")).execute(); (12)
result.doOnSuccess(r -> {
  long affected = r.getAffectedCount();
});
1 Build a MongoDB ReactiveDatastore bound to the test database name
2 Save (insert if not present or update otherwise) given PropertyBox value in the my_collection database collection name (using the TARGET declaration)
3 Get the auto-generated document id value, mapped to the ID property declaration
4 Insert given value using the BRING_BACK_GENERATED_IDS default write option
5 The auto-generated document id value will be setted in the provided PropertyBox value, bound to the ID property
6 Update given value
7 Delete the value
8 Perform a count query on the my_collection database collection, providing some filters
9 Perform a query on the my_collection database collection, providing a filter and a sort declaration and projecting the query results as a Flux of PropertyBox values
10 Perform a query to obtain a single value of the ID property, if available
11 Perform a bulk update
12 Perform a bulk delete

3.9. Custom BSON filters and sorts

The MongoDB Datastore APIs support custom BSON filters and sorts expressions, i.e. QueryFilter and QuerySort type expressions for which the BSON expression that represents the filtering and sorting conditions is directly provided.

3.9.1. BsonFilter

The BsonFilter interface is a QueryFilter implementation to provide the query restrictions using a BSON expression, and it can be used just like any other QueryFilter implementation in query definitions.

Datastore datastore = getMongoDatastore(); // build or obtain a MongoDB Datastore

long count = datastore.query(DataTarget.named("my_collection"))
    .filter(BsonFilter.create(Filters.size("my_field", 3))) (1)
    .count();
1 Use BsonFilter to provide a BSON filter expression that matches all documents where the value of the my_field field is an array of the specified size (3)

3.9.2. BsonSort

The BsonSort interface is a QuerySort implementation to provide the query sort using a BSON expression, and it can be used just like any other QuerySort implementation in query definitions.

Datastore datastore = getMongoDatastore(); // build or obtain a MongoDB Datastore

Stream<String> values = datastore.query(DataTarget.named("my_collection"))
    .sort(BsonSort.create(Sorts.descending("my_field"))) (1)
    .stream(MY_PROPERTY);
1 Use BsonSort to provide a BSON sort specification for a descending sort on the my_field field

3.10. Transactions management

The MongoDB Datastore API implementations support transactions management, if transaction support is available from the concrete MongoDB database engine.

The multi-document ACID transactions support is available since the version 4.0 of MongoDB platform and the MongoDB Java driver version 3.8 or higher is required.

The transactions management operations are made available using:

3.10.1. Synchronous transactions management

The isTransactional() or requireTransactional() methods of the Datastore API can be used to obtain the transactional API which can be used for transactions management.

The Transaction interface is used as transaction representation and provides methods to inspect the transaction status and perform the commit and rollback operations.

final Datastore datastore = getMongoDatastore(); // build or obtain a MongoDB Datastore

datastore.requireTransactional().withTransaction(tx -> { (1)
  datastore.save(TARGET, value);
  tx.commit(); (2)
});

OperationResult result = datastore.requireTransactional().withTransaction(tx -> { (3)
  return datastore.save(TARGET, value);
}, TransactionConfiguration.withAutoCommit()); (4)
1 Obtain the Transactional API to execute one or more Datastore operation within a transaction
2 Commit the transaction
3 Obtain the Transactional API to execute the Datastore operation within a transaction and return a value
4 The transaction is configured with the auto commit mode, this way the transaction is automatically committed at the transactional operation end if no error occurred

3.10.2. Asynchronous transactions management

The isTransactional() or requireTransactional() methods of the AsyncDatastore API can be used to obtain the transactional API which can be used for transactions management.

The AsyncTransaction interface is used as transaction representation and provides methods to inspect the transaction status and perform the commit and rollback operations.

final AsyncDatastore datastore = getMongoDatastore(); // build or obtain a MongoDB Datastore

CompletionStage<Boolean> committed = datastore.requireTransactional().withTransaction(tx -> { (1)
  datastore.save(TARGET, value);
  return tx.commit(); (2)
});

CompletionStage<OperationResult> result = datastore.requireTransactional().withTransaction(tx -> { (3)
  return datastore.save(TARGET, value);
}, TransactionConfiguration.withAutoCommit()); (4)
1 Obtain the AsyncTransactional API to execute one or more Datastore operation within a transaction
2 Commit the transaction
3 Obtain the AsyncTransactional API to execute the Datastore operation within a transaction and return a value
4 The transaction is configured with the auto commit mode, this way the transaction is automatically committed at the transactional operation end if no error occurred

3.10.3. Reactive transactions management

The isTransactional() or requireTransactional() methods of the ReactiveDatastore API can be used to obtain the transactional API which can be used for transactions management.

The ReactiveTransaction interface is used as transaction representation and provides methods to inspect the transaction status and perform the commit and rollback operations.

final ReactiveDatastore datastore = getMongoDatastore(); // build or obtain a MongoDB Datastore

Flux<Boolean> committed = datastore.requireTransactional().withTransaction(tx -> { (1)
  datastore.save(TARGET, value);
  return tx.commit().flux(); (2)
});

Flux<OperationResult> result = datastore.requireTransactional().withTransaction(tx -> { (3)
  return datastore.save(TARGET, value).flux();
}, TransactionConfiguration.withAutoCommit()); (4)
1 Obtain the ReactiveTransactional API to execute one or more Datastore operation within a transaction
2 Commit the transaction
3 Obtain the ReactiveTransactional API to execute the Datastore operation within a transaction and return a value
4 The transaction is configured with the auto commit mode, this way the transaction is automatically committed at the transactional operation end if no error occurred

3.11. Using the MongoDatabaseHandler for a direct database access

The MongoDatabaseHandler API can be used obtain a direct reference to the MongoDatabase object which represents the Mongo database configured for the current Datastore implementation.

The MongoDatabase reference can be used to perform database operations with the MongoDB Java driver directly, using BSON expressions and outside of the standard Datastore API operations.

Each specific MongoDB datastore implementation provides the MongoDatabaseHandler support, using a consistent MongoDatabase reference type.

MongoDatastore datastore = getMongoDatastore();

long count = datastore.withDatabase(db -> { (1)
  db.createCollection("test");
  return db.getCollection("test").countDocuments();
});
1 Get the MongoDatabase reference and use it to perform some operations

3.12. Extending the MongoDB Datastore API

3.13. Expression resolvers

The Datastore API can be extended using the ExpressionResolver API, to add new expression resolution strategies, modify existing ones and to handle new Expression types.

See the Datastore API extension documentation section for details.

3.13.1. MongoDB Expression resolvers registration

A new ExpressionResolver can be registered in the MongoDB Datastore API in two ways:

1. Using the MongoDB Datastore API instance:

An ExpressionResolver can be registered either using the Datastore builder API at Datastore configuration time:

Datastore datastore = MongoDatastore.builder() //
    .withExpressionResolver(new MyExpressionResolver()) (1)
    .build();
1 Register a new ExpressionResolver

Or using the Datastore API itself, which extends the ExpressionResolverSupport API:

datastore.addExpressionResolver(new MyExpressionResolver()); (1)
1 Register and new ExpressionResolver
The same registration options are available for the AsyncDatastore and ReactiveDatastore API and builders.

2. Automatic registration using Java service extensions:

The MongoDB Datastore implementatons support ExpressionResolver automatic registration using the MongoDatastoreExpressionResolver base type and the default Java service extensions modality.

To automatically register an ExpressionResolver this way, a class implementing MongoDatastoreExpressionResolver has to be created and its fully qualified name must be specified in a file named com.holonplatform.datastore.mongo.core.config.MongoDatastoreExpressionResolver, placed in the META-INF/services folder in classpath.

When this registration method is used, the expression resolvers defined this way will be registered for any MongoDB Datastore API instance.

3.13.2. Specific expression resolvers registration

All the default Datastore API operations supports operation specific expression resolvers registration, through the ExpressionResolverSupport API.

An ExpressionResolver registered for a specific Datastore API operation execution will be available only for the execution of that operation, and will be ignored by any other Datastore API operation.

For example, to register an expression resolver only for a single Query execution, the Query builder API can be used:

long result = datastore.query().target(DataTarget.named("test")) //
    .withExpressionResolver(new MyExpressionResolver()) (1)
    .count();
1 Register an expression resolver only for the specific Query operation definition

3.13.3. Expression resolvers priority

According to the standard convention, the javax.annotation.Priority annotation can be used on ExpressionResolver classes to indicate in what order the expression resolvers bound to the same type resolution pair (i.e. the expression type handled by a resolver and the target expression type into which it will be resolved) must be applied.

The less is the javax.annotation.Priority number assigned to a resolver, the higher will be it’s priority order.

All the default MongoDB Datastore expression resolvers have the minimum priority order, allowing to override their behavior and resolution strategies with custom expression resolvers with a higher assigned priority order (i.e. a priority number less then Integer.MAX_VALUE).

3.13.4. Expression validation

The internal MongoDB Datastore BSON composer engine will perform validation on any Expression instance to resolve and each corresponding resolved Expression instance, using the default expression validate() method.

So the validate() method can be used to implement custom expression validation logic and throw an InvalidExpressionException when validation fails.

3.13.5. MongoDB Datastore expressions

Besides the standard Datastore API expressions, such as DataTarget, QueryFilter and QuerySort, which can be used to extend the Datastore API with new expression implementations and new resolution strategies, the MongoDB Datastore API can be extended using a set of specific BSON resolution expressions, used by the internal BSON composer engine to implement the resolution and composition strategy to obtain the BSON expressions and document mappings from the Datastore API meta-language expressions.

These expressions are available from the com.holonplatform.datastore.mongo.core.expression package of the holon-datastore-mongo-core artifact.

The BsonExpression is the expression which represents a BSON expression part, used to compose the actual BSON statement which will be executed using the MongoDB Java driver.

The BsonExpression type can be used to directly resolve an abstract Datastore API expression into a BSON statement part.

For example, supposing to have a IdIs class which represents a QueryFilter expression type to represent the expression "the _id column name value is equal to a given ObjectId type value":

class IdIs implements QueryFilter {

  private final ObjectId value;

  public IdIs(ObjectId value) {
    this.value = value;
  }

  public ObjectId getValue() {
    return value;
  }

  @Override
  public void validate() throws InvalidExpressionException {
    if (value == null) {
      throw new InvalidExpressionException("Id value must be not null");
    }
  }

}

We want to create an ExpressionResolver class to resolve the IdIs expression directly into a BSON epression, using the BsonExpression type. Using the convenience create method of the ExpressionResolver API, we can do it in the following way:

final ExpressionResolver<IdIs, BsonExpression> keyIsResolver = ExpressionResolver.create( //
    IdIs.class, (1)
    BsonExpression.class, (2)
    (keyIs, ctx) -> Optional.of(BsonExpression.create(Filters.eq("_id", keyIs.getValue())))); (3)
1 Expression type to resolve
2 Target expression type
3 Expression resolution logic: since we resolve the IdIs expression directly into a BsonExpression type, the BSON filter representation is provided

After the ExpressionResolver is registered in the Datastore API, the new IdIs expression can be used in the Datastore API operations which support the QueryFilter expression type just like any other filter expression. For example, in a Query expression:

Datastore datastore = MongoDatastore.builder().withExpressionResolver(keyIsResolver) (1)
    .build();

Query query = datastore.query().filter(new IdIs(new ObjectId("xxxx"))); (2)
1 Register the new expression resolver
2 Use the IdIs expression in a query definition

Other expression types are used to represent the elements of a query or a Datastore operation. These expression types often represent an intermediate expression type, between the highest abstract layer (i.e. an expression of the Datastore API meta-language) and the final BSON expression representation.

An example is the BsonProjection to represent a query projection.

3.13.6. MongoDB Expression resolution context

The MongoDB Datastore APIs make available an extension of the standard expression ResolutionContext, to provide a set of configuration attributes and BSON resolution context specific operations.

This resolution context extension is represented by the MongoResolutionContext API.

3.14. Commodity factories

The MongoDB Datastore API supports Datastore commodities registration using a suitable commodity factory type, which is specific for each MongoDB datastore implementation:

See the Datastore commodities definition and registration documentation section to learn how the Datastore commodity architecture can be used to provide extensions to the default Datastore API.

These commodity factory types provide a specialized MongoDatastoreCommodityContext API extension as commodity context, to make available a set of MongoDB specific configuration attributes and references, for example:

  • The current database name and reference.

  • The current client session, if available.

  • The current codec registry.

  • The default read preference, read concern and write concern.

  • The available expression resolvers.

Furthermore, it makes available some API methods to interact with the underlying database, such as the withDatabase(MongoDatabaseOperation operation) method.

Example: definition of a commodity which provides a method to create a database collection:

interface MyCommodity extends DatastoreCommodity { (1)

  void createCollection(String name);

}

class MyCommodityImpl implements MyCommodity { (2)

  private final MongoDatabaseHandler<MongoDatabase> databaseHandler;

  public MyCommodityImpl(MongoDatabaseHandler<MongoDatabase> databaseHandler) {
    super();
    this.databaseHandler = databaseHandler;
  }

  @Override
  public void createCollection(String name) {
    databaseHandler.withDatabase(db -> {
      db.createCollection(name);
    });
  }

}

class MyCommodityFactory implements SyncMongoDatastoreCommodityFactory<MyCommodity> { (3)

  @Override
  public Class<? extends MyCommodity> getCommodityType() {
    return MyCommodity.class;
  }

  @Override
  public MyCommodity createCommodity(SyncMongoDatastoreCommodityContext context)
      throws CommodityConfigurationException {
    return new MyCommodityImpl(context);
  }

}
1 Datastore commodity API
2 Commodity implementation
3 Commodity factory implementation

A Datastore commodity factory class which extends the SyncMongoDatastoreCommodityFactory or the AsyncMongoDatastoreCommodityFactory interface can be registered in a MongoDB Datastore in two ways:

1. Direct registration using the MongoDB Datastore API builder:

The MongoDB Datastore APIs supports the commodity factory registration using the withCommodity builder method.

Datastore datastore = MongoDatastore.builder() //
    .withCommodity(new MyCommodityFactory()) (1)
    .build();
1 Register the MyCommodityFactory commodity factory in given Datastore implementation

2. Automatic registration using the Java service extensions:

To automatically register an commodity factory using the standard Java service extensions based method, a class implementing either SyncMongoDatastoreCommodityFactory or AsyncMongoDatastoreCommodityFactory has to be created and its qualified full name must be specified in a file named com.holonplatform.datastore.mongo.sync.config.SyncMongoDatastoreCommodityFactory or com.holonplatform.datastore.mongo.async.config.AsyncMongoDatastoreCommodityFactory, placed in the META-INF/services folder of the classpath.

When this registration method is used, the commodity factories defined this way will be registered for any MongoDB Datastore API instance.

4. Spring ecosystem integration

The holon-datastore-mongo-spring artifact provides integration with the Spring framework for the MongoDB Datastore APIs.

Maven coordinates:

<groupId>com.holon-platform.mongo</groupId>
<artifactId>holon-datastore-mongo-spring</artifactId>
<version>5.5.0</version>

4.1. Enable a MongoDB Datastore bean

A set of Spring configuration class annotations are provided to enable a MongoDB datastore implementation and make it available as a Spring bean.

4.1.1. Synchronous MongoDB Datastore

For the synchronous Datastore implementation, the EnableMongoDatastore annotation can be used on a Spring configuration class.

To enable a MongoDB Datastore bean the following preconditions must be met:

  • A synchronous MongoDB com.mongodb.client.MongoClient type bean must be available. By default, the mongoClient bean name is used to detect the MongoClient bean reference. The client reference bean name can be changed using the mongoClientReference() annotation attribute.

  • A database name must be specified using the database() annotation attribute.

@Configuration
@EnableMongoDatastore(database = "test") (1)
class ConfigSync {

  @Bean
  public com.mongodb.client.MongoClient mongoClient() { (2)
    return com.mongodb.client.MongoClients.create();
  }

}

@Autowired
Datastore datastore; (3)
1 Use test as database name
2 MongoClient bean definition
3 A synchronous Datastore bean is configured and available from Spring context

4.1.2. Asynchronous MongoDB Datastore

For the asynchronous AsyncDatastore implementation, the EnableMongoAsyncDatastore annotation can be used on a Spring configuration class.

To enable a MongoDB AsyncDatastore bean the following preconditions must be met:

  • An asynchronous MongoDB com.mongodb.reactivestreams.client.MongoClient type bean must be available. By default, the mongoClient bean name is used to detect the MongoClient bean reference. The client reference bean name can be changed using the mongoClientReference() annotation attribute.

  • A database name must be specified using the database() annotation attribute.

@Configuration
@EnableMongoAsyncDatastore(database = "test") (1)
class ConfigAsync {

  @Bean
  public com.mongodb.reactivestreams.client.MongoClient mongoClient() { (2)
    return com.mongodb.reactivestreams.client.MongoClients.create();
  }

}

@Autowired
AsyncDatastore asyncDatastore; (3)
1 Use test as database name
2 MongoClient bean definition
3 An asynchronous AsyncDatastore bean is configured and available from Spring context

4.1.3. Reactive MongoDB Datastore

For the reactive ReactiveDatastore implementation, the EnableMongoReactiveDatastore annotation can be used on a Spring configuration class.

To enable a MongoDB ReactiveDatastore bean the following preconditions must be met:

  • An asynchronous MongoDB com.mongodb.reactivestreams.client.MongoClient type bean must be available. By default, the mongoClient bean name is used to detect the MongoClient bean reference. The client reference bean name can be changed using the mongoClientReference() annotation attribute.

  • A database name must be specified using the database() annotation attribute.

@Configuration
@EnableMongoReactiveDatastore(database = "test") (1)
class ConfigReactive {

  @Bean
  public com.mongodb.reactivestreams.client.MongoClient mongoClient() { (2)
    return com.mongodb.reactivestreams.client.MongoClients.create();
  }

}

@Autowired
ReactiveDatastore reactiveDatastore; (3)
1 Use test as database name
2 MongoClient bean definition
3 A reactive ReactiveDatastore bean is configured and available from Spring context

4.2. MongoDB Datastore bean configuration

All the annotations described above to enable a MongoDB Datastore bean provide a set of configuration options, which can be setted using the annotations attributes.

4.2.1. MongoClient bean name

By default, the mongoClient bean name is used to detect the MongoClient bean reference to use and bind to the MongoDB Datastore implementation. The client reference bean name can be changed using the mongoClientReference() annotation attribute.

@Configuration
@EnableMongoDatastore(database = "test", mongoClientReference = "syncMongoClient") (1)
@EnableMongoAsyncDatastore(database = "test", mongoClientReference = "asyncMongoClient") (2)
class Config {

  @Bean
  public com.mongodb.client.MongoClient syncMongoClient() {
    return com.mongodb.client.MongoClients.create();
  }

  @Bean
  public com.mongodb.reactivestreams.client.MongoClient asyncMongoClient() {
    return com.mongodb.reactivestreams.client.MongoClients.create();
  }

}

@Autowired
Datastore datastore;

@Autowired
AsyncDatastore asyncDatastore;
1 The syncMongoClient bean name is used to enable the synchronous Datastore bean
2 The asyncMongoClient bean name is used to enable the asynchronous AsyncDatastore bean

4.2.2. Enum codec strategy

The enumeration type value codec strategy can be configured using the enumCodecStrategy() annotation attribute. See Enum codec strategy for details.

@Configuration
@EnableMongoDatastore(database = "test", enumCodecStrategy = EnumCodecStrategy.ORDINAL) (1)
class Config {

  @Bean
  public com.mongodb.client.MongoClient mongoClient() {
    return com.mongodb.client.MongoClients.create();
  }

}
1 Set ORDINAL as enumeration codec strategy

4.2.3. Mongo database configuration

The following annotation attributes can be used to set the default read preference, read concern and write concern to use:

  • The readPreference() annotation attribute can be use to set the default read preference.

  • The readConcern() annotation attribute can be use to set the default read concern.

  • The writeConcern() annotation attribute can be use to set the default write concern.

@Configuration
@EnableMongoDatastore(database = "test", readPreference = MongoReadPreference.PRIMARY, (1)
    readConcern = MongoReadConcern.LOCAL, (2)
    writeConcern = MongoWriteConcern.ACKNOWLEDGED (3)
)
class Config2 {

  @Bean
  public com.mongodb.client.MongoClient mongoClient() {
    return com.mongodb.client.MongoClients.create();
  }

}
1 Set the default read preference
2 Set the default read concern
3 Set the default write concern

4.2.4. Primary mode

Each annotation provides a primary() attribute which can be used to control the primary mode of the MongoDB Datastore bean registration.

If the primary mode is set to PrimaryMode.TRUE, the Datastore bean created with the corresponding annotation will be marked as primary in the Spring application context, meaning that will be the one provided by Spring in case of multiple available candidates, when no specific bean name or qualifier is specified in the dependency injection declaration.

This behaviour is similar to the one obtained with the Spring @Primary annotation at bean definition time.

By default, the primary mode is set to PrimaryMode.AUTO, meaning that the registred MongoDB Datastore bean will be marked as primary only when the MongoClient bean to which is bound is registered as primary candidate bean.

4.2.5. MongoDB Datastore configuration properties

When a Mongo Datastore bean is configured using one of the available annotations (@EnableMongoDatastore, @EnableMongoAsyncDatastore, @EnableMongoReactiveDatastore), the Spring environment is automatically used as configuration properties source.

This way, many Datastore configuration settings can be provided using a configuration property with the proper name and value.

The supported configuration properties are:

1. The standard Datastore configuration properties, avaible from the DatastoreConfigProperties property set (See Datastore configuration).

The configuration property prefix is holon.datastore and the following properties are available:

Table 1. Datastore configuration properties
Name Type Meaning

holon.datastore. trace

Boolean (true / false)

Enable/disable Datastore operations tracing.

2. An additional set of properties, provided by the MongoDatastoreConfigProperties property set, which can be used as an alternative for the annotations attributes described in the previous sections.

Table 2. MongoDB Datastore configuration properties
Name Type Meaning

holon.datastore.mongo. database

String

Set the database name to which the Datastore is bound.

holon.datastore.mongo. primary

Boolean (true / false)

Mark the MongoDB Datastore bean as primary candidate for dependency injection when more than one definition is available.

holon.datastore.mongo. read-preference

A valid String which identifies one of the names listed in the MongoReadPreference enumeration.

Set the default read preference to use.

holon.datastore.mongo. read-concern

A valid String which identifies one of the names listed in the MongoReadConcern enumeration.

Set the default read concern to use.

holon.datastore.mongo. write-concern

A valid String which identifies one of the names listed in the MongoWriteConcern enumeration.

Set the default write concern to use.

holon.datastore.mongo. enum-codec-strategy

A valid String which identifies one of the names listed in the EnumCodecStrategy enumeration.

The enumeration type values codec strategy to use. See Enum codec strategy.

Example of Datastore configuration properties:

holon.datastore.trace=true (1)

holon.datastore.mongo.database=test (2)
holon.datastore.mongo.read-preference=PRIMARY (3)
1 Enable tracing
2 Set the database name to test
3 Set the default read preference to PRIMARY

4.2.6. Datastore extension and configuration using the Spring context

The MongoDB Datastore implementation supports the standard Holon Platform Datastore Spring integration features for Datastore beans configuration and extension, which includes:

@DatastoreResolver (1)
class MyFilterExpressionResolver implements QueryFilterResolver<MyFilter> {

  @Override
  public Class<? extends MyFilter> getExpressionType() {
    return MyFilter.class;
  }

  @Override
  public Optional<QueryFilter> resolve(MyFilter expression, ResolutionContext context)
      throws InvalidExpressionException {
    // implement actual MyFilter expression resolution
    return Optional.empty();
  }

}

@Component
class MyDatastorePostProcessor implements DatastorePostProcessor { (2)

  @Override
  public void postProcessDatastore(ConfigurableDatastore datastore, String datastoreBeanName) {
    // configure Datastore
  }

}

@DatastoreCommodityFactory (3)
class MyCommodityFactory implements SyncMongoDatastoreCommodityFactory<MyCommodity> {

  @Override
  public Class<? extends MyCommodity> getCommodityType() {
    return MyCommodity.class;
  }

  @Override
  public MyCommodity createCommodity(SyncMongoDatastoreCommodityContext context)
      throws CommodityConfigurationException {
    // create commodity instance
    return new MyCommodity();
  }

}
1 Automatically register a Datastore expression resolver using the @DatastoreResolver annotation
2 Post process Datastore configuration using a DatastorePostProcessor type Spring bean
3 Automatically register a Datastore commodity factory using the @DatastoreCommodityFactory annotation

When a Mongo Datastore bean is configured using one of the available annotations (@EnableMongoDatastore, @EnableMongoAsyncDatastore, @EnableMongoReactiveDatastore), the Datastore extension and configuration using the Spring context is automatically enabled.

When one of the available annotation is not used to configure the MongoDB datastore bean, the Datastore extension and configuration support can be explicitly enabled using the @EnableDatastoreConfiguration annotation on Spring configuration classes.

4.3. Multiple MongoDB Datastores configuration

When more than one MongoDB Datastore bean has to be configured using the @Enable* annotations, the dataContextId attribute can be used to assign a different data context id to each MongoDB Datastore bean definition, in order to:

  • Provide different sets of configuration properties using the same Spring environment.

  • Provide a default name pattern matching strategy with the MongoClient bean definition to use for each MongoDB Datastore to configure: if not directly specified with the mongoClientReference() attribute, the MongoClient bean definition to use for each MongoDB Datastore will be detected in Spring context using the bean name pattern: mongoClient_{datacontextid} where {datacontextid} is equal to the dataContextId attribute of the annotation.

When a data context id is defined, a Spring qualifier named the same as the data context id will be associated to the generated MongoDB Datastore bean definitions, and such qualifier can be later used to obtain the right MongoDB Datastore instance through dependency injection.

@Configuration class Config {

  @Configuration
  @EnableMongoDatastore(database = "test", dataContextId = "one") (1)
  static class Config1 {

    @Bean(name = "mongoClient_one")
    public com.mongodb.client.MongoClient mongoClient() {
      return com.mongodb.client.MongoClients.create();
    }

  }

  @Configuration
  @EnableMongoDatastore(database = "test", dataContextId = "two") (2)
  static class Config2 {

    @Bean(name = "mongoClient_two")
    public com.mongodb.client.MongoClient mongoClient() {
      return com.mongodb.client.MongoClients.create();
    }

  }

}

@Autowired
@Qualifier("one") (3)
Datastore datastore1;

@Autowired
@Qualifier("two")
Datastore datastore2;
1 Configure the first MongoDB Datastore using one as data context id: by default the bean named mongoClient_one will be used as MongoClient
2 Configure the second MongoDB Datastore using two as data context id: by default the bean named mongoClient_two will be used as MongoClient
3 A specific Datastore type bean reference can be obtained using the data context id as qualifier

5. Spring Boot integration

The holon-datastore-mongo-spring-boot artifact provides integration with Spring Boot for MongoDB Datastores auto-configuration.

To enable the Spring Boot MongoDB Datastore auto-configuration features, the following artifact must be included in your project dependencies:

Maven coordinates:

<groupId>com.holon-platform.mongo</groupId>
<artifactId>holon-datastore-mongo-spring-boot</artifactId>
<version>5.5.0</version>

The MongoDB Datastore auto-configuration class support the configuration of the three available MongoDB Datastore implementations:

5.1. Synchronous MongoDB Datastore

A synchronous MongoDB Datastore is auto-configured only when:

  • A MongoDatastore type bean is not already available from the Spring application context.

  • A valid com.mongodb.client.MongoClient type bean is available from the Spring application context.

5.2. Asynchronous MongoDB Datastore

An asynchronous MongoDB AsyncDatastore is auto-configured only when:

  • A AsyncMongoDatastore type bean is not already available from the Spring application context.

  • A valid com.mongodb.reactivestreams.client.MongoClient type bean is available from the Spring application context.

5.3. Reactive MongoDB Datastore

A reactive MongoDB ReactiveDatastore is auto-configured only when:

  • A ReactiveMongoDatastore type bean is not already available from the Spring application context.

  • A valid com.mongodb.reactivestreams.client.MongoClient type bean is available from the Spring application context.

  • The holon-datastore-mongo-reactor artifact (and the Project Reactor core library) is available from classpath.

5.4. MongoDB Datastore configuration

The Spring Boot application properties can be used to configure the MongoDB Datastore beans, for example:

application.yml
holon:
  datastore:
    trace: true
  mongo:
    database: test

5.5. Disabling the MongoDB Datastore auto-configuration feature

To disable this auto-configuration feature the MongoDatastoreAutoConfiguration class can be excluded:

@EnableAutoConfiguration(exclude={MongoDatastoreAutoConfiguration.class})

5.6. Spring Boot starters

The following starter artifacts are available to provide a quick project configuration setup using Maven dependency system:

1. Default MongoDB Datastore starter provides the dependencies to the Holon MongoDB Datastore Spring Boot integration artifact holon-datastore-mongo-spring-boot and the synchronous and asynchronous MongoDB Datastore implementations.

The MongoDB Java driver library is included in the starters dependencies.

Furthermore, the following additional dependencies are provided:

Maven coordinates:

<groupId>com.holon-platform.mongo</groupId>
<artifactId>holon-starter-mongo-datastore</artifactId>
<version>5.5.0</version>

2. Reactive MongoDB Datastore starter provides the same dependencies as the default MongoDB Datastore starter, adding the holon-datastore-mongo-reactor artifact to provide the reactive MongoDB Datastore implementation and auto-configuration.

Maven coordinates:

<groupId>com.holon-platform.mongo</groupId>
<artifactId>holon-starter-mongo-datastore-reactor</artifactId>
<version>5.5.0</version>

6. Loggers

By default, the Holon platform uses the SLF4J API for logging. The use of SLF4J is optional: it is enabled when the presence of SLF4J is detected in the classpath. Otherwise, logging will fall back to JUL (java.util.logging).

The logger name for the MongoDB Datastore module is com.holonplatform.datastore.mongo.

7. System requirements

7.1. Java

The Holon Platform JDBC Datastore module requires Java 8 or higher.

7.2. MongoDB Java Drivers

The MongoDB Java Driver version 3.5+ is required to use this module.

The MongoDB Java Driver version 3.8+ is recommended for a full compatibility with the module APIs, for example to use the transactions management support.

For asynchronous and reactive interaction support, the MongoDB Reactive Streams Java Driver is required.