Strange behaviour- mongoDB GORM - mongodb

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?

Related

MongoDB - FindById is not working and giving null

I am using Spring Boot(V2.2.2.RELEASE) + Spring Data Mongo Example. In this example, I've records like below
{
"_id" : ObjectId("5cb825e566135255e0bf38a4"),
"firstName" : "John",
"lastName": "Doe"
}
My Repository
#Repository
public interface EmployeeRepository extends CrudRepository<Employee, ObjectId>{
Employee findById(String id);
}
Code
Employee findById = employeeRepository.findById("5cb825e566135255e0bf38a4");
System.out.println(findById);
Even below code not working
Query query = new Query(Criteria.where("id").is(new ObjectId("5cb825e566135255e0bf38a4")));
List<Employee> find = mongoTemplate.find(query, Employee.class);
Seems there might be two issues
ObjectId should be used
employeeRepository.findById(new ObjectId("5cb825e566135255e0bf38a4"))
ID field goes with underscore
new Query(Criteria.where("_id").is(new ObjectId("5cb825e566135255e0bf38a4")))
I'm not a Java guy, so might miss smth, but at least give it a try :)
Using the input document with _id: ObjectId("5cb825e566135255e0bf38a4") you can use either of the approaches. Assuming there is the document in the employee collection you can query by the _id's string value.
public class Employee {
#Id
private String id;
private String name;
// constructors (with and with arguments)
// get methods
// override toString()
}
// Spring-data app using MongoTemplate
MongoOperations mongoOps = new MongoTemplate(MongoClients.create(), "test");
Query query = new Query(where("id").is("5cb825e566135255e0bf38a4"));
Employee emp = mongoOps.findOne(query, Employee.class);
-or-
MongoRepository
interface extends CrudRepository and exposes the capabilities of the
underlying persistence technology in addition to the generic
persistence technology-agnostic interfaces such as CrudRepository.
#Repository
public interface EmployeeRepository extends MongoRepository<Employee, String> {
#Query("{ 'id' : ?0 }")
Optional<Employee> findById(String id);
}
// Spring-data app using the Repository
Optional<Employee> empOpt = employeeRepository.findById("5cb825e566135255e0bf38a4");
if (empOpt.isPresent()) {
System.out.println(empOpt.get());
}
else {
System.out.println("Employee not found!");
}
I too faced the issue. Interestingly things are working fine with version 2.1.7RELEASE
<documentRepository>.findById(<StringID>) treats as String Id in 2.2.2RELEASE
while 2.1.7RELEASE transforms it to ObjectId and hence the find query works.

springdata mongo repository method to return specific document property list

using spring data for mongodb, how do I specify the return type of the repository method to include a particular property from the document?
Ex:
#Document (collection = "foo")
class Foo {
String id
String name
... many more attributes
}
repository:
interface FooRepository extends MongoRepository<Foo, String> {
#Query { value = "{}", fields = "{'name' : 1}" }
List<String> findAllNames()
}
Above findAllNames works as expected and fetches only name property from the document. However spring data returned object is a string representation of Foo object which has id and name properties with values and remaining attributes as null.
Instead of Foo objects, I need to fetch List<String> which represents names.
As of now, I used a custom interface to achieve this. Moved the findAllNames() method from Spring data repository interface to my custom interface
interface FooRepositoryCustom {
List<String> findAllNames()
}
interface FooRepository extends MongoRepository<Foo, String>, FooRepositoryCustom {
}
#Component
class FooRepositoryImpl implements FooRepositoryCustom {
#Autowired
MongoOperations mongoOperations;
List<String> findAllNames() {
//using mongoOperations create the query and execute. Return the property values from document
}
}

javaee 6 rest api named query result

I have a simple JEE6 rest class that gets the data from db2. I am using Jackson in ApplicationConfig class to convert the entity objects to json. It converts with the field names as the key and the value as the right hand value. So for example:
Class Entity {
String name;
String address;
}
converts to
{name:"hello", address:"world"}
The service is as follows:
public List<T> findAll() {
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).getResultList();
}
Now I want to only return the name in json format. So I created a named query as follows in the entity class:
#NamedQuery(name = "justGetName", query = "SELECT a.name FROM Applications a")
And the service changed to
public List<T> findAll() {
return getEntityManager().createNamedQuery("justGetName").getResultList();
}
This returns the following array:
[{"first","second","third"}]
But I want to get back:
[{name:"first",name:"second",name:"third"}]
How do I write the named query so that the class field names are added to the json structure? Thank you.
You querying a list of strings from your database and this is what the service returns.
Their are multiple ways to achieve your goal.
Pure JPA
Using #JsonIgnore to tell Jackson not to serialize an attribute
class Application {
String name;
#JsonIgnore
String address;
}
Create a new Entity class that only contains the attributes you would like to share
class ApplicationName {
String name;
}
Alternatively you could introduce a separate class that only contains the attributes you would like to share and convert the results from the query into this class and return than the list of this converted values.

When does an Entity show up in the collection of the Model

I use entity framework and have a set of users:
public class DbModel : DbContext
{
public DbSet<User> Users { get; set; }
I add a User like so:
User UserOne = new User();
model.Users.Add( UserOne );
I request the users count:
int userCount = model.Users.Count();
userCount is "0" I would expect "1". Adding DetectChanges doen't help.
After "model.SaveChanges()" the Count = 1, but that is to late I need to combine the in memory stuff with the DB stuff for validation. Is there a way to do this?
SOLUTION
Using the answer of Erik Philips I wrote the following extension method for the DbSet
public static class DBSetExtentions
{
public static IEnumerable<T> AllMembers<T>(
this DbSet<T> target,
Func<T, bool> selection
) where T : class
{
return target.Local.Where(selection).Union(target.Where(selection));
}
}
it allows me to do selections an validations accross all entities like:
private void ValidateEmail(ValidationDto validationDto)
{
int usersWithSameEmail =
validationDto.Model.Users.AllMembers(
x => x.EmailAddress.Equals( EmailAddress ) ).Count();
if (usersWithSameEmail > 1)
{
validationDto.Result.Add(new ValidationResult("Email address is in use"));
}
}
You can query the client side of items (committed and uncomitted) to your data storage by using Local.
var count = model.Users.Local.Count();
One caveat is that this is only the local representation of Users. Meaning it could contain a partial amount of users from the database (changed and/or unchanged), and new users you've created and not saved.
Interesting Article - Using DbContext in EF 4.1 Part 7: Local Data

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

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();