Objectify Query by list's element - google-app-engine

I am using Objectify to create an entity:
#Entity
public class Collection {
#Id
private String name;
#Index
private List<Long> viewersIds;
//other fields
}
Now I am trying to retrieve the list of Collections which have a particular viewerId, lets say 1. I have tried:
List<Collection> usersCollections = ofy().load().type(Collection.class).filter("viewersIds",1).list();
and
ofy().load().type(Collection.class).filter("viewersIds =",1).list();
and
ofy().load().type(Collection.class).filter("viewersIds ==",1).list();
Getting all Collections works using:
ofy().load().type(Collection.class).list();
What am I doing wrong?
Thank you!
EDIT:
Changing the Colllection object to contain a list of strings viewerIds instead of Long
#Index
private List<String> viewersIds;
And then query it with:
ofy().load().type(Collection.class).filter("viewerIds", value).list();
works. So this could be a solution if the list can be of Strings.

One thing to check is that viewerIds is indeed indexed in the entities that are supposed to be returned: https://console.cloud.google.com/datastore/entities/query

You're looking for 'in'
.filter("<collection> in", value)

Related

Spring data JPA inserting null into column error even though I POST a value?

I want to save both child and parent entities whenever a POST call is made. I have an Item entity with a one to one mapping to a parent table Attribute:
#Entity
#Table(name="Item")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Item
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="attr_id")
private Attribute attribute;
#OneToMany(mappedBy = "item", cascade = CascadeType.ALL, orphanRemoval=true)
private List<ValueSet> valueSets = new ArrayList<>();
// Other fields, getters, setters, overriding equals and hashcode using Objects.equals() and Objects.hashCode() for all the fields, helper for adding and removing ValueSet
}
The Attribute entity looks like this:
#Entity
#Table(name="Attribute")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Attribute
{
#Id
#Column(name="id")
private Long id;
// Other fields, getters, setters, NOT overriding equals hashCode
}
Whenever an Item gets saved I need the Attribute to get saved as well. I've my postman sending JSON POST data as follows:
{
"attribute":{
"id":"6"
},
"valueSets":[
{
"value":"basic"
}
]
}
My handler looks like this:
#PostMapping("/item")
public void postItems(#RequestBody Item item)
{
itemRepository.save(item);
}
ItemRepository is just a one liner with #Repository annotation:
public interface ItemRepository extends CrudRepository<Item, Long>
When I try to save the Item I run into - Cannot insert the value NULL into column 'attr_id', table 'Item'; column does not allow nulls. INSERT fails.
I can't figure out why is it unable to take the id value of 6 that I am supplying as part of my POST invocation. The id value 6 already exists on the Attribute table. I have also tried making the relationship bi-directional using mappedBy and CASCADE.ALL but still get the same error.
Any thoughts/suggestions on what I'm messing/missing? Also, is there a better approach to handle nested entities? Should I try to save each of them individually? In that case can the #RequestBody still be the parent entity?
I have built an example project, and try to replicate your situation, not successful. I am able to insert the "item" without any issue.
I placed the project under this repository https://github.com/hepoiko/user-5483731-1
Hope this help you to troubleshooting further or let me know If I miss anything in there.

Saving a recursive object in Datastore via Objectify

I have a use case where in I have to save a recursive object
Two classes :
public class Item{
#Id
private Long id;
#Index
private String name;
#Index
private String sku;
#Index
private Long shopId;
#Index
private String imageUrl="";
#Index
private List<Long>optionIds;
private List<Option> options;
}
public class Option{
#Id
private Long id;
#Index
private String name;
#Index
private String sku;
#Index
private Long shopId;
#Index
private String imageUrl="";
#Index
private List<Long>itemIds;
private List<Item> items;
}
I do save the two objects separately in two different tables as well.
For that I need to add #Ignore on the List field in the Item model
and #Ignore on the List field in the Option model.
I now need a complete recursive structure and want to save that in another table.
To do that I was trying a hack by putting #IgnoreSave(IfNull.class) on the List field in the Item model and on the List field in the Option model.
But when I launched the application after doing the above, I got a stackoverflow error. Error being something of the below sort :
java.lang.StackOverflowError
at java.security.AccessController.doPrivileged(Native Method)
at sun.reflect.annotation.AnnotationInvocationHandler.getMemberMethods(AnnotationInvocationHandler.java:284)
at sun.reflect.annotation.AnnotationInvocationHandler.equalsImpl(AnnotationInvocationHandler.java:196)
at sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:63)
at com.sun.proxy.$Proxy9.equals(Unknown Source)
at java.util.Arrays.equals(Arrays.java:1869)
at com.googlecode.objectify.impl.translate.TypeKey.equals(TypeKey.java:60)
at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:996)
at com.googlecode.objectify.impl.translate.Translators.get(Translators.java:115)
at com.googlecode.objectify.impl.translate.CreateContext.getTranslator(CreateContext.java:27)
at com.googlecode.objectify.impl.KeyMetadata.findKeyFields(KeyMetadata.java:78)
at com.googlecode.objectify.impl.KeyMetadata.<init>(KeyMetadata.java:50)
at com.googlecode.objectify.impl.translate.ClassTranslatorFactory.createEntityClassTranslator(ClassTranslatorFactory.java:64)
I'm stuck now and need help badly. Is there an alternative solution to store the recursive structure via objectify?
It looks like you are trying to save two different entities that reference each other. For that, you should use Key<?> or Ref<?> fields. Recursion is no problem with pointers like this.
By specifying List<Item> and List<Option>, you are telling Objectify that you want to embed these things recursively in a single entity. Objectify does not support recursive embedded classes (at least, not yet).

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

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?

Load list of items in objectify

I have Question, Like and Hashtag entities. Also there is one to many relationship between Like and Question entities. I am using google cloud endpoints and my problem begins here. In my list method, I return 20 question as json. But for each question object in query I have to check if user is already liked the question and also fetch related hashtags that belongs to the question. How can I do the same operation by key only batch query. Otherwise, I do
ofy().load().type(Like.class)
.filter("questionRef =", questionKey)
.filter("accountRef =", accountKey).first().now();
for each object.
Like entity
#Entity
#Cache
public class Like {
#Id
#Getter
protected Long id;
#Index
#Load
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
private Ref<Account> accountRef;
#Index
#Load
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
private Ref<Question> questionRef;
#Index
#Getter
protected Date createdAt;
Like() {
}
Like(Key<Account> accountKey) {
this.accountRef = Ref.create(accountKey);
this.createdAt = new Date();
}
}
Hashtag entity
#Entity
#Cache
public class Hashtag implements Model<Hashtag> {
#Id
#Getter
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
private Long id;
#Index
#Load
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
private Ref<Question> questionRef;
#Index
#Getter
#Setter
private String text;
private Hashtag() {
}
private Hashtag(Builder builder) {
this.questionRef = builder.questionRef;
this.text = builder.text;
}
}
There are several parts to this question.
First, hashtags: Just store hashtags in the Question as an indexed list property. Easy.
Second, likes: There are a couple ways to do this efficiently.
One is to create a Like entity with a natural key of "account:question" (use the stringified websafe key). This way you can do a batch get by key for all the {user,question} tuples. Some will be absent, some will be present. Reasonably efficient if you're only concerned about 20 questions, especially if you #Cache the Like.
Another is to create a separate Relation Index Entity that tracks all the likes of a user and just load those up each time. You can put 5k items in any list property, which means you'll need to juggle multiple entities when a user likes more than 5k things. But it's easy to load them all up with a single ancestor query. The RIE will need to be #Parented by the User.
On a separate note - don't call fields thingRef. It's just a thing. The data in the database is just a key. You can interchange Ref<?>, Key<?>, and the native low-level Key. Type information doesn't belong in database names.
I am not sure if you can change the structure of your entities. If the answer is no, then there is no option other than the approach you have taken.
If yes, I would suggest structuring your Question to include the Like and Hashtag information as well.
#Entity
public class Question {
#Id
private long id;
private Set<Key<Account>> likedBy;
private List<String> hashtags;
}
For a question, you can retrieve all the information in one single query. Then collect all the Account keys and make another datastore query to retrieve all the people who have liked the question using keys as below:
Map<Key<Account>, Account> likedByAccounts = ofy().load().keys(accountKeys);

filter properties with Objectify 4

I'm trying to filter with Objectify in GAE:
List<users> ul = ofy.load().type(Usuario.class).filter("name", "gildus").list();
In the User's class use anotation #Index:
#Entity
public class Users {
#Id
private Long id;
#Index
private String name;
...
The filter result is empty, although there is value "gildus". When I use the ID field if it shows results (....filter("id", "1").list() ).
What more could I do to make it work ?
When I use the ID field if it shows results (....filter("id", "1").list() ).
Don't use filter for id. Use the following instead:
Usuario user = ofy.load().type(Usuario.class).id(1).get();

Resources