Vasyl Marceniuk Vasyl Marceniuk - 2 months ago 17
Java Question

GAE Objectify how to use result of query to another query

The basic idea of this question is the following. I execute some query

List<Class2> class2entities = ObjectifyService.ofy()
.load()
.type(Class2.class)
.filter(?,?)
.list();


In what way can I execute another query based on class2entities ?

Actually I'm developing GAE application based on Objectify. I use the following entities

Problem:

@Entity
public class Problem {
@Id public String problemname;

public Problem (String name) {
problemname = name;
}

public Problem () {
problemname = "nullProblem";
}
}


Tuple:

@Entity
public class Tuple {
@Parent Ref<Problem> theProblem;
@Index public String tuple_id;
@Id public Long id;

public Tuple()
{
String s="empty operator";
}

public Tuple(String sid, Problem problem)
{
tuple_id = sid;
try {
theProblem = Ref.create(problem);
}
catch (java.lang.NullPointerException e)
{
System.out.println(e.toString() + " tuple in datastore was not created because of Problem is empty" );
}
}
}


Attribute:

@Entity
public class Attribute {
@Parent com.googlecode.objectify.Ref<Problem> theProblem;
@Id public Long id;

public String attributeName;
@Index public String attributeFieldName;
@Index public Date date;

/**
* Simple constructor just sets the date
**/
public Attribute() {
date = new Date();
}

/**
* A connivence constructor
**/
public Attribute(Problem problem, String attributeName) {
this();
if( problem != null ) {
theProblem = Ref.create(problem); // Creating the Ancestor key
} else {
theProblem = Ref.create(new Problem("nullProblem"));
}
this.attributeName = attributeName;
}

/**
* Takes all important fields
**/
public Attribute(Problem problem, String attributeName, String var_attributeFieldName) {
this(problem, attributeName);
attributeFieldName = var_attributeFieldName;
}

}


CategorizedData:

@Entity
public class CategorizedData {
@Load public Ref<Attribute> theAttribute;
@Id Long id;
@Parent public Key<Tuple> theTuple;
public String attributeValue;

@Index public Date date;


/**
* getter and setter for theAttribute
**/

public Attribute getAttribute() { return theAttribute.get(); }
public void setAttribute(Attribute attribute) { theAttribute = Ref.create(attribute); }

/**
* Simple constructor just sets the date
**/
public CategorizedData() {
date = new Date();
}

/**
* A connivence constructor
**/
public CategorizedData(String tupleId, String attribute_field_name, String var_attributeValue) {
this();
Attribute attribute = ofy().load().type(Attribute.class).filter("attributeFieldName", attribute_field_name).first().now();
Tuple tuple = ofy().load().type(Tuple.class).filter("tuple_id",tupleId).first().now();

if( tupleId != null ) {
theTuple = Key.create(Tuple.class, tuple.id); // Creating the Ancestor key
} else {
theTuple = Key.create(Tuple.class, (new Tuple()).id);
}
if( attribute != null ) {
theAttribute = Ref.create(attribute); // Creating the Ancestor ref
} else {
theAttribute = Ref.create(new Attribute());
}
this.attributeValue = var_attributeValue;
}

}


Now I'd like to get all Tuple entities for given Problem and to get all entities of CategorizedData with theAttribute field with given AttributeFieldName.

I need to do something like

Key<Problem> theProblem = Key.create(Problem.class, problemName);
// Run an ancestor query
List<Tuple> tuples = ObjectifyService.ofy()
.load()
.type(Tuple.class)
.ancestor(theProblem)
.list();


and then I need to get entities of CategorizedData within this list tuples.
What should I do? Is it possible to use for Objectify queries not all datastore but the result of previous query? Please help me...

Answer

There are a couple of solutions for your problem. But first i'd like to say that your Entities are probably poorly designed for the datastore. When working with the datastore you should create your entities by analyzing what you will query for.

An example:

You have a Problem entity and a Tuple entity but your Problem entity only has a single attribute (problemname). The datastore way would be to denormalize this relationship and put the problemname attribute into your Tuple entity (or the Tuple attributes into Problem). This way you could start out by querying your Tuple entitiy and you would already have the data of formly two entities. Yes that means you will have redundant data in you datastore but that is fine from a datastore perspective.

Now to solve your actual problem

Objectify allows you to set an @Load annotation to Ref<Something>. What Objectify will do with this is

  1. Query the actual entity
  2. If @Load is defined for one of the attributes of your entity the values of that attribute are put in a list
  3. Objectify does a load().keys(<ListFrom2>)
  4. You can now use the Ref#get()-Method and the entity behind it will already be loaded

So you could use the @Load annotation but this will always load Refs even when you don't want them. I usually create a method for loading data like that myself. Essentially i implement all the steps from 1-4 except there is no @Load annotation and my method creates the List of Keys to load itself. The only issue here is matching the result Entities back to the Ref (actually that is not a huge issue because the result of a key query with a collection as parameter is a Map with the entitiy keys as key)

Secondly, if you want to/must keep your entities like that. Try to reduce the amount of calls to datastore and do some sorting in-memory:

ofy.load().ancestor(Key.create(Problem.class, "ProblemName"))

will give you the Problem and all related Tuples and Attributes. If you need the CategorizedData as well, make CategorizedData a child of Problem and refer to the Attribute with a simple Ref<Attribute> without @Parent. Then again: If this was my code i'd make CategroizedData an embedded List of Attribute. You can do all kinds of filtering and sorting yourself while working through the results of the ancestor query. This will be much faster than performing hundreds of queries.

Summarized, I believe your issue stems from not considering the actual use of the data when designing the entities. Denormalize where possible, consider redundant data as an option, not as no-go.

Comments