We are in process of porting an entire application on google app engine. This application has persistence using JPA and as usual contains mapping between entities using annotations. The mapping like @OnetoMany and @ManytoOne in Jpa may throw lot of errors in google app engine and therefore is neither straightforward nor trivial. In this blog we will see where our mapping may fail, with specific problem related to multiple parent key provider fields. Google app engine may throw this error in our application, we will see how can we manage it with unowned relationships in our application.
We have three entities User, Project and ProjectAssignment. ProjectAssignment entity has @ManyToOne mapping with an User and a Project. In these relationships we also have a bidirectional mapping using @OneToMany relationship from User and Project for ProjectAssignment.
Let’s have a look at the code listing for the entities described above.
[sourcecode language=”java”]
@Entity
public class User {
@OneToMany(mappedBy = "user")
private Set<ProjectAssignment> projectAssignments;
—
—
—
}
[/sourcecode]
[sourcecode language=”java”]
@Entity
public class Project {
@OneToMany(mappedBy = "project")
private Set<ProjectAssignment> projectAssignments;
—
—
—
}
[/sourcecode]
[sourcecode language=”java”]
@Entity
public class ProjectAssignment {
@ManyToOne(fetch = FetchType.EAGER)
private User user;
@ManyToOne
private Project project;
—
—
—
}
[/sourcecode]
This works fine for JPA code outside google app engine. We can access ProjectAssignmets of an user from User entity and can also access it from Project entity.
If we ported this code with same mappings in google app engine we encounter a problem. It complains that the above mentioned mappings have a problem with the following stack trace. “javax.persistence.PersistenceException: App Engine ORM does not support multiple parent key provider fields”.
It leaves us wondering, We already tried @OneToMany and @ManyToOne mappings before and it just worked fine. To top it up we also tried bidirectional mapping between the entities and that was okay too. What just happened here? let’s look at these entities a bit more closely.
In google app engine it is clear from the message that it will not allow an Entity to have more than one parent. Which in this case for ProjectAssignment is User as well as Project. Unlike traditional RDBMS Goole App Engine has a datastore and it needs to figure out how can it traverse through the entity groups which also needs to be unique. Specific to our case, in order to reach ProjectAssignment google app engine finds that it can be done via User entity and from ProjectAssignment as well.
The work around is to break away the mapping of one of the entities with ProjectAssignment so that ProjectAssignment has a single parent. Which indeed seems a hassle as our bidirectional mappings gave us the ease of accessing the entities and now a lot of application code and the DAO’s also need to change as well.
The choice which worked well for us is that whenever we encounter mapping problems of this nature we introduce unowned relationships between entities. Let’s see how can we tackle this problem with unowned relationships.
We introduce unowned relationship between Project and ProjectAssignment. We remove the @OneToMany annotation from Project for ProjectAssignment and also remove @ManyToOne annotation from ProjectAssignment for Project. We introduce set of keys of ProjectAssignment in Project and we also provide key of Project in ProjectAssignment. This breaks the parent child relationship between Project and ProjectAssignment and as expected the error goes way as well.
Let’s see how our code looks like for Project and ProjectAssignment after the above mentioned changes.
[sourcecode language=”java”]
@Entity
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String projectKey
@Transient
private Set<ProjectAssignment> projectAssignments;
@Basic(fetch = FetchType.EAGER)
private Set<String> projectAssignmentKeys = new HashSet<String>();
—
—
—
}
[/sourcecode]
[sourcecode language=”java”]
@Entity
public class ProjectAssignment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String projectAssignmentKey;
@ManyToOne(fetch = FetchType.EAGER)
private User user;
@Transient
private Project project;
// Unowned relationship
private String projectKey;
—
—
—
}
[/sourcecode]
There will be associated changes for managing the keys for these entities as now our application will need to manage them as well. Once, the associated keys are managed then we can access Project via ProjectAssignment and ProjectAssignments via Project by querying the entities based on their primary keys. After the data access layer is changed accordingly then the business layer can continue to work as before oblivious of the changes that went to port the code to google app engine.
Porting of jpa code to google app engine is not as straightforward as it appears to be. It will involve lot of code changes either in our data access layer or even in our service layer. We can manage the relationship problems effectively by using unowned relationships between entities.
Very nice article Meetu. I’m trying to migrate a traditional JPA app to GAE, and found the same problem.
By the way, on your examples, you’re not forgetting to exclude or comment “@OneToMany” and “@ManyToOne” from the refactored entities?
Hi jk,
Nice catch, I have updated the code now.
Hi Meetu, I have updated my code according to you article. Earlier exception is resolved now I am getting below mentioned exception please help :
Could not determine type for: java.util.List, at table: user_registration, for columns: [org.hibernate.mapping.Column(chatKeys)]