@Document
@Field
@Id
@Indexed
@TextIndexed
@Transient
@PersistenceConstructor
Make use of online MongoDB cloud platform to create a Database and querying with it by using https://cloud.mongodb.com
Use POST
/legostore/api
to insert json to MongoDB
Use
insert()
method fromMongoTemplate
when you don't have anId
is null. A newId
will get generated.
If the object has an
Id
value: IfId
value not already present in the collection, will use it to insert. Else MongoDB will throw aDuplicateKeyException
{
"name": "NASA Apollo Saturn V",
"difficulty": "MEDIUM",
"theme": "NASA",
"reviews": [
{
"userName": "Joe",
"rating": 9
}
],
"deliveryInfo": {
"deliveryDate": "2020-04-21",
"deliveryFee": "50",
"inStock": true
},
"nbParts": 454
}
For Bulk insert use mongoTemplate.insertAll(Collection<T>)
For example:
this.mongoTemplate.insertAll(
List.of(mcLarenSenna, skyPolice, milleniumFalcon, mindstormsEve));
Use GET
/all
to get all the values from MongoDB Collection
[
{
"id": "5e9f369cda730013c3acb0e0",
"name": "NASA Apollo Saturn V",
"difficulty": "MEDIUM",
"theme": "NASA",
"reviews": [
{
"userName": "Joe",
"rating": 9
}
],
"deliveryInfo": {
"deliveryDate": "2020-04-21",
"deliveryFee": 50,
"inStock": true
},
"nbParts": 0
}
]
Use PUT
/legostore/api
to Update a Document to MongoDB. Here I have changed userName
value.
If the object has no
Id (Id is null)
, a newId
will get generated.
If the object has
Id
value
- If Id value not already present, will use it
- If Id value present, will update the document.
{
"id": "5e9f369cda730013c3acb0e0",
"name": "NASA Apollo Saturn V",
"difficulty": "MEDIUM",
"theme": "NASA",
"reviews": [
{
"userName": "Karthi",
"rating": 9
}
],
"deliveryInfo": {
"deliveryDate": "2020-04-21",
"deliveryFee": 50,
"inStock": true
},
"nbParts": 0
}
Use DELETE
/legostore/api/{id}
to Delete a Document to MongoDB.
Example:
/legostore/api/5e9f3533da730013c3acb0df
To verify this Delete, execute /all
Use mongoTemplate.dropCollection(LegoSet.class)
MongoTemplate | MongoRepository |
---|---|
+More flexible and powerful (more complex queries, aggregations) | +Easier to use because they are a higher abstraction (90% cases) |
-Low level; You need to know how Mongo queries work | +Friendly syntax for filtering data |
+ Build on top of JPA, consistent syntax, same methods as repositories over SQL | |
- Do not work on all use cases; Sometimes you need to fall back to the MongoTemplate |
In order to create a Mongo Repository, refer below code snippet.
import com.techstack.mongo.model.LegoSet;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface LegoSetRepository extends MongoRepository<LegoSet, String> {
}
Spring Data can provide proxy implementation of queries based on method names:
- findByLastName
- findByAgeLessThan
- findByActiveTrue
For more information refer JPA Query Methods and Query Creation
For some business cases, you can't achieve your expectations using Query by method (like findBy). For those cases,
use @Query
annotation.
@Query("{'_id':'123'}")
Don't always use @Query
annotation which contains statically typed queries. Because, if you change your Class property
you have to change your query accordingly.
To solve this issue, we can use QueryDSL.
- QueryDSL allows us to write queries that are type safe.
- No more magic Strings
- Scans all classes annotated with
@Document
and their children. - Generates "Query Types" during the process goal of Maven build.
- For Example: for a
LegoSet
class ==> QLegoSet class.
- For Example: for a
- We can then use query types in out code to build complex queries in a type safe manner.
To start using QueryDSL for Mongo we need to bring in:
- Library Dependencies
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-mongodb</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>4.0.0</version>
</dependency>
- Plugin
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>
org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor
</processor>
</configuration>
</execution>
</executions>
</plugin>
Execute maven goal mvn package
. Once this process done, navigate to
\target\classes\generated-sources\java\<your-mongodb-documents-package>\Qxxxxx
How to Apply multiple filters?
- Are in Stock
- Have delivery fee less than 50
- Have at least one review with a 10 rating
// build query
QLegoSet query = new QLegoSet("query");
BooleanExpression inStockFilter = query.deliveryInfo.inStock.isTrue();
Predicate smallDeliveryFeeFilter = query.deliveryInfo.deliveryFee.lt(50);
Predicate hasGreatReviews = query.reviews.any().rating.eq(10);
Predicate bestBuysFilter = inStockFilter
.and(smallDeliveryFeeFilter)
.and(hasGreatReviews);
// pass the query to findAll()
return (Collection<LegoSet>) this.legoSetRepository.findAll(bestBuysFilter);
Add another interface QuerydslPredicateExecutor
to support Predicate.
public interface LegoSetRepository extends MongoRepository<LegoSet, String>, QuerydslPredicateExecutor<LegoSet> {
}
Our Manager wants a report on all the lego sets and their average rating so he can optimize sales.
It's mainly for
- Report purpose
- Aggregation
- Data Transformation
Using @TextIndexed
- you can add it on root level properties
- You can add it on sub-documents and array elements
At various occations you would thing about migrating your data by
- changing the field name
- marking certain field not to persist
- removing certain field from the document
- etc
Dut to this, your existing query may not work as it was.
-
By configure
-
It's a Java tool which helps you to manage changes in your MongoDB and synchronize them with your application.
How can you run integration tests? Against
Which server/database?
There is full Spring support for writing integration tests
We can use an embedded (in memory) MongoDB Server for testing.
What should you test?
- Any method that uses magic String (
@Query
) - Any projection or aggregations
- Any query that is complex enough
- Don't test base implementations (
findById
) or methods that will get implemented by Spring Data Mongo via a Proxy
By add the following dependency in your pom file it would initiate an InMemory Mongo DB.
<!-- Embedded MongoDB for Testing -->
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
- In normal RDBMS it's called as relationships (PrimaryKey and ForeignKey)
- In MongoDB, in order to refer another document in your document you have to References.
- using
$ref
and$id
- using
Example:
LegoSet Collection
{
"id": "123",
"name": "NASA Apollo Saturn V",
"paymentOption": {
"$ref": "PaymentOptions",
"$id": "567"
}
}
PaymentOptions Collection
{
"_id": "567",
"type": "CreditCard",
"fee": 0
}
- Eager Loading / Serialization in Spring. Spring would load both parent and sub document
- You can't traverse the object tree to query based on payment options
json{ "paymentOptions.fee": 0 }
- No cascading operations by default on parent-child relationships. You need to manually manage them.
- But you can implement cascading operations via Mongo event listeners like
onBeforeConvert
- The Nature of NoSQL is to minimize relationships between collections. Use
@DbRef
only when really needed.
db.getCollection('legosets').find({
"paymentOptions": {
"$ref": "paymentOptions",
"$id": ObjectId("1233")
}
})