Google app engine, objectify how to order by a sub entity field? - google-app-engine

I have a Course entity that contains the following field
#Index
private #Load
Ref<Student> student;
The student entity then has the field
#Index
private String matric;
I want to load all the Course entities sorted using the students matric number.
I have tried using the "." operator to get the sub field like this
ofy().load().type(Course.class).filter("course", course).order("student.matric").list();
but this return no result.
Is it possible to do this? how?

I don't think that is possible with objectify. I would let Course implement Comparable:
#Entity
public class Course implements Comparable<Course> {
.
.
.
#Override
public int compareTo(Course otherCourse) {
return this.getStudent().getMatric().compareTo(otherCourse.getStudent().getMatric());
}
}
Remove the "order" part of the Objectify load and use Collections.sort() instead:
List<Course> courses = ofy().load().type(Course.class).filter("course", course).list();
Collections.sort(courses);

There are no joins in the datastore. If you want to query your Courses by Student properties, you probably will need to denormalize the data into the Course and index it. This means changing the Student data will also require changing Courses.
As an aside: This data model is weird. Are you sure what you're calling Course isn't really an Enrollment?

Related

Is there a way to avoid explicitly writing document fields as Strings in Spring Data MongoDB queries?

I have recently started to use Spring Data MongoDB and I wonder if there is any way to avoid writing entities' attributes explicitly as they are stored in the database. For example, given the following class representing a MongoDB collection:
public class Employee {
#Id
public String id;
private double salary;
...
}
If I want to make a query using MongoTemplate like:
public List findEmployeeBySalaryRange(double salary) {
Query query = new Query();
query.addCriteria(Criteria.where("salary").lt(salary));
...
}
I would like to avoid writing "salary", since that will make the code harder to maintain in the future in case the field name changes. I am thinking of something like getting the field name from the class attribute, but I am not quite sure how. Is there a way to do it? I have looked into the documentation but did not find anything related unless I missed it.
Thanks in advance.
You may create a Utility Class to store all database field names, use #Field annotation on field with constant from that class and use that constant in query to avoid error prone hardcoded Strings.
In Employee Model
#Field(DbFields.SALARY)
private double salary;
In Query,
query.addCriteria(Criteria.where(DbFields.SALARY).lt(salary));
In DbFields Utility class
public static final String SALARY = "salary";

How to unindex lists in Objectify?

Why is it not possible to unindex a list of an objectify entity?
To demonstrate the problem I made a simple example project.
I used the entity
#Entity
public class Car {
#Id String id;
#Unindex List<Passenger> passengers;
}
an the object
public class Passenger {
String name;
}
and saved it using this simple method.
public class CarFactory {
public void writeCarEntity() {
Car car = new Car();
car.setId("myCar");
List<Passenger> passengers = new LinkedList<Passenger>();
Passenger carl = new Passenger();
carl.setName("Carl");
Passenger pete = new Passenger();
pete.setName("Pete");
Passenger jeff = new Passenger();
jeff.setName("Jeff");
passengers.add(carl);
passengers.add(pete);
passengers.add(jeff);
car.setPassengers(passengers);
ObjectifyService.register(car.getClass());
ObjectifyService.ofy().save().entity(car).now();
}
}
Looking up the entity in the datastore you get this information:
Although the passengers field has the annotation #Unindex it will be indexed, as one can see in the google "Datastore". Why does the annotation #Unindex has no effect in this example???
This is unrelated to Objectify and appears to be some new quirk of the datastore. It might just be a display glitch in the UI. Is it causing problems?
With the code you posted, Objectify will call Entity.saveUnindexedProperty() on the passengers field (even without the #Unindex annotation). But even if Objectify tried to index it, historically you can't index embedded objects, so it's unclear what it means to index a list of them. Maybe Google is rolling out some new behavior and they haven't got the GUI working correctly yet? Or maybe there is a bug in their save behavior?
If you want to be a good citizen, create a simple test case with the low level API (an Entity that contains a property of type List<EmbeddedEntity>), verify that this same behavior occurs, and file a bug in the GAE issue tracker.

Add field to App Engine-hosted database

I'm currently developing a mobile application who uses a Google App Engine-hosted web service.
But i'm facing an issue. I just want to add a field in one my database's table.
App Engine doesn't use classic SQL syntax, but GQL. So i cannot use the ALTER TABLE statement. How can i do this with GQL ? I looked for a solution on the web, but there's not a lot of help.
public MyEntity() {
}
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Key idStation;
private String name;
private double longitude;
private double latitude;
private java.util.Date dateRefresh = new Date(); //the field i want to add in DB
So, now when i create a "MyEntity" object, it should add the "dateRefresh" field into the database... I create my object like this:
MyEntity station = new MyEntity();
station.setName("test");
station.setLatitude(0);
station.setLongitude(0);
station.setDateRefresh(new Date("01/01/1980"));
DaoFactory.getStationDao().addStation(station);
addStation method:
#Override
public MyEntity addStation(MyEntity station) {
EntityManager em = PersistenceManager.getEntityManagerFactory().createEntityManager();
try {
em.getTransaction().begin();
em.persist(station);
em.getTransaction().commit();
} finally {
if(em.getTransaction().isActive()) em.getTransaction().rollback();
em.close();
}
return station;
}
The field "dateRefresh" is never created into my DB...
Someone to help me please ?
Thanks in advance
Just add another field to your data structure, maybe providing a default clause, and that's all. For example, if you have a UserAccount:
class UserAccount(db.Model):
user = db.UserProperty()
user_id = db.StringProperty()
you may easily add:
class UserAccount(db.Model):
user = db.UserProperty()
user_id = db.StringProperty()
extra_info = db.IntegerProperty(default=0)
timezone = db.StringProperty(default="UTC")
and let it go.
While the datastore kinda mimics tables, data is stored on a per entity basis. There is no schema or table.
All you need to do is update your model class, and new entities will be saved with the structure (fields) of the new entity.
Old entities and indexes, however, are not automatically updated. They still have the same fields as they had when they were originally written to the datastore.
There's two ways to do this. One is to make sure your code can handle situations where your new properties are missing, ie make sure no exceptions are thrown, or handle the exceptions properly when you're missing the properties.
The second way is to write a little function (usu a mapreduce function) to update every entity with appropriate or null values for your new properties.
Note that indexes are not updated unless the entity is written. So if you add a new indexed property, old entities won't show up when you query for the new property. In this case, you must use the second method and update all the entities in the datastore so that they are indexed.

Queries with Objectify: UmbrellaException

I am using Objectify to manage GAE Datastore for my GWT app. The problem is that I am not using queries properly and I get UmbrellaExceptions as per below:
Caused by: java.lang.RuntimeException: Server Error: java.lang.String cannot be cast to java.lang.Number
at com.google.web.bindery.requestfactory.shared.Receiver.onFailure(Receiver.java:44)
Say that I have a class Box with a unique field String id. I want to get the Box object whose id == "cHVQP6zZiUjM"
This is how I do it now:
public Box getBox(String boxId)
{
Objectify ofy = ObjectifyService.begin();
Query<Box> q=ofy.query(Box.class).filter("id",boxId);
Box targetBox = q.get();
return targetBox;
}
#Entity
public class Box extends DatastoreObject{
private String id;
private String title;
}
I tried doing this with ofy.load() but that method is not defined in my class Objectify (I don't know why).
Your key is encoded. Try using:
Box targetBox = ofy.get(Box.class, KeyFactory.stringToKey(boxId));
To decode your key.
The short answer: You are missing the #Id annotation in your entity.
The long answer: Id fields are special in the datastore. The id is not a real property, but rather a part of the Key that identifies the entity. You can't really filter on id fields, but you can filter on a special field called __key__. Objectify is somewhat clever about letting you filter by the id field and converting this to a __key__ filter under the covers, but it can't do it if you don't annotate the entity properly!
Actually I'm a little confused because Objectify shouldn't let you register the entity without an #Id field.
By the way, there are two sections of the documentation: Objectify4 (release coming soon) and Objectify3. Since you're using Ofy3, there is no load() method.
Another thing: Get-by-key operations are strongly preferred to queries when the operations are equivalent (as they are in your example).

Playframework Siena Filtering and Ordering

This is my first question on any of these websites so pardon my unprofessionalism.
I use playframework with SIENA module (with GAE) and I came accross the following problem:
Given 3 entities:
public class Meeting extends Model{
#Id
public Long id;
public String place;
#Owned
Many<MeetingUser> users;
.
.
.
}
public class User extends Model{
#Id
public Long id;
public String firstName;
public String lastName;
#Owned
Many<MeetingUser> meetings;
.
.
.
}
public class MeetingUser extends Model{
#Id
public Long id;
public Meeting meeting;
public User user;
.
.
.
public User getUser(){
return Model.all(User.class).filter("id", user).get();
}
public Meeting getMeeting(){
return Model.all(Meeting.class).filter("id", meeting).get();
}
}
For instance I am listing a meeting and all their users:
public static void meetingInfo(Long meetingId){
Meeting meeting = Models.all(Meeting.class).filter("id",meetingId);
List<MeetingUser> meetingusers = meeting.asList();
List<User> users = new ArrayList<User>();
for(MeetingUser mu: meetingusers){
users.add(mu.getUser());
}
render(users);
}
This is done(is there any better way here?) however when it comes to filtering (especially dynamic filtering for many many fields) I can not use the Query's filter method on the MeetingUser as I need to filter on a MeetingUser's field's field (firstName). The same problem arise for ordering. I need the solution for both problems.
I hope my problem is clear and I appreciate any kind of help here.
Remember that you are in GAE which is a NoSQL DB.
So you can't do Join request as in RDBMS.
Yet, this is not really the pb you have so this was just to be sure you are aware of it ;)
So if you want to find the person having given firstname in a given meeting, can you try the following:
List<MeetingUser> meetingusers = meeting.users.asQuery().filter("firstname", "XXX");
(you can also order)
Nevertheless, knowing that you can't join, remember that you can't write a query searching for a meeting in which there are users whose firstname is XXX as it would require some joins and it doesn't exist in GAE. In this case, you need to change your model following NoSQL philosophy but this is another subject
regards
Let's try to give a way to do what you want...
Your relation is a Many-to-Many which is always the worst case :)
You want to filter Meeting by User's firstname.
It requires a join request which is not possible in GAE. In this case, you must change your model by denormalizing it (sometimes use redundancy also) and manage the join by yourself. Actually, you must do the job of the RDBMS by yourself. It seems overkill but in fact, it's quite easy. The only drawback is that you must perform several requests to the DB. NoSQL means No Schema (& No Join) so there are a few drawbacks but it allows to scale and to manage huge data load... it depends on your needs :)
The choice you did to create the MeetingUser which is a "joined" table and a kind of denormalization is good in GAE because it allows to manage the join yourself.
Solution:
// fetch users by firstname
List<User> users = users.all().filter("firstName", "John").fetch();
// fetch meetingusers associated to these users (verify the "IN" operator works because I didn't use that for a long time and don't remember if it works with this syntax)
List<MeetingUser> meetingusers = MeetingUser.all().filter("user IN", users);
// now you must fetch the whole meeting because in MeetingUser, only the Meeting ID is stored (other fields are Null or O)
List<Meeting> meetings = new ArrayList<Meeting>()
for(MeetingUsers mu:meetingusers) {
meetings.add(meetingusers.meeting);
}
// use the batch feature to fetch all objects
Meeting.batch(Meeting.class).get(meetings);
// you have your meetings
Hope this helps!

Resources