I have just one week experience in GAE/Java and trying to port an legacy application(developed using PHP/MySQL) to GAE+JDO. I'm now stuck with a basic issue in creating a relationship between two tables(kinds in GAE).
So here is the case:
We have a Users table which holds the user authentication information. It also has a field user_role which store role_id, which is actually a foreign key of another table user_roles.
From the the Entity-Relationship documentation in GAE, I understand that DataStore doesn't support foreign-key relationships and designed the Users class by adapting the Employee-ContactInfo example in the docs.
When I executed the application, the user_roles kind is inserted each time I add an entry in Users table. The user_roles kind is supposed to have only three static values. But this is having redundant values as I input more records in Users.
I think that I'm missing something very trivial, but I couldn't figure it out due to my inexperience to datastore. It would be very nice if someone could guide me to solve this issue.
Here is the code:
#PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Users {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
private String userName;
#Persistent
private String password;
#Persistent
private String salt;
#Persistent
private Date createdDate;
#Persistent
private Key createdBy;
#Persistent
private Date lastLogin;
#Persistent
private boolean status;
#Persistent
private String authKey;
#Persistent(defaultFetchGroup="true")
private SecurityRole securityRole;
#Autowired
SecurityRepository securityRepository ;
public SecurityPrincipals(String userName, String password,SecurityRole securityRole,boolean status) {
this.securityRole = securityRole;
this.userName = userName;
this.password = password;
this.status = status;
}
//getters and setters
}
Definition for Roles:
#PersistenceCapable(detachable="true")
public class SecurityRole {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
private String securityRoleName;
#Persistent
private String securityRoleDescription;
#Persistent
private String securityRoleStatus;
#Persistent
private Date securityRoleCreatedDate;
public SecurityRole(String securityRoleName, String securityRoleDescription, String securityRoleStatus,String securityBaseType)
{
this.securityRoleName = securityRoleName;
this.securityRoleDescription = securityRoleDescription;
this.securityRoleStatus = securityRoleStatus;
this.securityBaseType = securityBaseType;
}
// getters and setters
}
The relevant code from Controller:
SecurityRole securityRole = securityRepository.getSecurityRole( securityRoleName);
users = new Users(userName,password,status,securityRole);
iUserRepository.save(employeeDetails);
Here is the definition of getSecurityRole:
public SecurityRole getSecurityRole(String securityRoleName)
{
PersistenceManagerFactory pmf = this.jdoTemplate.getPersistenceManagerFactory();
PersistenceManager pm = pmf.getPersistenceManager();
try {
Query query = pm.newQuery( SecurityRole.class);
query.declareImports("import java.lang.String");
query.declareParameters("String securityRoleName");
query.setFilter("this.securityRoleName == securityRoleName");
List<SecurityRole> securityRoles = (List<SecurityRole>)query.execute(new String(securityRoleName));
SecurityRole temp = null;
for(SecurityRole securityRole: securityRoles)
{
temp = securityRole;
}
return temp;
}
finally {
pm.close();
}
}
Here is the definition of iUserRepository.save():
public void save(Users user) {
jdoTemplate.makePersistent(companyDetails);
}
In the Users class, you have the defined the property
#Persistent(defaultFetchGroup="true")
private SecurityRole securityRole;
This statement creates an "owned" relationship in GAE datastore, which means that when you create an object of the Users class, an object of the SecurityRole class will be created as well.
What you need, is an unowned relationship that can be created as follows:
#Persistent(defaultFetchGroup="true")
private Key securityRole;
In this way, a SecurityRole object is not created each time you create an instance of the Users class. For more information about owned and unowned relationships, take a look at http://code.google.com/appengine/docs/java/datastore/jdo/relationships.html
Hope this helps!
Related
I'm strugling with JPA. I tried several things but I can't figure out the right way to put the annotations.
What is want is like an Order/OrderLine relationship.
Thus:
Order( PK=orderId, fields=[...])
OrderLine (Pk1=orderId,Pk2=orderLineId, fields=[...])
Obviously, OrderLine.orderId refers to the 'Order' table.
What I functionally want to do is at least:
retrieve the Order with and without all orderlines. It should have a Set
retrieve an orderline by full PK, but without the associated Order.
retrieve a list of orderlines by orderId.
I only want these 2 tables and classes. nothing more nothing less.
I tried several things. Can anybody help me out with putting in the right annotations and members on these two classes?
Edit: what i've done so far.
Note that in this real example User=Order and UserRun=OrderLine. So, i am not interested in a seperate 'Run'-entity. Merely a UserRun as described by the Orderline.
#Entity
#Table(name = "user_runs")
public class UserRun {
#EmbeddedId
private UserRunKey id;
public UserRun(){};
public UserRun(String userName, String runUuid) {
this.id = new UserRunKey(userName, runUuid);
}
public String getUserName() {
return this.id.getUserName();
}
public String getRunUuid() {
return this.id.getRunUuid();
}
}
#Embeddable
class UserRunKey implements Serializable {
#Column(name = "username")
private String userName;
#Column(name = "run_uuid")
private String runUuid;
public UserRunKey(){};
public UserRunKey(String userName, String runUuid) {
this.runUuid = runUuid;
this.userName = userName;
}
public String getUserName() {
return userName;
}
public String getRunUuid() {
return runUuid;
}
}
This created a userruns/orderline table with the PK in the wrong way:
create table user_runs (run_uuid varchar(255) not null, username varchar(255) not null, primary key (run_uuid, username))
I want the primary key in reverse.
I want username as FK to User
I want a Set in my User-class.
When I do the following in my User-class:
#OneToMany
private Set<UserRun> userRuns;
It will create a
create table user_user_runs (user_username varchar(255) not null, user_runs_run_uuid varchar(255) not null, user_runs_username varchar(255) not null, primary key (user_username, user_runs_run_uuid, user_runs_username))
And that's something I definitely don't want! Once again, I don't want a Run-object (same as nobody's interested in a Line-class, from OrderLine)
I think I figured it out.
The UserRun/Orderline class:
#Entity
#Table(name = "user_runs")
public class UserRun {
#EmbeddedId
private UserRunKey id;
public UserRun(){};
public UserRun(String userName, String runUuid) {
this.id = new UserRunKey(userName, runUuid);
}
public String getUserName() {
return this.id.getUserName();
}
public String getRunUuid() {
return this.id.getRunUuid();
}
}
#Embeddable
class UserRunKey implements Serializable {
#Column(name = "username")
private String userName;
#Column(name = "run_uuid")
private String zrunUuid; //starts with a z, so the PK will be pk(username,run_uuid). Apparently, order in PK is determined from the variable names (alphabetic order)....
public UserRunKey(){};
public UserRunKey(String userName, String zrunUuid) {
this.zrunUuid = zrunUuid;
this.userName = userName;
}
public String getUserName() {
return userName;
}
public String getRunUuid() {
return zrunUuid;
}
}
In the userclass:
#OneToMany(mappedBy = "id.userName", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<UserRun> userRuns;
Unfortunately, there are 2 downsides:
I see that there are 2 queries executed instead of a Join on username. One to retrieve user, and 1 to retrieve the Set...
I needed to alter variablenames of the PK (compound/Embeddable). It seems there is no clean way to define the PK order. (Seriously?). Fortunately, the variable name is private, and not exposed by getter.
If anybody knows a cleaner way for these 2 issues. Let me know!
I think what you have to do is the following:
Because the primary key is compound key you need an ID class, as you already did:
#Embeddable
class OrderLinePK implements Serializable {
// you can use physical mapping annotations such as #Column here
#Column(name="...")
private Integer orderLineID;
// This is foreign key and the physical mapping should be done
// on the entity, and not here
private Integer orderID;
public OrderLinePK(){}
// getters + setters
// orverride equals() and hashCode() methods
}
Implement OrderLine entity
#Entity
public class OrderLine {
#EmbededId private OrderLinePK id;
#Mapsid("orderID")
#ManyToOne
#JoinColumn(name = "ORDER_ID", referencedColumn="ID")
private Order order;
// getters + setters ....
}
And the Order entity:
#Entity
public class Order {
#Id
private Integer id;
#OneToMany(fetch = FetchType.LAZY) // actually default by 1-to-n
private Coolection<OrderLine> orderLines;
// getters + setters ....
}
Requirement is to add username column for every audit entry.
After googling a lot and going through the enverse docs I figured out how to implement it. I have implemented it as follows:
Implemented a revision listener:
#Configurable
public class UserRevisionListener implements RevisionListener
{
public void newRevision(Object revisionEntity)
{
UserRevEntity revision = (UserRevEntity) revisionEntity;
String username = "";
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
if (principal != null && principal instanceof DepotUser)
username = ((DepotUser) principal).getUsername();
revision.setUsername(username);
}
}
Create an entity class:
#Entity
#Table(name = "USER_REV_ENTITY")
#RevisionEntity(UserRevisionListener.class)
public class UserRevEntity extends DefaultRevisionEntity
{
private static final long serialVersionUID = 1L;
private String username;
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
}
I have referred a link that has an example of the same "Thinking in Software ".
Default auditing is working fine.
Firstly, the table for the audit revision with the new username column should get auto generated (I think!). Its not getting generated.
So, I tried by creating the table and adding the table mapping in UserRevEntity. But no luck.
Can anyone help my identifying the issue?
Thanks, in advance.
#MappedSuperclass
public class DefaultRevisionEntity implements Serializable {
#Id
#GeneratedValue
#RevisionNumber
private int id;
#RevisionTimestamp
private long timestamp;
// ... rest of the class body here....
This is DefaultRevisionEntity, which has already declared with id & timestamp.
For your new column username, it has to be annotated as #Column in UserRevEntity as given below.
#Column(name="username")
private String username;
I will be very thankful if someone could help me to solve this problem
as I have already spent a lot of time on it.
The frontend of my application sends employee data to server end,
which creates Emmployee object and saves the data on datastore. My
application provides the keyword search functionality on title,
company and jobDesc so I am using Search Api.
The problem is that I want to use datastore for storing the complete
data and document for storing searchable data. How can I link
datastore with document? I know it can be achieved if I set employee’s
key as document id but the problem is how will I get the key of the
data which is being store. If I try to get key using e.getKey() that
obviously returns nullPointerException because it does have the key at
that time.
I can achieve this by reading all employee data stored on datastore
and creating document with it and setting employee’s key as document
id but I want to create document as the data is received from frontend
of application.
//EmployeeServlet
PersistenceManager pm = PMF.get().getPersistenceManager();
Employee e = new Employee(title, company, location, category,
jobType, gender,
careerLevel, salaryRange,
sector, jobDesc);
Document newDoc = Document.newBuilder().setId(???)
.addField(Field.newBuilder().setName("title").setText(title))
.addField(Field.newBuilder().setName("company").setText(company))
.addField(Field.newBuilder().setName("jobDesc").setText(jobDesc)).build();
SearchIndexManager.INSTANCE.indexDocument("Employee", newDoc);
pm.makePersistent(e);
//Employee
#PersistenceCapable
public class Employee {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
private String title;
#Persistent
private String company;
#Persistent
private String location;
#Persistent
private String category;
#Persistent
private String jobType;
#Persistent
private String gender;
#Persistent
private String careerLevel;
#Persistent
private String salaryRange;
#Persistent
private String sector;
#Persistent
private Text jobDescription;
public Employee(String title, String company, String location,
String category,
String jobType, String gender,
String careerLevel, String salaryRange,
String sector,
String jobDescription) {
super();
this.title = title;
this.company = company;
this.location = location;
this.category = category;
this.jobType = jobType;
this.gender = gender;
this.careerLevel = careerLevel;
this.salaryRange = salaryRange;
this.sector = sector;
this.jobDescription = new Text(jobDescription);
}
}
Save employee entity. Get the id.
Set this id as a document id, index the document.
Both steps can be done in the same server call. Just move your makePersistent() before you create a document.
I'm trying to store in the GAE DB a class which some of its fields are classes themselves.
Just before going into more details I want to say it worked just fine before I added these new class field.
So, I followed the documentation here: https://developers.google.com/appengine/docs/java/datastore/jdo/dataclasses
and I'm getting this error :
org.datanucleus.jdo.exceptions.ClassNotPersistenceCapableException: The class "The class "sporteam.web.client.User" is not persistable. This means that it either hasnt been enhanced, or that the enhanced version of the file is not in the CLASSPATH (or is hidden by an unenhanced version), or the Meta-Data/annotations for the class are not found." is not persistable. This means that it either hasnt been enhanced, or that the enhanced version of the file is not in the CLASSPATH (or is hidden by an unenhanced version), or the Meta-Data for the class is not found.
The main class I'm trying to use is User:
#SuppressWarnings("serial")
#PersistenceCapable(identityType = IdentityType.APPLICATION)
public class User implements Serializable
{
// data members
// user unique facebook id
#Persistent
#PrimaryKey
private String facebook_id;
// users facebook code
#Persistent
private String facebook_code;
// users device ID code
#Persistent
private String dev_id;
// users registration ID code
#Persistent
private String reg_id;
// user first name
#Persistent
private String first_name;
// user last name
#Persistent
private String last_name;
// user age, should be grater then 8
#Persistent
private int age;
// user email, as setup in facebook
#Persistent
private String email;
// user weight, should be greater then 40
#Persistent
private double weight;
// user workout_level (0.8 is the highest(best shape) and 1 is the lowest)
#Persistent
private double workout_level;
// user gender ("male"/"female"/"unknown")
#Persistent
private String gender;
#Persistent
#Embedded
private UserTracks userTracks = null;
// default constructor
public User()
{
}
//....
}
This is the UserTracks class:
#PersistenceCapable
#EmbeddedOnly
#SuppressWarnings("serial")
public class UserTracks implements Serializable
{
#Persistent
#Embedded
//#Element(embedded="true")
private List<Track> tracks = null;
#Persistent
private long date = 0;
public UserTracks()
{
}
}
And theses are the other 2 classes used:
#PersistenceCapable
#EmbeddedOnly
#SuppressWarnings("serial")
public class Track implements Serializable
{
/** running information **/
#Persistent
private double avgSpeed = 0;
#Persistent
private double distance = 0;
#Persistent
private double calories = 0;
#Persistent
private long time = 0;
/************************/
#Persistent
private long date = 0;
#Persistent
private String name = null;
#Persistent
#Embedded
private List<GeoPtr> track = null;
public Track()
{ // needed for Serializable
}
}
GeoPtr:
#PersistenceCapable
#EmbeddedOnly
#SuppressWarnings("serial")
public class GeoPtr implements Serializable
{
/** the speed is in m/s **/
#Persistent
float speed;
#Persistent
long time;
#Persistent
double altitude;
#Persistent
double latitude;
#Persistent
double longitude;
#Persistent
double calorie = 0;
public GeoPtr()
{ //needed for Serializable
}
}
As far as I can see, every class is PersistenceCapable and all the fields are either PersistenceCapable themselves or a collection of it and according to the documentation above it should work.
Ideas?
Thanks
You try to use embedded class Track to store collection of GeoPtr to UserTracks. How DataStore will handle it? When embedding into UserTracks fields of GeoPtr added to common single record in DataStore. You will have:
avgSpeed, distance, ...., speed, time
What DataStore should do in collection case?
avgSpeed, distance, ...., speed_1, time_1, ..., speed_2, time_2, ... speed_N, time_N
???
Is this make any sence?
Don't use embedded. Create three separate classes with primary keys. Use one-to-one relationship for User - UserTracks and one-to-many for UserTracks - GeoPtr.
Read about relations here
Well apparently there is some problem with embedding a collection of user defined classes.
I've read somewhere that its not possible to embed a collection field that has an embedded collection field itself. However, you can achieve almost the same by serializing these fields (you wont be able to index them - that's the difference between serialized to embedded, but I don't need indexes on these fields for my use so it worked out just fine) you can do so by using the next notation :
#Persistent(serialized = "true")
Hoped it will save all of you the pain I've been through.
Bar.
I am trying to load the full object graph for User, which contains a
collection of decks, which then contains a collection of cards, as
such:
User:
#PersistenceCapable(detachable = "true")
#Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE)
#FetchGroup(name = "decks", members = { #Persistent(name =
"_Decks") })
public abstract class User {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
protected Key _ID;
#Persistent
protected String _UniqueIdentifier;
#Persistent(mappedBy = "_Owner")
#Element(dependent = "true")
protected Set<Deck> _Decks;
protected User()
{
}
}
Each Deck has a collection of Cards, as such:
#PersistenceCapable(detachable = "true")
#FetchGroup(name = "cards", members = { #Persistent(name =
"_Cards") })
public class Deck {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key _ID;
#Persistent
String _Name;
#Persistent(mappedBy = "_Parent")
#Element(dependent = "true")
private Set<Card> _Cards = new HashSet<Card>();
#Persistent
private Set<String> _Tags = new HashSet<String>();
#Persistent
private User _Owner;
}
And finally, each card:
#PersistenceCapable
public class Card {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key _ID;
#Persistent
private Text _Question;
#Persistent
private Text _Answer;
#Persistent
private Deck _Parent;
}
I am trying to retrieve and then detach the entire object graph. I
can see in the debugger that it loads fine, but then when I get to
detaching, I can't make anything beyond the User object load. (No
Decks, no Cards). At first I tried without a transaction to simply
"touch" all the fields on the attached object before detaching, but
that didn't help. Then I tried adding everything to the default fetch
group, but that just generated warnings about GAE not supporting
joins. I tried setting the fetch plan's max fetch depth to -1, but
that didn't do it. Finally, I tried using FetchGroups as you can see
above, and then retrieving with the following code:
PersistenceManager pm = _pmf.getPersistenceManager();
pm.setDetachAllOnCommit(true);
pm.getFetchPlan().setGroup("decks");
pm.getFetchPlan().setGroup("cards");
Transaction tx = pm.currentTransaction();
Query query = null;
try {
tx.begin();
query = pm.newQuery(GoogleAccountsUser.class); //Subclass of User
query.setFilter("_UniqueIdentifier == TheUser");
query.declareParameters("String TheUser");
List<User> results = (List<User>)query.execute(ID); //ID = Supplied
parameter
//TODO: Test for more than one result and throw
if(results.size() == 0)
{
tx.commit();
return null;
}
else
{
User usr = (User)results.get(0);
//usr = pm.detachCopy(usr);
tx.commit();
return usr;
}
} finally {
query.closeAll();
if (tx.isActive())
{
tx.rollback();
}
pm.close();
}
This also doesn't work, and I'm running out of ideas...
I'm sure reading of the log (Debug level) would tell you way more, since it certainly tells you when it is detaching things. Perhaps GAE/J is not respecting lazy loading at detach ? DataNucleus itself works fine, with all other datastores.
Why call FetchPlan.setGroup() when that overwrites all existing groups ? addGroup() makes more sense to me.