Mapping a Collection without using DBRef (Spring Data MongoDB) - mongodb

I've been reading a lot that the use of DBRef for Collection Mapping in Spring Data/MongoDB is discouraged. So, how can I implement a mapping that stores an array of ObjectId taken from those objects inside the students collection?
Assuming that I have the following POJO model:
#Document (collection = "courses")
public class Course {
#Id
private String id;
private String name;
private List<Student> students = new LinkedList<Student>();
//.. constructors, getters and setters ..
}
public interface CourseRepository extends MongoRepository<Course, String> { }
The result should be something like this:
courses
{
_id : ObjectId("foo"),
_class: "model.Course",
name: "MongoDB for Dummies",
students: [ ObjectId("foo2"), ObjectId("foo3"), ... ]
}
Instead of this:
courses
{
_id : ObjectId("foo"),
_class: "model.Course",
name: "MongoDB for Dummies",
students: [
DBRef("student", ObjectId("foo2")),
DBRef("student", ObjectId("foo3"))
]
}
Thanks!

You might wanna try the obvious and change students to a List<ObjectId>. ;)

Related

Get likes count and whether user is liked record from MongoDB

I am trying to get count of likes and if user is liked this post in Mongo.
I managed to get this via native query with facets, but problems is how can i map this two fields on my custom java class (LikeStatus.class)?
thanks in advance!
please code below:
POJO:
public class LikeStatus {
String entityId;
LikedEntityType entityType;
long likesCount;
boolean isLikedByUser;
}
Document class:
public class Like {
#Id
private String id;
#EqualsAndHashCode.Include
private String entityId;
#EqualsAndHashCode.Include
private String profileId;
#EqualsAndHashCode.Include
private LikedEntityType entityType;
private LocalDateTime createdAt = LocalDateTime.now();
}
Query i used in Mongo:
> db.likes.aggregate({$facet:
{count:[
{$match:{entityId:"entityId"},
$match:{entityType:"OFFER"}}, {$count:"count"}],
isliked:[{$match:{profileId:"profileId4"}}, {$count:"isliked"}]}}).pretty();
and gives me result:
{
"count" : [
{
"count" : 3
}
],
"isliked" : [
{
"isliked" : 1
}
]
}
I managed to find solution which is suited my needs, hope it will be useful who faced with the same kind of queries in Mongodb and it will give some idea how it can be solved)
Java solution: i used Facet object to collect two aggregation request in one query like this:
In repository layer i created query:
default Aggregation getLikeStatus(String entityId, String entityType, String profileId){
FacetOperation facet = Aggregation.facet(match(where(ENTITY_ID_FIELD).is(entityId).and(ENTITY_TYPE_FIELD).is(entityType)),
Aggregation.count().as(LIKES_COUNT_FIELD)).as(LIKES_COUNT_FIELD)
.and(match(where(ENTITY_ID_FIELD).is(entityId)
.and(ENTITY_TYPE_FIELD).is(entityType)
.and(PROFILE_ID_FIELD).is(profileId)),
Aggregation.count().as(IS_LIKED_BY_USER_FIELD)).as(IS_LIKED_BY_USER_FIELD);
ProjectionOperation project = project()
.and(ConditionalOperators.ifNull(ArrayOperators.ArrayElemAt.arrayOf(LIKES_COUNT_FIELD).elementAt(0)).then(0)).as(LIKES_COUNT_FIELD)
.and(ConditionalOperators.ifNull(ArrayOperators.ArrayElemAt.arrayOf(IS_LIKED_BY_USER_FIELD).elementAt(0)).then(0)).as(IS_LIKED_BY_USER_FIELD)
.andExclude("_id");
return newAggregation(facet, project);
}
then in service layer it returns Document object which is mapped on my custom class LikeStatus fields:
Document status = template.aggregate(likeRepo.getLikeStatus(entityId, entityType, profileId), Like.class, Document.class).getUniqueMappedResult();
my custom POJO:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class LikeStatus {
String entityId;
LikedEntityType entityType;
long likesCount;
boolean isLikedByUser;
}
Also i post native query solution in Mongo for reference:
db.likes.aggregate([
{$facet:
{"likesCountGroup":[
{$match:{entityId:"entityId", entityType:"TYPE"}},{$count:"likesCount"}
],
"isUserLikedGroup":[
{$match:{entityId:"entityId", entityType:"TYPE", profileId:"604cd12c-1633-4661-a773-792a6ec22187"}},
{$count:"isUserLiked"}
]}},
{$addFields:{}},
{$project:{"likes":{"$ifNull":[
{$arrayElemAt:["$likesCountGroup.likesCount", 0]},0]},
"isUser":{"$ifNull:[{$arrayElemAt["$isUserLikedGroup.isUserLiked",0]},0]}}}]);

Many to many relationship using spring boot and mongoDB

I am new to spring boot technology.
I have a problem, how to map many to many relationships using spring boot when passed the objectId of another collection and retrieve the details of that object too.
Ex:
There are two models named Vehicles & Categories. When creating a new vehicle type we can pass the Category Id like below.
When retrieving the Vehicle should display like this.
This example developed using node.js and express.js.
Like that way how I can do that for spring boot. I tried with #JoinColumns, #DBRef and none of them doesn't work for me.
Here are my models.
Vehicle model
#Document(collection = "Vehicle")
public class Vehicle {
#Id
private String id;
private String name;
private String type;
private List<Category> categoryList;
//getters and setters go here
Category model
#Document(collection = "Category")
public class Category {
#Id
private String id;
private String name;
private Double amount;
//getters and setters go here
It's better if you can tell me the way of implementing the Controller and Service or Repository according to this.
Thanks in advance.
can't help you with spring-boot stuff but you need to do a $lookup operation to join the foreign documents using the mongodb driver you're using.
the following is how it's done using mongo shell:
db.Vehicle.aggregate(
[
{ $match: { _id: "vehicle-id" } },
{
$lookup: {
from: "Category",
localField: "categories._id",
foreignField: "_id",
as: "categories"
}
}
])
see it in action here: https://mongoplayground.net/p/OgXrAxE2uXu
ගුඩ් ලක්‌!

MongoTemplate aggregate projection, getting an exception when map array properties

I have two objects and two projection objects like below,
Main Objects
public class MainItem {
private String name;
private List<subItem> subItems;
}
public class subItem {
private String id;
private String groupId;
private String displayName;
private Status status;
}
Projection objects
public class MainItemLight {
private String name;
private List<subItemLight> subItemList;
}
public class subItemLight {
private String id;
private String name;
}
I'm trying to map Main objects to projection objects and return a list of MainItemLight objects. Below is my code,
mongoTemplate.aggregate(
newAggregation(project("name")
.and("subItems").as("subItemList")
.and("subItems.displayName").as("subItemList.name")
),
"MyCollection", MainItemLight.class).getMappedResults();
When I'm trying to map subItems.displayName to subItemList.name I get bellow exception,
Command failed with error 40176 (Location40176): 'Invalid $project
:: caused by :: specification contains two conflicting paths.
Cannot specify both 'subItemList.name' and 'subItemList'
Any idea how to fix this?
You need to do it like this:
db.collection.aggregate([
{
$project: {
name: 1,
subItemList: {
$map: {
input: "$subItems",
as: "item",
in: {
id: "$$item.id",
name: "$$item.displayName"
}
}
}
}
}
])
MongoPlayground
MongoTemplate
Aggregation agg = newAggregation(project("name").and(
VariableOperators.Map.itemsOf("subItems").as("item").andApply(
doc -> new Document()
.append("id", "$$item.id")
.append("name", "$$this.displayName")
)
).as("subItemList"));
Note: The $map implementation is not friendly in Spring Mongo, so wee need to implement it manually.
Source: SpringData mongoDB API for Aggregation $map

MongoDB - FindById is not working and giving null

I am using Spring Boot(V2.2.2.RELEASE) + Spring Data Mongo Example. In this example, I've records like below
{
"_id" : ObjectId("5cb825e566135255e0bf38a4"),
"firstName" : "John",
"lastName": "Doe"
}
My Repository
#Repository
public interface EmployeeRepository extends CrudRepository<Employee, ObjectId>{
Employee findById(String id);
}
Code
Employee findById = employeeRepository.findById("5cb825e566135255e0bf38a4");
System.out.println(findById);
Even below code not working
Query query = new Query(Criteria.where("id").is(new ObjectId("5cb825e566135255e0bf38a4")));
List<Employee> find = mongoTemplate.find(query, Employee.class);
Seems there might be two issues
ObjectId should be used
employeeRepository.findById(new ObjectId("5cb825e566135255e0bf38a4"))
ID field goes with underscore
new Query(Criteria.where("_id").is(new ObjectId("5cb825e566135255e0bf38a4")))
I'm not a Java guy, so might miss smth, but at least give it a try :)
Using the input document with _id: ObjectId("5cb825e566135255e0bf38a4") you can use either of the approaches. Assuming there is the document in the employee collection you can query by the _id's string value.
public class Employee {
#Id
private String id;
private String name;
// constructors (with and with arguments)
// get methods
// override toString()
}
// Spring-data app using MongoTemplate
MongoOperations mongoOps = new MongoTemplate(MongoClients.create(), "test");
Query query = new Query(where("id").is("5cb825e566135255e0bf38a4"));
Employee emp = mongoOps.findOne(query, Employee.class);
-or-
MongoRepository
interface extends CrudRepository and exposes the capabilities of the
underlying persistence technology in addition to the generic
persistence technology-agnostic interfaces such as CrudRepository.
#Repository
public interface EmployeeRepository extends MongoRepository<Employee, String> {
#Query("{ 'id' : ?0 }")
Optional<Employee> findById(String id);
}
// Spring-data app using the Repository
Optional<Employee> empOpt = employeeRepository.findById("5cb825e566135255e0bf38a4");
if (empOpt.isPresent()) {
System.out.println(empOpt.get());
}
else {
System.out.println("Employee not found!");
}
I too faced the issue. Interestingly things are working fine with version 2.1.7RELEASE
<documentRepository>.findById(<StringID>) treats as String Id in 2.2.2RELEASE
while 2.1.7RELEASE transforms it to ObjectId and hence the find query works.

Spring Data Mongodb POJO mapping

I know that Spring Data MongoDB is a POJO centric model for interacting with a MongoDB DBCollection. But in my case the POJO mapping is somewhat irrelevant.For example the JSON string that I need to store as a single document in a collection is as follows
columns:[
{
name:"xyz"
age:12
},
{
name:"abc"
age:34
},
{
name:"pqr"
age:76
}
]
What can be the equivalent POJO mapping in this case?
I think you can achieve it through this mapping (If I understood your question correctly):
class Person {
String name;
int age;
}
class Outer {
#Field("columns")
List<Person> persons;
}