JPA search by Key without Knowing Parent Key - google-app-engine

Ok so I have an application that uses GAE and consequently the datastore.
Say I have multiple companies A, B and C and I have within each company Employees X,Y and Z. The relationship between a company and employee will be OneToMany, with the company being the owner. This results in the Company Key being of the form
long id = 4504699138998272; // Random Example
Key CompanyKey = KeyFactory.createKey(Company.class.getSimpleName(), id);
and the employee key would be of the form
long id2 = 5630599045840896;
Key EmployeeKey = KeyFactory.createKey(CompanyKey,Employee.class.getSimpleName(),id2);
all fine and well and there is no problem, until in the front end, during jsp representation. Sometimes I would need to generate a report, or open an Employees profile, in which case the div containing his information would get an id as follows
<div class="employeeInfo" id="<%=employee.getKey().getId()%>" > .....</div>
and this div has an onclick / submit event, that will ajax the new modifications to the employee profile to servelet, at which point I have to specify the primary key of the employee, (which I thought I could easily get from the div id), but it didnt work server side.
The problem is I know the Employees String portion of the Key and the long portion, but not the Parent key. To save time I tried this and it didnt work
Key key = KeyFactory.creatKey(Employee.class.getSimpleName(); id);
Employee X = em.find(Employee.class,key);
X is always returned null.
I would really appreciate any idea of how to find or "query" Entities by keys without knowing their parents key (as I would hate having to re-adjust Entity classes)
Thanks alot !!

An Entity key and its parents cannot be separated. It's called ancestor path, a chain composed of entity kinds and ids.
So, in your example ancestor paths will look like this:
CompanyKey: ("Company", 4504699138998272)
EmployeeKey: ("Company", 4504699138998272, "Employee", 5630599045840896)
A key composed only of ("Employee", 5630599045840896) is a completely different one comparing to the EmployeeKey even though both keys end with the same values. Think of concatenating elements into a single "string" and comparing final values, they will never match.
One thing you can do is use encoded keys instead of their id values:
String encodedKey = KeyFactory.keyToString(EmployeeKey);
Key decodedKey = KeyFactory.stringToKey(encodedKey);
decodedKey.equals(EmployeeKey); // true
More about Ancestor Paths:
https://developers.google.com/appengine/docs/java/datastore/entities#Java_Ancestor_paths
KeyFactory Java doc:
https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/datastore/KeyFactory#keyToString(com.google.appengine.api.datastore.Key)

Related

ndb retrieving entity key by ID without parent

I want to get an entity key knowing entity ID and an ancestor.
ID is unique within entity group defined by the ancestor.
It seems to me that it's not possible using ndb interface. As I understand datastore it may be caused by the fact that this operation requires full index scan to perform.
The workaround I used is to create a computed property in the model, which will contain the id part of the key. I'm able now to do an ancestor query and get the key
class SomeModel(ndb.Model):
ID = ndb.ComputedProperty( lambda self: self.key.id() )
#classmethod
def id_to_key(cls, identifier, ancestor):
return cls.query(cls.ID == identifier,
ancestor = ancestor.key ).get( keys_only = True)
It seems to work, but are there any better solutions to this problem?
Update
It seems that for datastore the natural solution is to use full paths instead of identifiers. Initially I thought it'd be too burdensome. After reading dragonx answer I redesigned my application. To my suprise everything looks much simpler now. Additional benefits are that my entities will use less space and I won't need additional indexes.
I ran into this problem too. I think you do have the solution.
The better solution would be to stop using IDs to reference entities, and store either the actual key or a full path.
Internally, I use keys instead of IDs.
On my rest API, I used to do http://url/kind/id (where id looked like "123") to fetch an entity. I modified that to provide the complete ancestor path to the entity: http://url/kind/ancestor-ancestor-id (789-456-123), I'd then parse that string, generate a key, and then get by key.
Since you have full information about your ancestor and you know your id, you could directly create your key and get the entity, as follows:
my_key = ndb.Key(Ancestor, ancestor.key.id(), SomeModel, id)
entity = my_key.get()
This way you avoid making a query that costs more than a get operation both in terms of money and speed.
Hope this helps.
I want to make a little addition to dargonx's answer.
In my application on front-end I use string representation of keys:
str(instance.key())
When I need to make some changes with instence even if it is a descendant I use only string representation of its key. For example I have key_str -- argument from request to delete instance':
instance = Kind.get(key_str)
instance.delete()
My solution is using urlsafe to get item without worry about parent id:
pk = ndb.Key(Product, 1234)
usafe = LocationItem.get_by_id(5678, parent=pk).key.urlsafe()
# now can get by urlsafe
item = ndb.Key(urlsafe=usafe)
print item

objectify query filter by list in entity contains search parameter

in an app i have an entity that contains a list of other entities (let's say an event holding a list of assigned employees)
using objectify - i need to find all the events a particular employee is assigned to.
is there a basic way to filter a query if it contains the parameter - kind of the opposite of the query in
... quick pseudocode
findAll(Employee employee) {
...
return ofy.query(Event.class).filter("employees.contains", employee).list();
}
any help would be greatly appreciated
i tried just doing filter("employees", employee) after seeing this http://groups.google.com/group/objectify-appengine/browse_thread/thread/77ba676192c08e20 - but unfortunately this returns me an empty list
currently i'm doing something really inefficient - going through each event, iterating through the employees and adding them to a new list if it contains the given employee just to have something that works - i know this is not right though
let me add one thing,
the above query is not actually what it is, i was just using that because i did not think this would make a difference.
The Employee and Events are in the same entity group with Business as a parent
the actual query i am using is the following
ofy.query(Event.class).ancestor(businessKey).filter("employees", employee).list();
unfortunately this is still returning an empty list - does having the ancestor(key) in there mess up the filter?
solution, the employees field was not indexed correctly.
I added the datastore-indexes file to create a composite index, but was testing originally on a value that I added before the employees field was indexed, this was something stupid i was doing - simply having an index on the "business" field and the "employees" field fixed everything. the datastore-indexes file did not appear to be necessary, after deleting it and trying again everything worked fine.
Generally, you do this one of two ways:
Put a property of Set<Key<Employee>> on the Event
or
Put a property of Set<Key<Event>> on the Employee
You could also create a relationship entity, but if you're just doing filtering on values with relatively low counts, usually it's easier to just put the set property on one entity or the other.
Then filter as you describe:
ofy.query(Event.class).filter("employees", employee).list()
or
ofy.query(Employee.class).filter("events", event).list()
The list property should hold a Keys to the target entity. If you pass in an entity to the filter() method, Objectify will understand that you want to filter by the key instead.
Example :
/***************************************************/
#Entity
#Cache
public class News {
#Id Long id;
String news ;
#Index List<Long> friend_list = new ArrayList<Long>();
// My friends who can see my news , exemele : friend_list.add(id_f1); friend_list.add(id_f2); friend_list.add(id_f3);
//To make an operation on "friend_list", it is obligatory to index it
}
/*************************************************/
public News(Long id_f){
List<Long> friend_id = new ArrayList<Long>();
friend_id.add(id_f);
Query<Nesw> query = ofy().load().type(News.class).filter("friend_list in",friend_id).limit(limit);
//To filter a list, just after the name of the field you want to filter, add "IN".
//here ==> .filter("friend_list in",friend_id);
// if friend_list contains "id_friend" ==> the query return value
.........
}

In a Big Table, is it normal to key values in child (sub) collections?

I'm using Google App Engine and thus Big Table.
I have a person entity that looks like this:
{
// This property would be encoded into JSON and saved un-indexed as db.Text()
phone_numbers:
{
'hHklams8akjJkaJSL': // <-- Should I key this object?
{
number:'555-555-5555',
type:'mobile',
},
etc...
},
// This property is an array of strings.
// It is searchable so that a query could be run to find all
// people with a particular phone number:
// "SELECT * FROM person WHERE phone_number_search_property =
// '5555555555'"
phone_number_search_property:['5555555555','other phone numbers...'],
first_name:'...',
etc...
}
The phone_number property is stored as a blob of unindexed text in JSON format (db.Text). If I want to refer to a particular phone number in this situation, I decode the json, then get the phone number with the particular key that I am looking for.
The phone_number_search_property is used for searching. It enables a search by phone number: "SELECT * FROM person WHERE phone_number_search_property = '5555555555'"
What is a good way to refer to a phone number inside of an entity in this situation? Here, I have each value keyed using a UUID. Is this a "normal" and accepted way of doing things? If not, what is?
Thanks!
If data object is really just part of another object and is never accessed without the "parent" object (as is the case with phone number and person) then IMHO it's ok to serialize it and store it inside the "parent" object. So what you did is OK.
You search persons by phone number, so the solution to have additional property with (normalized) phone numbers is working. If you'd need to search on additional property, then it would not work (e.g. limiting search to only mobile numbers).
Why do you key serialized phone numbers by a hashed string (I assume you generate it via UUID.fromString(String))? Just use the (normalized) phone number - it is unique.

One To Many and Duplicate entry

I use JPA->Hibernate. PlayFramework. I want to have relationship.
Category - 1:n -> Tag
Every category can have many tags, but tags do not know about it.
So, i do like this:
#Entity
public class Category ... {
#OneToMany
public List<Tag> tags = new LinkedList<Tag>();
}
I have test:
#Test
public void playWithTags() {
Tag tag1 = new Tag("tag1").save(); // managed by playframework
Category cat1 = new Category("cat1");
cat1.tags.add(tag1);
cat1.save();
// check if tag1 and cat1 were saved
assertEquals(1, Tag.count());
assertEquals(1, Category.count());
Category cat2 = new Category("cat2");
cat2.tags.add(tag1);
cat2.save();
}
The result is:
16:18:01,555 ERROR ~ Duplicate entry '1' for key 'tags_id'
16:18:01,555 ERROR ~ Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelp
....
java:908)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.sql.BatchUpdateException: Duplicate entry '1' for key 'tags_id'
at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:2020)
It seems that cat2.save() try to do more then it should
If if use merge() instead of save() it works good:
cat2.merge();
BUT WHY?
I have fixed the problem. The problem was in that, that I used NOT THAT annotation. So i just changed #OneToMany to #ManyToMany and voilĂ  - No any restrictions anymore.
But if saying about the OneToMany then it seems there was a unique-restriction on database-level which prevented us to put not-unique values to tags_id. Therefore we could not put same tag to One category. I.e. it wanted One category for Many tags, but if tags were already 'used' - no way.. I tried to put unique=true/false in #JoinTable -> #JoinColumn - but it does not help. For me it's still strange, but at least current problem was fixed.
You're mixing up two concepts: Primary key and foreign key.
There can be only one PK but FK just means "there must be an element with this ID in some other table". FK doesn't constrain uniqueness.
[EDIT] Your problem is that you're mixing entities. How did you get the tag1 which is returned by save()?
This entity must be one which you get from Hibernate, not the result from new. Even if it looks insane, you must do this in save():
session.save(tag);
return session.load(tag.getId());
This way, you get an entity that is managed by Hibernate. Only when the entity is managed by Hibernate, Hibernate knows when it has to save the entity and when it has already been saved.
So when you do cat2.tags.add(tag1); in your example above, Hibernate thinks "oh, I don't know anything about this tag, it must be a new one".
And tries to save the tag again.

best method to keep and update unique data entries google app engine python

What is the best method to create a database for the given example model and assign each entry with a unique key/name which I already have and to overwrite it if the given key/name shows up again. From what I read you are supposed to use keyname? But I am not getting it to overwrite.
class SR(db.Model):
name = db.StringProperty()
title = db.StringProperty()
url = db.StringProperty()
s = SR(key_name="t5-2rain")
s.name = 't5-2rain'
s.title = 'kaja'
s.url = 'okedoke'
db.put(s)
If I enter this again with the same key name but different title value, will create another entry how do I overwrite an existing value with the same key-name.
Basically how do I populate a table with unique identifiers and overwrite values if the same unique identifier already exist?
I realize I can search for an existing name or key name etc, call that object make the changes to the instances and repopulate but, I would imagine there has to be a better method than that for overwriting especially if I am trying to put a list where some values may be overwrites and some not.
You've got the right idea already.
If 2 SR entities were constructed with the same key_name argument, then they will have the same Key path. Writing one will overwrite any old SR entity which had that key_name argument.
You should be able to observe this by querying the datastore for the entity with its unique key:
s = db.get(db.Key.from_path('SR', 't5-2rain'))

Resources