Spring data offers various repositories for CRUD, paging and sorting for mongodb.
When we need a document from mongodb using various attributes, we tend to write the findBy method or write a method with explicit query annotated with #Query.
But if my document has many properties and I need various means of fetching the document, does spring offer a "repository method" which takes in a Map of attributes and values?
Writing various findBy may be cumbersome. Using QueryDSL seems to be an option but requires code generation and build.gradle changes. I can use QueryDSL if map based solution is not available.
For ex:
class FooDocument {
String id ;//mongodb ObjectId
String name
String version
boolean deleteIndicator
... any more attributes
}
Need a method on the repository like
List<FooDocument> findByMap ( Map attrValMap );
Call the method (groovy)
repository.findByMap ( [ "name" : "fooname", "version" : "1.0" ] )
repository.findByMap ( [ "name" : "fooname"] )
repository.findByMap ( [ "version" : "1.0", "deleteIndicator" : false] )
Am willing to write this method using Criteria/Query combination or BasicDBObject based fetch using MongoOperations interface in case spring data does not offer this out of the box.
I'm not sure if Spring Data can do it easily. But if you are willing to use MongoDB's BasicDBObject to get what you need, then you can use it in the following way:
BasicDBObject doc = new BasicDBObject("name", "fooname").append("version", "1.0");
DBCursor cursor = find(doc);
List<FooDocument> fooDocList = new ArrayList<FooDocument>();
while (cursor.hasNext()) {
DBObject obj = cursor.next();
FooDocument foo = mongoTemplate.getConverter().read(FooDocument.class, obj);
fooDocList.add(foo);
}
Related
I need to migrate a Spring Boot project to Quarkus. The project has been using Spring Data Mongodb for all the queries. Now I find it difficult to migrate complex queries.
One example is
public List<MyData> findInProgressData(MyConditions conditions) {
Query mongoQuery = new Query();
mongoQuery.addCriteria(Criteria.where("status").is(IN_PROGRESS));
mongoQuery.addCriteria(Criteria.where("location").is(conditions.getLocationCode()));
if (StringUtils.isNotBlank(conditions.getType())) {
mongoQuery.addCriteria(Criteria.where("type").is(conditions.getType()));
}
if (StringUtils.isNotBlank(conditions.getUserId())) {
mongoQuery.addCriteria(Criteria.where("userId").is(conditions.getUserId()));
}
mongoQuery.with(Sort.by(Sort.Direction.ASC, "createdAt"));
return mongoTemplate.find(mongoQuery, MyData.class);
}
How can I implement the conditional query with Quarkus?
You can probably try Panache with Mongodb.
You can pass Document into .find() method, In this object you can specify any criteria.
final Document document = new Document();
document.put("status","IN_PROGRESS");
...
result = MyData.find(document); // or MyDataRepository
But you'll need to adapt some of the code to Panache, which can be done either via extending PanacheEntity or PanacheEntityBase
I have passed my query from postgres to mongodb, everything is correct. But in the ordering I do not see how to integrate it within #Query as in the example sql
// sql-postgres ( repository )
#Query("select c from ClienteElser c where c.securityDomainId IN (2,10) and c.deleted is null order by c.razonSocial")
//mongodb ( repository )
#Query("{security_domain_id: { $in: [2,10] },'deleted':null}")
public List<Cliente> findAllByOrderByRazonSocialAsc(Sort sort);
//service
Sort sort = new Sort(Sort.Direction.ASC, "razon_social");
List<Cliente> result = clienteRepository.findAllByOrderByRazonSocialAsc(sort);
At the moment I have fixed it like that, but I would prefer it to be inside #Query
And order giving priority to capital letters, I do not know how to avoid that. Example : "ACS" is before "Abalos"
Can someone help me to integrate the sort within #Query with mongodb and that does not differ between uppercase and lowercase
Thank you
This has been adressed in DATAMONGO-1979 for the Lovelace release (aka Spring Data MongoDB 2.1.0).
It will allow to set a default sort for repository query methods via the #Query annotation.
#Query(sort = "{ age : -1 }")
List<Person> findByFirstname(String firstname);
Using an explicit Sort parameter along with the annotated one allows to alter the defaults set via the annotation. The method argument Sort parameters will be added to / or override the annotated defaults depending on the fields used.
#Query(sort = "{ age : -1 }")
List<Person> findByFirstname(String firstname, Sort sort);
I'm trying to figure out how to persist a semi-structured document to MongoDB, using Spring Mongo. Here's an example of what I'm trying to accomplish:
{
"name": "Test",
"admin": false,
"unstructured_field_one": "Some arbitrary data"
}
I know how to do this with fully unstructured data as a field in a parent document, where I can just use something like
//...
private Object someRandomObject;
But how can I accomplish a semi-structured document (at parent level), where I have, as in the example, name and admin as required fields, and whatever else comes in along with the request gets added automagically?
You can do it without any pojo, just with a Json Parser(Jackson) and MongoTemplate. Since MongoTemplate can save any DbObject, You need to convert your json to a DBObject.
Something like this will do
ObjectMapper mapper = new ObjectMapper();
TypeReference<Map<String,Object>> typeRef
= new TypeReference<Map<String,Object>>() {};
Map<String,Object> map = mapper.readValue(json, typeRef);
DBObject dbObject = new BasicDBObject(map);
mongoTemplate.getCollection("blahblah").save(dbObject);
I am new to both Morphia and MongoDB. Is there a way to check using Morphia that a certain field in my database is not null and also exists. For example from the following record of a user from a collection of users in database:
{ "_id" : ObjectId("51398e6e30044a944cc23e2e"),
"age" : 21 ,
"createdDate" : ISODate("2013-03-08T07:08:30.168Z"),
"name" : "Some name" }
How would I use a Morphia query to check if field "createdDate" is not null and exists.
EDIT:
I am looking for a solution in Morphia. So far I have come up with this:
query.and(query.criteria("createdDate").exists(),
query.criteria("createdDate").notEqual(null));
From documentation, I learnt Morphia does not store empty or null fields. Hence the justification for notEqual(null).
EDIT 2: From the answers I can see the problem needs more explanation. I cannot modify the createdDate. To elaborate: the example above is less complex than my actual problem. My real problem has sensitive fields which I cannot modify. Also to complicate things a bit more, I do not have control over the model otherwise I could have used #PrePersist as proposed in one of the answers.
Is there a way to check for null and non existing field when I have no control over the model and I am not allowed to modify fields?
From the documentation, Morphia does not store Null/Empty values (by default) so the query
query.and(
query.criteria("createdDate").exists(),
query.criteria("createdDate").notEqual(null)
);
will not work since it seems you are not able to query on null, but can query for a specific value.
However, since you can only query for a specific value, you can devise a workaround where you can update the createdDate field with a date value that is never used in your model. For example, if you initialize a Date object with 0, it will be set to the beginning of the epoch, Jan 1st 1970 00:00:00 UTC. The hours you get is the localized time offset. It will be sufficient if your update only involves modifying the matching element(s) in mongo shell, hence it would look similarly to this:
db.users.update(
{"createdDate": null },
{ "$set": {"createdDate": new Date(0)} }
)
You can then use the Fluent Interface to query on that specific value:
Query<User> query = mongoDataStore
.find(User.class)
.field("createdDate").exists()
.field("createdDate").hasThisOne(new Date(0));
It would be much simpler when defining your model to include a prePersist method that updates the createdDate field. The method is tagged with the #PrePersist annotation so that the date is set on the order prior to it being saved. Equivalent annotations exist for #PostPersist, #PreLoad and #PostLoad.
#Entity(value="users", noClassNameStored = true)
public class User {
// Properties
private Date createdDate;
...
// Getters and setters
..
#PrePersist
public void prePersist() {
this.createdDate = (createdDate == null) ? new Date() : createdDate;
}
}
When you first create your Morphia instance, before calling morphia.mapPackage() do this:
morphia.getMapper().getOptions().setStoreNulls(true);
to have Morphia store null values.
Anyway, you should be able to query non-null values with:
query.field("createdDate").notEqual(null);
In mongo you can use this query:
db.MyCollection.find({"myField" : {$ne : null}})
This query will return objects that have the field 'myField' and has a value that is not null.
i have a collection 'placements', each document has fields: placement_id, program_id, category, ... i need to find all placements what has program_id = 3 and only return a list of placement_id,
i can do it from mongo command line like this:
db.placements.find({program_id:{$in: [3]}}, {placement_id:1, _id:0}).map( function(doc){return doc.placement_id})
it return placement_ids in a array:
[196, 197, 198...]
but how can i implement the above query in Java, i checked the mongodb java api's DBCursor class, it doesn't have any function as 'map' or 'forEach'.
The DBCursor.next returns DBOBject which has a get method which you can use to push the values to an array.
I am thinking of something like this. (Not tested)
List<Integer> placementIdList = new ArrayList<Integer>();
while (cursor.hasNext()) {
DBObject obj = cursor.next();
int id = obj.get("placement_id");
placementIdList.add(id);
}
UPDATE :
The implementation on the database side for the map implementation is very similar to above.
mongos> db.test.find().map
function ( func ){
var a = [];
while ( this.hasNext() )
a.push( func( this.next() ) );
return a;
}
So either you do it in db or in the Java layer.
Advantage of doing it in db would be transmitting a slimmer packet across the wire (lesser serialization/deserialization costs), whereas doing it in Java you can take advantage of the processing power of a JVM.