Objectify One-to-Many and Many-To-One in Google App Engine - google-app-engine

I'm creating simple backend on Google App Engine using Objectify, that can store Users and thier Recipes.
Model look like this:
User has many Recipes, every Recipe has one author.
I want to be able to:
Get list of Recipes with authors in it
Get list of Users without fetching all recipes
Get one User with all his recipes
According to guide I have done this:
#Entity
public class User {
#Id
private Long id;
#Index
private String name;
List<Recipe> recipes = new ArrayList<>();
/* Other fields */
}
and Recipe :
#Entity
public class Recipe {
#Id
Long id;
String name;
#Index
#Load
Ref<User> author;
/* Other fields */
}
I save Recipe with author (User object) in it.
and 2. requirements work fine,
but when I try to get User with all recipes like this:
public User get(#Named("id") long id) throws NotFoundException {
User user = ofy().load().type(User.class).id(id).now();
if (user == null) {
throw new NotFoundException("Could not find User with ID: " + id);
} else {
List<Recipe> recipes = ofy().load().type(Recipe.class).filter("author", user).list();
account.recipes = recipes;
}
return account;
}
I get empty recipe list.
What am I doing wrong ?

Your author property in your Recipe is a Ref, which is essentially a Key, so you need to filter on that key, like this:
List<Recipe> recipes = ofy().load().type(Recipe.class).filter("author =", Key.create(user)).list();
Incidentally, instead of using .now(), checking for null, then throwing an exception, you could use .safe() which does the same thing for you.

Related

Objectify index is not created

I try to use objectify on google app engine standart environment and get exception. My classes look like this:
#Entity
public class Company {
#Id Long id;
#Index String companyName;
public Company() {
}
public Company(Long id, String companyName) {
this.id=id;
this.companyName = companyName;
}
}
#Entity
public class CompanyProject {
#Id Long id;
#Index String projectName;
#Parent Key<Company> owner;
public String cost;
public CompanyProject() {
}
public CompanyProject(long userId, String projectName) {
this();
this.projectName = projectName;
owner = Key.create(Company.class, userId); // Creating the Ancestor key
}
}
When I query data like this:
Key<Company> theUser = Key.create(Company.class, 1);
Iterable<CompanyProject> projects = ObjectifyService.ofy().load().type(CompanyProject.class).ancestor(theUser).order("projectName").list();
I get exception
com.google.cloud.datastore.DatastoreException: no matching index found. recommended index is:
- kind: CompanyProject
ancestor: yes
properties:
- name: projectName
Without order("projectName") query works just fine. Removed all entities of this kind from datastore, than added new, still get this exception. I use Gradle, not Maven if this matters. Maybe should be extra build step to create indexes or smth.
You are filtering on multiple properties (well, ancestor counts as 'another property') so you need a multi-property index defined in datastore-indexes.xml.
See the official documentation: https://cloud.google.com/appengine/docs/standard/java/config/indexconfig

How to store data owned by User?

I'm learning Google App Engine + Google Cloud Endpoints + Objectify and I'm trying to understand how to create REST API which will let each particular user to save his data in the cloud.
My current struggle is how to store Entity owned by the User (com.google.appengine.api.users.User)?
So far I have endpoint:
#ApiMethod(name = "saveBook")
public void saveBook(Book book, User user) throws OAuthRequestException, IOException {
if (user == null) {
throw new OAuthRequestException("User is not authorized");
}
ofy().save()
.entity(BookRecord.fromBook(user, book))
.now();
}
Entity (in this context let's assume that User wrote the book):
#Entity
public class BookRecord {
#Parent
private Key<User> user;
#Id
private String id;
#Index
private String name;
public static BookRecord fromBook(User user, Book book) {
return new BookRecord(
Key.create(user),
book.getId(),
book.getName()
);
}
public BookRecord() {
}
private BookRecord(Key<User> user, String id, String name, String author) {
this.user = user;
this.id = id;
this.name = name;
this.author = author;
}
}
Problem arises from User not being an Entity, so I can't really use this solution and use User as #Parent directly. What is the general solution to solve this problem and store data owned by User?
Create your own User entity, this is where you'll store custom information/preferences for your app (as I suggested here).
You'll be using getUserId() to tie it with the Google user, and from there on you're free to use it as needed.

Objectify Delete doesn't seem to be working

I'm trying to delete an entity from my datastore using objectify but doesn't seem to be deleted even after shutting down the instance and restarting it. This is what the entity looks like in the datastore (both when it's on the production server & dev server):
This is the code i'm using to try and delete it:
#ApiMethod(name = "deleteDataVersion")
public Result deleteDataVersion(#Named("id") String id) {
// Where id is the id of the entity in the datastore.
if (id != null && !id.equals("")) {
ofy().delete().type(DataVersion.class).id(id).now();
return new Result(Result.STATUS_SUCCESS);
} else
return new Result(Result.STATUS_FAILED);
}
I've also tried this code:
#ApiMethod(name = "deleteDataVersion")
public Result deleteDataVersion(#Named("id") String id) {
if (id != null && !id.equals("")) {
// DataVersion doesn't have a parent.
Key<DataVersion> key = Key.create(null, DataVersion.class, id);
ofy().delete().key(key).now();
return new Result(Result.STATUS_SUCCESS);
} else
return new Result(Result.STATUS_FAILED);
}
But the entity never gets deleted. This is the code for my entity:
#Entity
public class DataVersion {
#Id
private Long id;
String folderName;
#Index
String effective;
public DataVersion() {
}
public DataVersion(String folderName, String effective ) {
this.folderName= folderName;
this.effective = effective;
}
// Getters & setters..
}
I just can't seem to find the problem :( Any help would be greatly appreciated! I'm sure it's something minor I'm overlooking (fairly new to Objectify/AppEngine).
The ID you have in parameter in your Endpoint is a String, and you try to delete the object DataVersion where the ID is a Long.
ofy().delete().type(DataVersion.class).id(Long.valueOf(id)).now();
would work better !
First get the key.
Key<DataVersion> key = Key.create(null, DataVersion.class, id);
Then fetch the entity from the database using the key.
DataVersion dataVersion = ofy().load().key(key).now();
Then delete the entity using objectify.
ofy().delete().entity(dataVersion).now();

Hibernate #OneToMany remove child from list when updating parent

I have the following entities:
TEAM
#Entity
#Table
public class Team {
[..]
private Set<UserTeamRole> userTeamRoles;
/**
* #return the userTeamRoles
*/
#OneToMany(cascade = { CascadeType.ALL }, mappedBy = "team", fetch = FetchType.LAZY)
public Set<UserTeamRole> getUserTeamRoles() {
return userTeamRoles;
}
/**
* #param userTeamRoles
* the userTeamRoles to set
*/
public void setUserTeamRoles(Set<UserTeamRole> userTeamRoles) {
this.userTeamRoles = userTeamRoles;
}
}
and
USER_TEAM_ROLE
#Entity
#Table(name = "user_team_role")
public class UserTeamRole {
#ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinColumn(name = "FK_TeamId")
public Team getTeam() {
return team;
}
}
Now, when updating a Team entity that contains for example Team.userTeamRoles = {UTR1, UTR2} with {UTR1, UTR3}, I want UTR2 to be deleted. But the way I do it now, the old list remains the same and it only adds UTR3 to the list.
This is how I do it at the moment:
if (!usersDualListData.getTarget().isEmpty()) {
// the role for each user within the team will be "employee"
team.setUserTeamRoles(new HashSet<UserTeamRole>());
Role roleForUser = roleService
.getRoleByName(RoleNames.ROLE_EMPLOYEE.name());
for (User user : usersDualListData.getTarget()) {
UserTeamRole utr = new UserTeamRole();
utr.setUser(user);
utr.setTeam(team);
utr.setRole(roleForUser);
team.getUserTeamRoles().add(utr);
}
}
teamService.updateTeam(team);
I thought that by doing team.setUserTeamRoles(new HashSet<UserTeamRole>()); the list would be reset and because of the cascades the previous list would be deleted.
Any help is appreciated. Thank you
Instead of replacing the collection (team.setUserTeamRoles(new HashSet<UserTeamRole>());) you have to clear() the existing one. This happens because if Hibernate loads the entity (and its collections) from DB, it "manages" them, ie. tracks their changes. Generally when using Hibernate it's better not to create any setters for collections (lists, sets). Create only the getter, and clear the collection returned by it, ie:
team.getUserTeamRoles().clear();
Another thing is that you miss orphan deletion (ie. delete child object when it's removed from collection in the parent). To enable it, you need to add #OneToMany(orphanRemoval=true) in owning entity.

How do I query a single field in AppEngine using JDO

I've got a Product POJO that looks like.
#PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Product extends AbstractModel {
#Persistent
private String name;
#Persistent
private Key homePage;
#Persistent
private Boolean featured;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Key getHomePage() {
return homePage;
}
public void setHomePage(Key homePage) {
this.homePage = homePage;
}
public boolean isFeatured() {
return featured;
}
public void setFeatured(Boolean featured) {
this.featured = featured;
}
}
My DataStore is currently completely empty.
I'd like to retrieve all homePage keys where featured is true for the Product.
I'm trying
PersistenceManager persistenceManager = getPersistenceManager();
Query query = persistenceManager.newQuery("SELECT homePage FROM " + getModelClass());
query.setFilter("featured == true");
List<Key> productPageKeys = (List<Key>) query.execute();
However this is giving me a null pointer error. How should I be constructing this query?
Cheers,
Peter
To do a projection, you would do something like
Query q = pm.newQuery("SELECT myField FROM mydomain.MyClass WHERE featured == true");
List<String> results = (List<String>)q.execute();
where String is the type of my field. Any basic JDO documentation would define that.
Internally GAE/J will retrieve the Entity, and then in the post-processing before returning it to the user it is manipulated into the projection you require.
As Nick pointed out in the other reply, this gives no performance gain over doing it yourself ... but then the whole point of a standard persistence API is to shield you from such datastore-specifics of having to do such extraction; it's all provided out of the box.
Entities are stored as serialized blobs of data in the datastore, so it's not possible to retrieve and return a single field from an entity. You need to fetch the whole entity, and extract the field you care about yourself.

Resources