Spring Boot and Mongo DB: only get latest version of Document - spring-data-mongodb

I have a document collection where each unique templateId can have multiple versions:
#Document(collection = "templates")
public class TemplateEntity {
#Id
private String id;
private String templateId;
private int version;
}
Is there an easy way to only get the latest versions for each templateId with a simple MongoRepository interface method?
Here is my current programmatic workaround to explain what I am trying to do and my helper query method to sort the list for my needs:
#GetMapping("/list")
public List<TemplateEntity> listTemplates() {
List<TemplateEntity> filtered = new ArrayList<>();
List<TemplateEntity> allTemplates = repository.findAllByOrderByTemplateIdAscVersionDesc();
String templateId = "";
for (TemplateEntity tpl : allTemplates) {
if (!tpl.getTemplateId().equals(templateId)) {
filtered.add(tpl);
templateId = tpl.getTemplateId();
}
}
return filtered;
}

For this kind of operation you need to make use of the MongoDB Aggregation Framework.
db.getCollection("templates").aggregate([
{ "$group": { "_id": "$templateId", "tmpId": { "$first": "$_id" }, "maxVersion": { "$max": "$version"} } },
{ "$project": {"_id": "$tmpId", "templateId": "$_id", "version": "$maxVersion"} }
])
Spring Data offers support for Aggregations via MongoOperations. Please have a look at the Reference Documentation.
Generally speaking I think having something like an #Aggregate annotation in Spring Data would be benefitial. I've opended DATAMONGO-2153 to investigate that option.

Related

How do I query a referenced object in MongoDB

There are two collections in my mongo database: Book and Author
The Book collection has an author field which references the author who created it
const schema = new mongoose.Schema({
...
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Author'
})
I am using graphql, and my current code looks like this. booksByAuthor is a query that takes in a name, then returns the books written by that author.(this is working and gives me the correct output)
booksByAuthor: async (root) => {
const author = await Author.findOne({name: root.name})
return await Book.find({
author: author._id
})
}
My current implementation first queries the Author collection, and then uses the id of the author to then query the Book collection. However, I feel like there must be a way to do this without going through the Author collection.
Ideally, I want it to be something like this: (pseudo-code)
return await Book.find({
author.name: {root.name}
})
I've tried doing
return await Book.find({
"author.name": { $in: [root.name] }
})
and
return await Book.find({
name: { author: { $in: [root.name] } }
})
but both returns an empty array. I don't know if I should even be using $in for this case
You can use $lookup to achieve this in single query. $lookup is similar to joins in sql.
Based on your question i have created some sample documents. Below is the query for getting the books by author.
db.book.aggregate([
{
"$lookup": {
"from": "author",
"localField": "author",
"foreignField": "_id",
"as": "author"
}
},
{
"$match": {
"author.name": "Kafka"
}
}
])
Here is the Mongo Playground. Please follow the link to see the query in action. You will also able to see the sample documents i have used. Feel free to play around with it according to your needs.

Transform JSONArray values in WSO2 EI

I am having some transformation logic during mediation in wso2 EI. i have stuck while "#type":"Lead" into "attributes":{"type":"Lead"} transformation.
Note: i know to use payloadfactory to achieve this( Expected format). received at least 1000 JSONObject under getFields Array.
Input:
{
"getDataResponse":{
"result":{
"DataSyncTime":"sometime",
"getFields":[
{
"#type":"Lead",
"FirstName":"Justin"
},
{
"#type":"Lead",
"FirstName":"Manoj"
}
//received atleast 1000 records ie jsonobject here(Dynamic)
],
"Message" :"Text messsage",
"Success":200
}
}
}
Expected:
{
"DataSyncTime":"sometime",
"getEvents":[],
"getFields":[
{
"attributes":{"type":"Lead"},
"FirstName":"Justin"
},
{
"attributes":{"type":"Lead"},
"FirstName":"Manoj"
}
],
"getTask":[],
"Message" :"Text messsage",
"Success":200
}
Trying using Foreach mediator with Payload Factory mediator to loop through the array. If Payload Factory does not satisfy your requirement, you may have to use a Custom Class mediator to achieve your requirement.

#RepositoryRestResource's collectionResourceRel attribute not being obeyed

I have a MongoRepository of
#RepositoryRestResource(collectionResourceRel = "tools")
public interface ToolRepository extends MongoRepository<Tool, Long>
Tool can be one of 2 implementations:
public class Screwdriver extends Tool
public class Hammer extends Tool
Tool is mapped using #JsonTypeInfo
#JsonTypeInfo(use =
com.fasterxml.jackson.annotation.JsonTypeInfo.Id.CLASS, include =
As.PROPERTY, property = "_class")
public abstract class Tool
When I do toolRepository.findAll() this returns a JSON response of :
{
"_embedded" : {
"screwdrivers" : [ {
"name" : "Screwdriver",
...
} ],
"hammers" : [ {
"name" : "Hammer",
...
}
}
Expected response should be :
{
"_embedded" : {
"tools" : [ {
"name" : "Screwdriver",
...
},
{
"name" : "Hammer",
...
}
}
The collectionResourceRel is not being obeyed for classes with Json mapping data in.
Investigating further; PersistentEntitiesResourceMapping.getMetadataFor() (within Spring) is saying make sure that if there's no entry for these subclasses of tool inside the ResourceMetadata cache then use a TypeBasedCollectionResourceMapping which results in each class having its own entry in the json response.
Is there a way of telling Spring data rest that a specific subclass should be bound to a specific repository, in this case is there a way of telling Spring data rest that Screwdriver is part of the ToolRepository and therefore should use the collectionResourceRel of this repository?
Try to set these annotations:
#RestResource(rel = "tools", path = "tools")
public abstract class Tool {...}
#RepositoryRestResource(path = "tools", collectionResourceRel = "tools", itemResourceRel = "tool")
public interface ToolRepository extends MongoRepository<Tool, Long> {...}
See working example. It's not about Mongo but I'm sure it will be useful...

Google Cloud Datastore REST API v1 (gqlQuery)

Would someone walk me through the syntax on how to use the runQuery method to preform a gqlQuery using the Google Cloud Datastore REST API v1. I only need help in understanding the query structure of the REST API and do not need help with Google OAUTH or setting up Cloud Datatore. I have included a link to the documentation, example gqlQuery to run and Cloud Datastore structure below.
Method: projects.runQuery
https://cloud.google.com/datastore/docs/reference/rest/v1/projects/runQuery
Example gqlQuery = ("Select * From Customer")
Sample Google DataStore Structure
id = "78090499534213120"
Address = "888 Fake St"
City = "Fake City"
FirstName = "Name"
LastName = "Name"
State = "CT"
POST https://datastore.googleapis.com/v1/projects/{YOUR_PROJECT_ID}:runQuery?key={YOUR_API_KEY}
Where {YOUR_PROJECT_ID} can be found on the Cloud Console home page for your project and is alphanumeric.
The body of the message will be a JSON string with the details of the query, so in your case:
{
"gqlQuery":{"queryString": "select * from customer"}
}
If you want to include conditionals, you can do this as well by using parameter binding. The below example shows how to use positional binding to achieve this:
{
"gqlQuery":
{
"queryString": "select * from Customers where State = #1 AND FirstName = #2",
"positionalBindings": [
{"value": {"stringValue": "CT"}}.
{"value": {"stringValue": "Name"}}
]
}
}
Instead of positional binding, you could also do named bindings:
{
"gqlQuery":
{
"queryString": "select * from Customers where State = #State AND FirstName = #FirstName",
"namedBindings": {
"State": {"value": {"stringValue": "CT"}},
"FirstName": {"value": {"stringValue": "Name"}}
}
}
}
Lastly, and not recommended as it can lead to query injection attacks, you can include literals in the query string itself by setting the appropriate flag:
{
"gqlQuery":
{
"queryString": "select * from Customers where State = 'CT' AND FirstName = 'Name'",
"allowLiterals": true
}
}
Do you get a proper response back when you make this request? I get the following 200 response, but don't see any of my expected rows of data
{
"batch": {
"entityResultType": "PROJECTION",
"endCursor": "CgA=",
"moreResults": "NO_MORE_RESULTS"
}
}

how to perform a save operation with breeze and angular without using EF context

Within my project I am trying to perform a save operation that updates my Breeze model as well as passes the updated object to my webAPI. In this project I am not using EF context as the project has been modeled to work with other interfaces. So within my webAPI class I have the following:
[BreezeController]
public class ReportLibraryApiController : ApiController
{
readonly long userid = 1;//testing
readonly IReportLibraryManager manager = new ReportLibraryManager();//interface
//method for share
[Route("reportlibrary/SetReportShare/")]
[HttpPost]
public DTOs.Report SetReportShare(JObject report )
{
//within here I plan to unwrap the JSobject and pull out the necessary
//fields that I need
DTOs.Report updatedreport=null;
//manager.ShareReport(updatedreport.ReportId);
//manager.UnShareReport(updatedreport.ReportId);
return updatedreport;
}
}
The Report Object looks like this
public class Report
{
public Int64 ReportId { get; set; }
public string ReportName { get; set; }
public string ReportDescription { get; set; }
public DateTime? ReportDateCreated { get; set; }
public string ReportOwner { get; set; }
public IEnumerable<ReportLabel> ReportLabels { get; set; }
public bool IsShared { get; set; }
public bool IsFavorite { get; set; }
}
From my angular service I am trying to call the save operation as:
app.factory('reportLibraryService', function(breeze, model){
var serviceName = "reportlibrary/";
var ds = new breeze.DataService({
serviceName: serviceName,
hasServerMetadata: false,
useJsonp: true,
});
var manager = new breeze.EntityManager({ dataService: ds });
model.initialize(manager.metadataStore);
function returnResults(data){ return data.results}
function setReportShare(report) {
var option = new breeze.SaveOptions({ resourceName: 'SetReportShare' })
return manager.saveChanges(null, option).then(returnResults)
};
}
I realize the return results may not be correct but at this point I am just trying to call the save operation in the API. When I run the code everything executes but the save/setting of the share does not fire. A secondary question is I'm still not clear on how the breeze model is updated? Am I supposed to issue a new query from the api and pass that back or is there a way to update the cached object. I'm new to breeze (obviously) and trying to figure out where to look.All the examples I have seen thus far use EF context to perform these actions. However in my case I don't have that option.
Should breeze be making the call or since I am not using EF should I be using $http directive to push the object up. then return a new object to breeze for binding? (that seems a little heavy to me and now how it is designed to work).
I'd appreciate any guidance, or information or how to figure this out.
Edited for more information...
Here is a little more detail based on some of the questions posted by Ward to my original question:
where is your metadata that you code says is not on the server?
The solution I am working on does not expose the EF context this far our. As a result interfaces have been created that handle the queries etc within the project. As a result I don't have the ability to use EF. Very similar to the Edmunds sample. I created a meta definition in the web project which references a report object I have defined. This class is much different from what is returned from the DB but it represents what the UI needs. I created two models
report.cs
public class Report
{
public Int64 ReportId { get; set; }
public string ReportName { get; set; }
public string ReportDescription { get; set; }
public DateTime? ReportDateCreated { get; set; }
public string ReportOwner { get; set; }
public IEnumerable<ReportLabel> ReportLabels { get; set; }
public bool IsShared { get; set; }
public bool IsFavorite { get; set; }
}
model.js
app.factory('model', function () {
var DT = breeze.DataType;
return {
initialize: initialize
}
function initialize(metadataStore) {
metadataStore.addEntityType({
shortName: "Report",
namespace: "Inform.UI.DTOs",
dataProperties: {
reportId: { dataType: DT.Int64, isPartOfKey: true },
reportName: { dataType: DT.String },
reportDescription: { dataType: DT.String },
reportDateCreated: { dataType: DT.String },
reportOwner: { dataType: DT.String },
reportLabels: { dataType: DT.Undefined },
isShared: { dataType: DT.Bool },
isFavorite: { dataType: DT.Bool }
},
navigationProperties: {
labels: {
entityTypeName: "Label:#Inform.UI.DTOs", isScalar: false,
associationName: "Report_Labels"
}
}
});
metadataStore.addEntityType({
shortName: "ReportLabel",
namespace: "Inform.UI.DTOs",
dataProperties: {
labelId: { dataType: DT.Int64, isPartOfKey: true },
reportId: { dataType: DT.Int64 },
labelName: { dataType: DT.String },
isPublic: { dataType: DT.Bool },
reports: { dataType: DT.Undefined }
},
navigationProperties: {
labels: {
entityTypeName: "Report:#Inform.UI.DTOs", isScalar: false,
associationName: "Report_Labels", foreignKeyNames: ["reportId"]
}
}
});
}
})
why are you configuring useJsonp = true ... and then POSTing to the SetReportShare endpoint?
The dataservice was originally defined for GET requests for querying/returning results to the client. I reused the dataservice and tacked on the POST event. Based on your comment though I assume this is a no-no. In looking at the project I am working within the same domain (and always will be) so I don't really think I need jsonp as part of the dataservice definition. Am I wrong in thinking that?
I gather from your question that I should have a separate dataservice for POST's and a separate one for GET's
why is it returning the DTOs.Report type when you can tell by looking at the standard Breeze SaveChanges method that it returns a SaveResult?
This was a typo on my part. The save Result was originally defined as just a JObject. My intent is to return (if necessary) an updated Report object. However I am unsure of the best practice here. If the client (breeze) is updating the cache object. Why do I need to return the report object. Would it not be better to just return a success fail result or boolean of some sort vs. returning an entire report object?
why is your client-side saveChanges callback treating the response as if it came from a query?
This is very simple as you stated, I have no idea what I am doing. I am certainly diving into the deep end as I don't have a choice right now... My question here is when you perform a CRUD operation are these not wrapped in a promise as when performing a query? Or is the promise only important for queries?
Thank you again-
-cheers
You're jumping into the deep end w/o knowing how to swim.
You haven't explained why you're going exotic. That's OK but you want to ease into it. I strongly recommend that you start with the "happy" path - Web API, EF, SQL Server - and then unwind them slowly as you start to understand what's going on.
If you can't do that, at least look at the NoDb sample which doesn't use EF or SQL Server (see the TodoRepository).
You absolutely can do what you're striving to do ... once you know how ... or find someone who does.
At this point, you've created nothing but questions. For example,
where is your metadata that you code says is not on the server?
why are you configuring useJsonp = true ... and then POSTing to the SetReportShare endpoint?
why is it returning the DTOs.Report type when you can tell by looking at the standard Breeze SaveChanges method that it returns a SaveResult?
why is your client-side saveChanges callback treating the response as if it came from a query?
As for your questions:
the breeze model is updated automatically when the save completes
no, you don't issue a new query from the api
yes, you can (and usually do) return the server-updated object back in the SaveResult.
$http (a method not a directive) is used by Breeze itself to communicate to the server; using it directly isn't going to change anything.
Not sure any of my answers help. But I do think you'll be fine if you take it from the top and work deliberately forward from one thing you understand to the next.

Resources