Java Mongo: How to get the max of each docuemnt - java

I have a collection with complex document, each with user Id. each userId has timestamp, so I'd like to return document for all users in organization, with the latest timestamp per each user.
This is what I tried, it sort of worked, except only the timestamp & userId fields were mapped in the result - all other data wasn't transferred:
Criteria criteria = Criteria.where("organization").is("someOrg");
Aggregation agg = newAggregation(
match(criteria),
group("userId").last("timestamp").as("timestamp")
);
AggregationResults<UserPerformanceAlert> groupResults = mongoTemplate.aggregate(agg, collectionName, UserPerformanceAlert.class);
I tried project but it kept giving me exceptions saying "java.lang.IllegalArgumentException: ExposedFields must not be null!"
Note: the full document has complex inner objects that I need to retrieve. normal find() method works just find to serialize the data to my class model.
Thanks!

Well, I just found the issue, I had to specify all the fields at the group() part:
group("userId", "type", "header", "body", "scopes", "accountId").last("timestamp").as("timestamp")

Related

MongoDB how to find all documents who's array contains a specific string?

Below is an example of my "Event" document in MongoDB. I want to be able to query all of the Event documents where the attendees array contains "623d03730e82c57fefa52fb2" (a user ID).
Here is one of my event documents:
_id: ObjectId(623ce74372a28f08dea6c959)
description: "Fun BBQ to celebrate my 21st!"
host: "623d03730e82c57fefa52fb2"
invitees: Array
location: "My address..."
name: "Fun Birthday BBQ"
private: true
date: "03/28/22"
end: "11:15 PM"
start: "06:35 PM"
attendees:Array
0: "623d03730e82c57fefa52fb2"
Here is my broken query code:
String id = "623d03730e82c57fefa52fb2";
// I have also tried Document queryFilter = new Document("attendees", id);
Document queryFilter = new Document("attendees", new Document("$in", Arrays.asList(id)));
The above code always returns an empty result. To clarify I am using Java and MongoDB Realms but that shouldn't matter.
You don't need $in, use only $eq is ok.
db.collection.find({
attendees: "623d03730e82c57fefa52fb2"
})
mongoplayground
For easier and more efficient queries, it's important that the types of field values remain consistent.
For example, if "_id" is stored at an ObjectId, then query parameters should also be of type ObjectId. Likewise, if they are strings, then consistently use strings.
If different value types are stored for individual field values, successful queries can still be possible, but not as efficiently since the field types must be considered in the queries. For example, if trying to find a doc by a field that may have a string or an ObjectId type, the query must either search for both types, or the query writer must know the type beforehand. It's easier and more efficient to just pick one type for a field and stick to it.

Get the object id after inserting the mongodb document in java

I am using mongodb 3.4 and I want to get the last inserted document id. I have searched all and I found out below code can be used if I used a BasicDBObject.
BasicDBObject docs = new BasicDBObject(doc);
collection.insertOne(docs);
ID = (ObjectId)doc.get( "_id" );
But the problem is am using Document type not BasicDBObject so I tried to get it as like this, doc.getObjectId();. But it asks a parameter which I actually I want, So does anyone know how to get it?
EDIT
This is the I am inserting it to mongo db.
Document doc = new Document("jarFileName", jarDataObj.getJarFileName())
.append("directory", jarDataObj.getPathData())
.append("version", jarDataObj.getVersion())
.append("artifactID", jarDataObj.getArtifactId())
.append("groupID", jarDataObj.getGroupId());
If I use doc.toJson() it shows me whole document. is there a way to extract only _id?
This gives me only the value i want it like the objectkey, So I can use it as reference key.
collection.insertOne(doc);
jarID = doc.get( "_id" );
System.out.println(jarID); //59a4db1a6812d7430c3ef2a5
Based on ObjectId Javadoc, you can simply instantiate an ObjectId from a 24 byte Hex string, which is what 59a4db1a6812d7430c3ef2a5 is if you use UTF-8 encoding. Why don't you just do new ObjectId("59a4db1a6812d7430c3ef2a5"), or new ObjectId("59a4db1a6812d7430c3ef2a5".getBytes(StandardCharsets.UTF_8))? Although, I'd say that exposing ObjectId outside the layer that integrates with Mongo is a design flaw.

Spring Data mongo to insert null values to DB

I am using Spring data mongo to insert a record to Mongo,
here is my code
mongoTemplate.save(person,"personCollection");
Here is my person object
public class Person implements Serializable {
int age;
String address;
String name;
//Getters and setters including hashcode and equals
}
my address is null here , after inserting the record in the collection, the data is populated with only age and name
i know that mongodb treats null value and noKey as the same thing, but my requirement is to even populate the address:null to have the schema consistent how do i do this with Spring Data mongo
current o/p: {"age":21,"name":"john Doe"}
expected o/p: {"age":21,"name":"john Doe","address":null}
NoSQL DB works in a different way compared to RDBMS.
the document {"age":21,"name":"john Doe"} is same as {"age":21,"name":"john Doe";"address":null}
instead of storing the key's as null better to not store the key at all this improves the performance of your reads/updates against the DB.
However, if your usecase still demands to sore null due to whatever the reasons it might be convert your POJO to BSONObject and then persist the BSONObject in the MongoDB.
Here is the example ( but however it will be only a work around to get the things going)
BSONObject personBsonObj = BasicDBObjectBuilder.start()
.add("name","John Doe")
.add("age",21)
.add("address",null).get();
if you are using spring data mongo use
mongoTemplate.insert(personBsonObj,"personCollection");
document in the db:
db.personCollection.findOne().pretty();
{"age":21,"name":"John Doe";"address":null}*
I've solved this problem using the below code
final Document document = new Document();
document.put("test1", "test1");
document.put("test2", null);
document.put("test3", "test3");
mongoTemplate.getCollection("your-collection-name").insert(document);
Here instead of using BSONObject, I used Document object and it worked fine.
Document inserted in DB
{
"_id" : ObjectId("some-id"),
"test1" : "test1",
"test2" : null,
"test3" : "test3"
}

How to search date field for a String using JPA Criteria API

This is a question that spins off my other Question here . I thought it would be best put as a different question after someone(#Franck) pointed me to this link and this one too.
I'm stumped on how to search for a string in a database Date column (in my case MySQL DATETIME) using the JPA Criteria API.
Here's what I've done;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Client> cq = cb.createQuery(Client.class);
Root<Client> entity = cq.from(Client.class);
cq.select(entity);
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(cb.like(cb.lower(entity.get("dateJoined").as(String.class)), "%"+search.toLowerCase()+"%"));
cq.where(predicates.toArray(new Predicate[]{}));
TypedQuery<Client> query = em.createQuery(cq); //<--- Error gets thrown here
return query.getResultList();
But it fails with the following exception;
java.lang.IllegalArgumentException: Parameter value [%10-2015%] did not
match expected type [java.lang.Character]
where 10-2015 is the String being searched for;
I'm stuck on how to go by achieving this. I need some help.
Ok, after lots of experimenting with various strategies, here's what I did that finally worked.
I saw this post here and suddenly remembered the JPA Tuple Interface which is an Object that can return multiple result Type(s). So to perform my like comparison, and since Date cannot be simply cast to a String here are the steps;
I get the column as a Tuple
do a check on The Tuple Object to see if it's assignable from Date
if it is, then get the Date-Format expression and pass it to the like expression.
So essentially, here's what I initially had which was apparently failing;
predicates.add(cb.like(cb.lower(entity.get("dateJoined").as(String.class)), "%"+search.toLowerCase()+"%"));
Now, this is what I have that works beautifully;
Path<Tuple> tuple = entity.<Tuple>get("dateJoined");
if(tuple.getJavaType().isAssignableFrom(Date.class)){
Expression<String> dateStringExpr = cb.function("DATE_FORMAT", String.class, entity.get("dateJoined"), cb.literal("'%d/%m/%Y %r'"));
predicates.add(cb.like(cb.lower(dateStringExpr), "%"+search.toLowerCase()+"%"));
}
NOTE-WORTHY CONSIDERATIONS -
I am aware that from wherever the search would be initiated, all my Dates are presented in this form 07/10/2015 10:25:09 PM hence my ability to know how to format the Date for the comparison in my like expression as "'%d/%m/%Y %r'".
This is just one step that works for Dates. Most other Types e.g int, long, char ...etc... can all be directly Cast to String and as I explore more Types of data, I'll definitely do the same for any other Type that cannot be directly Cast to String.
Though this works perfectly for me, but before I mark this as the right answer, I'm going to subject it to some more extensive tests and in the process keep it open for comments by anyone that has any reservations about my strategy.
And finally, to that one person that this helped out in any way... Cheers!
This works in my case H2 (I use it for unit-tests), and I hope will work as well in Postgresql and Oracle, since TO_CHAR function seems to be cross-DB supported.
Path<Date> path = ua.get(MyEntity_.timestamp);
Expression<String> dateStringExpr = cb.function("TO_CHAR", String.class, path, cb.literal("DD.MM.YYYY HH24:MI:SS"));
predicates.add(cb.like(dateStringExpr, "%" + value + "%"));
PS. MyEntity_ stands for metamodel generated for real MyEntity. You may read about Metamodels in Oracle docuemntation for Criteria API.
I would suggest you convert you search string to Date object, and do the comparison
SimpleDateFormat dateFormat = new SimpleDateFormat(...desired date format here...);
Date dateSearchParam = dateFormat.format(search);
predicates.add(cb.eq(entity.get("dateJoined"), dateSearchParam);
Or if you want, you can change the type of your dateJoined attribute in your Entity to String, while your MySQL DB type remains DATETIME. You can utilize JPA #Convert to convert DATETIME to java.lang.String when Entity is retrieved from DB (and vice-versa when Entity is being persisted to DB).
See a sample here.
Attribute Converters are only available in JPA 2.1 version.

Hibernate Envers get revisions for criteria

Using Hibernate Envers (4.1.9.Final). Trying to get all the revisions (date, revision number) for which a entities have changed of a certain type and that match a certain criterion.
This is the code that I'm currently having:
AuditReader auditReader = AuditReaderFactory.get(entityManager);
AuditQuery query = auditReader.createQuery()
.forRevisionsOfEntity(InventoryItem.class, false, true)
.add(AuditEntity.property("section_uuid").eq(sectionUuid))
.addOrder(AuditEntity.revisionNumber().desc());
List<Object[]> revisions = query.getResultList();
This returns one element for each changed InventoryItem. So, if two InventoryItems were changed in a revision, I get two elements -- I do not want that.
This returns also the actual InventoryItems, I think that's a bit heavy -- I do not want that.
How can I get a distinct collection of revisions (date, revision number)?
On basis of Adam's answer, here is the code that I implemented. I'll mark his answer as the accepted answer.
AuditReader auditReader = AuditReaderFactory.get(entityManager);
AuditQuery query = auditReader.createQuery()
.forRevisionsOfEntity(InventoryItem.class, false, true)
.addProjection(AuditEntity.revisionNumber().distinct())
.addProjection(AuditEntity.revisionProperty("created"))
.add(AuditEntity.property("section_uuid").eq(sectionUuid))
.addOrder(AuditEntity.revisionNumber().desc());
As a result, query.getResultList() will return a collection of Object[], where each Object[] contains:
Object[0]: revision number as int
Object[1]: revision date as java.util.Date, corresponding the created revisionProperty
I think what you are looking for is the addProjection method on AuditQuery. You can add a projection on the revision number and date.

Categories