Using the $in operator through Morphia - doing it wrong? - mongodb

I have the following Play Framework entity (using Morphia for persistence) as part of a generic blogging app:
#Entity
public class Comment extends Model {
...
#Reference
#Indexed
public SiteUser commenter;
public static List<Comment> getLastCommentsByUsers(final List<SiteUser> users) {
final Query<Comment> query ds().createQuery(Comment.class);
query.field(commenter).hasAnyOf(users);
return query.asList();
}
}
SiteUser:
#Entity(noClassnameStored=true)
public class SiteUser extends AbstractUser {
public String realName;
}
AbstractUser:
public class AbstractUser extends Model {
#Indexed(value= IndexDirection.DESC, unique = true)
public String emailAddress;
#Required
public String password;
}
The method getLastCommentsByUsers() is supposed to return all comments by the users in the users parameter, but I always get an empty List back. The reason that Commment is a separate collection is to be able to retrieve last X Comments by certain users across their associated Posts, which isn't possible if the Comment is embedded in the Post collection.
Is there something wrong with my query (should I be using something other than hasAnyOf), or is it a problem with the relationship mapping - should I be using ObjectId instead?

I use the in() method with a list or set and its working perfectly. Here's a snippet:
List<String> keywordList;
List<Product> products = Product.find().field("keywords").in(keywordList).asList();
This should work for collection of embedded or references too.

You should use List<Key<SiteUser>> to query:
public static List<Comment> getLastCommentsByUsers(final List<SiteUser> users) {
final Query<Comment> query ds().createQuery(Comment.class);
query.field(commenter).hasAnyOf(toKeys(users)); // convert to keys
return query.asList();
}
public static List<Key<SiteUser>> toKeys(List<SiteUser> users) {
List<Key<SiteUser>> keys = new ArrayList<Key<SiteUser>>();
for(SiteUser user: users) {
keys.add(ds().getMapper().getKey(user));
}
return keys;
}
Or you can just get the keys by:
List<Key<SiteUser>> keys = ds().createQuery(SiteUser.class).query().filter(...).asKeyList();

Related

Spring Data - MongoRepository - #Query to find item within nested list of objects

I'm trying to query MongoDB to return a single Answer object contained within a QuestionDocument object.
I am using Spring, MongoRepository, and JDK 11.
My QuestionDocument POJO:
#Data
#Document(collection = "Questions")
#AllArgsConstructor(onConstructor = #__(#Autowired))
#NoArgsConstructor
public class QuestionDocument {
#Id
private String questionId;
(...)
private List<Answer> answers;
(...)
}
My Answer POJO:
#Data
public class Answer implements Serializable {
private String answerId;
(...)
My QuestionRepository:
#Repository
public interface QuestionRepository extends MongoRepository<QuestionDocument, String> {
#Query(value = "{ { 'questionId' : ?0 }, { 'answers.$answerId' : ?1 } }")
Answer findByQuestionIdAndAnswerId(String questionId, String answerId);
My QuestionServiceImpl:
public getAnswer(String questionId, String answerId){
Answer answer = findByQuestionIdAndAnswerId(questionId, answerId);
return answer;
}
protected Answer findByQuestionIdAndAnswerId(String questionId, String answerId){
Answer answer;
try {
answer = questionRepository.findByQuestionIdAndAnswerId(questionId, answerId);
} catch (Exception e) {
throw new IllegalArgumentException("There is no answer with this ID.");
}
return answer;
}
When I hit my endpoint in Postman, the correct response body appears, but all of its values are null. I have verified that the correct questionId and answerId are passed in my parameters.
I have also consulted several additional SO posts and Spring and MongoDB documentation, but so far, implementing what I've read regarding traversing nested objects by property hasn't helped.
How does my #Query value need to change to properly return a specific Answer object from this nested list of answers?
I have attempted to create findBy methods like:
findByQuestion_Answers_AnswerId(String answerId);
I have attempted to add #DBRef above my List<Answer> answers, and adding #Document(collection = "Answers") and #Id above private String answerId; in my Answer POJO. I then cleared my database, created a new question and answer, and queried for the specific answerId, and still returned null data.
What I expect, is that given the questionId and answerId, the query will return one Answer object and its associated information (answerBody, answerAuthor, etc.).
My postman response states SUCCESS, but the data is null.
You can change the Query to this.
#Query(value = "{{'questionId' : ?0, 'answers.answerId' : ?1}}")
or, just define this method.
findByQuestionIdAndAnswerId(String questionId, String answerId);
The return type will be of QuestionDocument, not Answer.
More details here.

How can I get data from #DBRef document using #Query -> Spring data mongo

I need help to get the data from another document I have the following class.
#Data
#Document(collection = "tmVersion")
public class TmVersion {
#Id
private String id;
private String cVrVersionId;
#DBRef
private TaApplicationVersion taApplicationVersion;
}
and
#Data
#Document(collection = "taApplicationVersion")
public class TaApplicationVersion {
#Id
private String id;
private String dVrAppName;
private String dVrAppCode;
}
This is my repository in which I map what I want to be shown but in taApplicationVersion I need to show all this object also how is it done?
#Query(value="{}", fields="{'cVrVersionId': 1, 'taApplicationVersion.dVrAppName': 2,
'dVrVersionNumber': 3}")
Page<TmVersion> getAllVersionWithOutFile(Pageable pageable)
Couple of things to mention here.
If you want this kind of join between tables, then you need to rethink your choice of Mongodb as database. No Sql Databases thrive on the fact that there is very less coupling between tables(collections). So if you are using #DBRef, it negates that. Mongodb themselves do not recommend using #DBRef.
This cannot be achieved with the method like you have in the repository. You need to use Projections. Here is the documentation for that.
Create a Porjection interface like this. Here you can control which fields you need to include in the Main class(TmVersion)
#ProjectedPayload
public interface TmVersionProjection {
#Value("#{#taApplicationVersionRepository.findById(target.taApplicationVersion.id)}")
public TaApplicationVersion getTaApplicationVersion();
public String getId();
public String getcVrVersionId();
}
Change the TmVersionRepository like this
public interface TmVersionRepository extends MongoRepository<TmVersion, String> {
#Query(value="{}")
Page<TmVersionProjection> getAllVersionWithOutFile(Pageable pageable);
}
Create a new Repository for TaApplicationVersion. You can add #Query on top of this method and control which fields from subclass needs to be returned.
public interface TaApplicationVersionRepository extends MongoRepository<TaApplicationVersion, String> {
TaApplicationVersion findById(String id);
}

How can i design a Model of a document contains more than one collection in Mongodb

I am novice in mongodb.
I have a mongodb and i have a table / document(Ex: sampleTable).
I know how to retrieve data from this single table.
I used below code to retrieve data and working fine.
#Document(collection="sampleTable")
public class sampleMapping {
private String id;
private String strClassName;
public sampleMapping () {}
public sampleMapping (String strClassName) {
super();
this.strClassName = strClassName;
}
public String getStrClassName() {
return strClassName;
}
public void setStrClassName(String strClassName) {
this.strClassName = strClassName;
}
}
But now, i wanted to retrieve data from more than one table. How can i do that?
Say, i need to retrieve table1 and table2.
How can i update the above code to get data from multiple collections?

How to use criteria query on refrence collection in mongo db

How do I find all the person which are having city ="XYZ" in Address collection
public class Person {
#Id
private String id;
private String description
#DBRef
private Address address;
// Getters and Setters
}
public class Address
{
#Id
private String id;
private String area
private String city
// Getters and Setters
}
Mongo understands #DBRef as a reference to another document, in this case, an Address document and ultimately when the object is loaded from MongoDB, those references will be eagerly resolved and this will get populated to the user as a HATEOAS friendly link. You will get back a mapped object that looks the same as if it had been stored embedded within your master document.
You can define your repository, which will map the endpoints to your database, for the given object, like PersonRepository defined below as an example:
import com.mycompany.domain.Person;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface PersonRepository extends MongoRepository<Person, String> {
List<Person> findByCity(#Param("city") String city);
}
Another way you could go around this using the query criteria methods is executing two queries.
First query would be to fetch the address documents which have the city = "XYZ". Resolve the ids from the list returned.
Generate another query on the Person entity using the ids from the previous operation.
The following demonstrates this approach
Query addressQuery = new Query(where("city").is("XYZ"));
addressQuery.fields().include("_id");
List<Address> addressList = mongoTemplate.find(addressQuery, Address.class, "address"); // get the addresses list that satisfy the given city criteria
// Resolve the ids for the addresses
final List<ObjectId> addressIds = new ArrayList<ObjectId>(addressList.length);
for(final Address address : addressList) {
addressIds.add(new ObjectId(address.getId()));
}
// Get the Person list using the ids from the previous operation
Query personQuery = new Query(where("address.$id").in(addressIds));
List<Person> list = mongoTemplate.find(personQuery, Person.class, "person");
If you knew the address id before hand you can then use a custom query:
public interface PersonRepository extends MongoRepository<Person, String> {
#Query("{ 'address': {'$ref': 'address', '$id': { '$oid': ?0 } } }")
List<Person> findByAddres(String addressIdAsString);
}

Strange behaviour- mongoDB GORM

I have a Grails application using Grails 2.3.8 and Mongo GORM plugin 3.0.1 . I have a service which constructs an object during its first invocations and saves it in mongoDB and returns it. In subsequent invocations, it would just retrieve the constructed object from the mongoDB and return it.
def loadWeekData(String startDate,String storeId){
def weekJson = WeekJson.findByStoreIdAndStartDate(storeId,startDate)
if(weekJson==null){
//construct weekJson here
weekJson.save(flush:true)
weekJson=WeekJson.findByStoreIdAndStartDate(storeId,startDate)
}
weekJson
}
WeekJson domain class has other nested objects with hasMany relation. WeekJson hasMany Employee which hasMany Day which hasMany Planned which hasMany Activity
WeekJson domain class
public class WeekJson{
static hasMany = [employees:Employee]
static mapWith = "mongo"
static mapping = {
employees fetch: 'join'
}
String toString()
{
"$employees"
}
}
Employees domain class
public class Employee {
static mapWith = "mongo"
static hasMany = [days:Day]
static mapping = {
days fetch: 'join'
}
String toString()
{
"$days"
}
}
Day domain class
public class Day {
Planned planned;
static mapWith = "mongo"
static constraints = {
planned nullable:true
}
String toString()
{
" plan: $planned "
}
static mapping = { planned lazy:false}
}
Planned domain class
public class Planned {
List<Activity> activities
static hasMany = [activities:Activity]
static mapWith = "mongo"
static mapping = {
activities lazy:false
}
String toString()
{ activities }
}
Activity Domain class
public class Activity {
String inTime;
String outTime;
double duration;
String type;
String desc;
static mapWith = "mongo"
static constraints = {
duration nullable:true
type nullable:true
desc nullable:true
}
String toString()
{
"$inTime to $outTime"
}
}
I have changed fetching behavior to eager in all the classes with hasMany relations.
The first time, all the nested objects are constrcuted properly, saved in mongoDB, and the returned object is correct.
However, for the next call, Activity objects are null. I've verified that the nested objects are still present in mongoDB during this call. Records in the Planned collection have ids to Activity collection records .
When I do,
println weekJson.employees.days.planned.activities
the list of `Activity is printed. However,
println weekJson
gives Activity list null and so does rendering as Json.
Why is GORM not retrieving the nested objects the second time around ?
Is it possible that this a problem of GORM being unable to handle relationships with this level of nesting ?
Maybe you should switch to sub-documents in your domain model.
Btw, if you want to help us help you, post more data on your case: which version of mongo, grails etc. you are using? what your domain classes look like? what do you see in the mongo collections upon saving?