In my spring boot app utilizing mongodb I am trying to get a list of distinct nested objects.
The document looks like this (other details removed fore brevity):
class Movie {
otherproperties...;
List<CastRoleLink> cast;
...
}
//structure of CastRoleLink
class CastRoleLink {
String name;
String urlName;
String roleID;
}
//additional class for structure of results
class CastLink {
String name;
String urlName;
}
What I really want is a list of all the unique CastLink from all of the Movies matching my criteria, which means that I need all of the distinct CastRoleLink objects without the roleID. The tricky part is that for different movies I could have the same name and urlName properties but a different roleID property. In these cases I would consider them the same because the CastLink would be the same. My current solution is close but not quite right.
//query is Query object with criteria for Movies
mongoTemplate.findDistinct(query, "cast", Movies.class, CastLink.class);
This gives me duplicates when the name and urlName properties are the same but the roleID property is different. Is there a way that I can find distinct name and urlName objects while ignoring the roleID property?
I figured it out. The key is to use Aggregation.
Aggregation aggregation = newAggregation(
match(criteria),
unwind("cast"),
group("cast.urlName").addToSet("cast.name").as("name").addToSet("cast.urlName").as("urlName")
project("name").and("urlName").previousOpertaion(),
sort(Sort.Direction.ASC, "name")
)
return mongoTemplate.aggregate(aggregation, Movie.class, CastLink.class).getMappedResults();
Related
I have a User model on a Mongo collection that looks like this:
User.class
#Id
private String userId;
private String userName;
private String organizationId;
private String organizationName;
private String status
I need to get a list of OrganizationDTO to send as a response to the frontend. The DTO is shaped as below:
OrganizationDTO.class
private String organizationId;
private String organizationName;
But in the collection is obviously possible that different Users have the same organizationId and organizationName, so I want to list them only once.
The list of OrganizationDTO should contain every distinct organizationId/Name that has a status included in a set I choose.
I'll be glad to add everything that could be helpful if needed.
I tried using mongoTemplate.findDistinct() but it clearly isn't the solution I'm looking for.
The over-complicated "solution" I found is this:
Query orgIdListQuery = new Query().addCriteria(
Criteria.where("status").in(statusList)
);
List<String> organizationIdList = mongoTemplate.findDistinct(orgIdListQuery, "organizationId", User.class, String.class);
List<String> organizationNameList = mongoTemplate.findDistinct(orgIdListQuery, "organizationName", User.class, String.class);
// Map the two lists to get a single one with both fields, obtaining a list of OrganizationDTO
but I didn't like it at all, so I tried with aggregations:
MatchOperation match = new MatchOperation(getCriteria(statusList)); //getCriteria returns the Criteria as above
GroupOperation group = new GroupOperation(Fields.fields("organizationId", "organizationName"));
Aggregation aggregation = Aggregation.newAggregation(match, group);
AggregationResults<OrganizationDTO> results = mongoTemplate.aggregate(aggregation, User.class, OrganizationDTO.class);
return results.getMappedResults();
It seems that I'm near the right implementation, but at the moment the result is a List with some empty objects. Is this the right solution once the aggregation is correct? Or maybe there's something better?
I think the problem can be the result can't be serialized into your OrganizationDTO so you can try to add a $project stage into aggregation, something like this (not tested):
ProjectionOperation project = Aggregation.project().andExclude("_id").and("organizationId").nested(Fields.fields("_id.organizationId")).and("organizationName").nested(Fields.fields("_id.organizationName"));
Aggregation aggregation = Aggregation.newAggregation(match, group, project);
And now the result is like the DTO and can be mapped so now should not be empty objects.
I have a Spring Java microservice using Mongodb and Javers for versioning. The document being persisted is large and includes a list of value objects. At it's simplest level the data structure looks as below.
class MyEntity {
String id,
Map<String, ItemA> items,
}
class ItemA {
List<ItemB> values
}
class ItemB {
String name,
LocalDate startDate
}
I am trying to get the versions of the list.
I tried the following code
String path = "items/" + key + "/values";
JqlQuery query = QueryBuilder.byValueObjectId(id, MyEntity.class, path)
.withShadowScope(ShadowScope.DEEP_PLUS)
.build();
List<Shadow<List<ItemB>>> shadows = javers.findShadows(query);
However it returns no shadows. I tried annotating ItemB as a #ValueObject and tried changing the ShadowScope but had no improvement.
I have below code in place where I am trying to get only 1 column (JSON type) from postgres DB and map it to a POJO class. But, in the result I am getting correct count but with null values only, even though data is present. Any Help/Suggestion is appreciated.
POJO Class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class EmployeePayload {
private String empName;
private String empID;
//getters and setters
}
Query Execution Code ::
String query = "SELECT emp_payload FROM EmpDetails";
ResultSetHandler<List<EmployeePayload>> employeePayload = new BeanListHandler<EmployeePayload>(EmployeePayload.class);
List<EmployeePayload> resultList = runner.query(dbConnection, query, employeePayload);
log.info(":: resultList is :: " + resultList);
Data in DB ::
"{""empName"":""james"",""empID"":""008""}",
"{""empName"":""bond"",""empID"":""007""}"
Result in Log ::
resultList is :: [EmployeePayload{empName='null', empID='null'}, EmployeePayload{empName='null', empID='null'}]
The BeanListHandler comes from apache dbutils (docs)
By looking at the docs / source, it's implemented by mapping single columns to single properties of the Bean. You are essentially trying to map a single column to multiple properties which does not work. Now there are two ways to go about this:
Writing your own RowProcessor
Rewriting the query to return multiple columns.
In this situation I would favor the second solution for it's simplicity as postgres has this functionality built-in for its json field types.
Your query could look like this:
String query = "SELECT emp_payload->'empName' AS empName, emp_payload->'empID' AS empID FROM EmpDetails";
(The AS ... might not be necessary but I don't know how psql generates the column names for extracted json values).
If you would execute this query directly in postgres, you would get a result set with column names empName and empID which is exactly what the BeanProcessor (the default processor for BeanListHandler) expects.
I've been started a project with mongodb and spring boot and spring JPA data and I realised I cannot map my data model to entity and make a query on that easily, so I have two questions,
My data model is like that ( just for one Collection )
{
name: "Name",
lastName: "Last Name",
attributes: {
age: 25
eye: {
color: "RED",
size: "BIG"
}
}
}
And my entity is
#Entity // or #Document
public class User{
private String name;
private String lastName;
private Map<String, ?> attributes = new HashMap<>();
// id or the setter getter are omitted
}
Can I map attributes property in my mongodb collection like I did ( Map )
How can I make query for finding the attributes?
Can I do it like that?
List<User> findAllByAttributesAge(Integer age);
Today I had a problem with Map query in Spring Mongodb, and since this question pops the first one in google I will provide another answer.
Since the other answer references to documentation, and that documentation does not have a lot of information, I am going to put an example of how good is the #Query annotation for dynamic schema:
#Query(value = "{'attributes.age' : ?0}")
List<User> findAllByAttributesAge(int age);
Also, you could query eye color, too:
#Query(value = "{'attributes.eye.color' : ?0}")
List<User> findAllByAttributesEyeColor(String color);
As the other answers documentation says, you could filter the result, and receive only the part of the document that you prefer:
// It will return the users with only name and last name
#Query(value = "{'attributes.age' : ?0}", fields = "{ name : 1, lastName : 1 }")
List<User> findAllByAttributesAge(int age);
Can I map attributes property in my mongodb collection like I did ( Map )
Yes, you can, and it may prove useful (though tricky) for a "dynamic" or "partly defined" schema.
If you know your schema in advance, I strongly recommend that it shows in your domain object. Take a look here.
How can I make query for finding the attributes?
If you don't use a dynamic schema, property traversal for nested properties is clearly explained in the Spring Data MongoDB Reference Documentation.
If you use dynamic schema, you'll most probably use MongoDB JSON based query methods.
This gave me a tough time. The other answer pretty much helps with querying an object by the field value of an hashmap if you know the key for the field. But what if you don't know the key by which the object will be queried? I labored for hours trying to figure out what's wrong even after I followed Noki's very good explanation exactly.
In my case, I have a Customer class that has a hashmap field to store account objects.
public class Customer {
private String firstName;
private String lastName;
private Map<String, Account> customerAccounts = new HashMap<>();
//Constructors, getters, setters...
}
A data Model collection looks something like this
{
firstName: "firstname",
lastName: "lastname",
customerAccounts: {
"0123456789": {
firstName: "firstName",
lastName: "lastname",
accoutNumber: "0123456789",
accountType: "Savings"
},
"1012234494": {
firstName: "firstname",
lastName: "lastname",
accoutNumber: "1012234494",
accountType: "Current"
}
}
}
So, I wanted to query the customer from the database by the accountNumber field of the account object inside customerAccounts. Since the keys for every account in each customerAcoounts object in any Customer is unique and dynamic, and no Customer collection has a similar accountNumber with another Customer collection, you'll need to write your query like this:
#Query(value = "{'customerAccount.?0.accountNumber' : ?0}"
Optional<Customer> findCustomerByCustomerAccount(String accountNumber);
The ?0 in 'customerAccount.?0.accountNumber' will take care of the dynamic value, find an object that its key matches exactly the argument coming in and finally check the accountNumber field for that account object.
If I got a collection full of following elements
#Entity
public void MyEntity{
public String name;
public String type;
...
}
And I want to return a List<String> (or Set) of not the elements, but only their name fields.
List<String> allNames = datasotre.find(MyEntity.class).asList("name");
This is sample query, there is no such method of Morphia datastore.
To limit the fields returned call the "retrievedFields" method on Query. For example, to only get the name field of all MyEntity objects:
datastore.find(MyEntity.class).retrievedFields( true, "name").asList()
Edit - You can get a list Strings using the following query as long as you don't mind that the list will only contain unique values (i.e. no duplicate names):
DBCollection m = datastore.getCollection( MyEntity.class );
List names = m.distinct( "name", new BasicDBObject() );
The "names" list will only contain Strings.
The problem here is that there is no actual query for "keys". The queries all return "key/value pairs".
In theory, the fields in datastore.find() should map to the fields in MyEntity so you could just use reflection. However, if you have other people writing to the DB from different places they may have seeded extra tables.
If this is the case, you will need to run a Map/Reduce to get the list of all the "key" names.
There is a sample here.
You are looking for datastore.find(MyEntity.class).retrievedFields(true, "name").asList();. This will contain the _id and name attribute.
See http://code.google.com/p/morphia/wiki/Query#Ignoring_Fields