Problem detaching entire object graph in GAE-J with JDO - java

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.

Related

JPA persist entities with one to many relation

Config
EcliplseLink 2.3.2
JPA 2.0
The entities are auto created from the db schema from netbeans with Entity Classes from Database... wizard.
The controller classes are auto created from netbeans with JPA Controller Classes from Entity Classes... wizard
Short version of question
In a classic scenario, two tables with one to many relation. I create the parent entity, then the child entity and I attach the child to the parent's collection. When I create (controller method) the parent entity, I expect the child entity to be created to and associated with parent. Why doesn't it happen?
Long version
Parent class
#Entity
#XmlRootElement
public class Device implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
private Integer id;
#Column(unique=true)
private String name;
#Temporal(TemporalType.TIMESTAMP)
private Date updated;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "deviceId")
private Collection<NetworkInterface> networkInterfaceCollection;
public Device() {
}
public Device(String name) {
this.name = name;
updated = new Date();
}
// setters and getters...
#XmlTransient
public Collection<NetworkInterface> getNetworkInterfaceCollection() {
return networkInterfaceCollection;
}
public void setNetworkInterfaceCollection(Collection<NetworkInterface> networkInterfaceCollection) {
this.networkInterfaceCollection = networkInterfaceCollection;
}
public void addNetworkInterface(NetworkInterface net) {
this.networkInterfaceCollection.add(net);
}
public void removeNetworkInterface(NetworkInterface net) {
this.networkInterfaceCollection.remove(net);
}
// other methods
}
Child class
#Entity
#Table(name = "NETWORK_INTERFACE")
#XmlRootElement
public class NetworkInterface implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
private Integer id;
private String name;
#Temporal(TemporalType.TIMESTAMP)
private Date updated;
#JoinColumn(name = "DEVICE_ID", referencedColumnName = "ID")
#ManyToOne(optional = false)
private Device deviceId;
public NetworkInterface() {
}
public NetworkInterface(String name) {
this.name = name;
this.updated = new Date();
}
// setter and getter methods...
public Device getDeviceId() {
return deviceId;
}
public void setDeviceId(Device deviceId) {
this.deviceId = deviceId;
}
}
Main class
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("wifi-dbPU");
DeviceJpaController deviceController = new DeviceJpaController(emf);
NetworkInterfaceJpaController netController = new NetworkInterfaceJpaController(emf);
Device device = new Device("laptop");
NetworkInterface net = new NetworkInterface("eth0");
device.getNetworkInterfaceCollection().add(net);
deviceController.create(device);
}
}
This class throws a NullPointerException in line: device.getNetworkInterfaceCollection().add(net);
The system knows that there is a new entity device and it has an element net in it's collection. I expected it to write device in db, get device's id, attach it to net and write it in db.
Instead of this, I found that these are the steps I have to do:
deviceController.create(device);
net.setDeviceId(device);
device.getNetworkInterfaceCollection().add(net);
netController.create(net);
Why do I have to create the child when the parent class knows it's child and it should create it for me?
The create method from DeviceJpaController (sorry for the long names in fields, they are auto generated).
public EntityManager getEntityManager() {
return emf.createEntityManager();
}
public void create(Device device) {
if (device.getNetworkInterfaceCollection() == null) {
device.setNetworkInterfaceCollection(new ArrayList<NetworkInterface>());
}
EntityManager em = null;
try {
em = getEntityManager();
em.getTransaction().begin();
Collection<NetworkInterface> attachedNetworkInterfaceCollection = new ArrayList<NetworkInterface>();
for (NetworkInterface networkInterfaceCollectionNetworkInterfaceToAttach : device.getNetworkInterfaceCollection()) {
networkInterfaceCollectionNetworkInterfaceToAttach = em.getReference(networkInterfaceCollectionNetworkInterfaceToAttach.getClass(), networkInterfaceCollectionNetworkInterfaceToAttach.getId());
attachedNetworkInterfaceCollection.add(networkInterfaceCollectionNetworkInterfaceToAttach);
}
device.setNetworkInterfaceCollection(attachedNetworkInterfaceCollection);
em.persist(device);
for (NetworkInterface networkInterfaceCollectionNetworkInterface : device.getNetworkInterfaceCollection()) {
Device oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface = networkInterfaceCollectionNetworkInterface.getDeviceId();
networkInterfaceCollectionNetworkInterface.setDeviceId(device);
networkInterfaceCollectionNetworkInterface = em.merge(networkInterfaceCollectionNetworkInterface);
if (oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface != null) {
oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface.getNetworkInterfaceCollection().remove(networkInterfaceCollectionNetworkInterface);
oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface = em.merge(oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface);
}
}
em.getTransaction().commit();
} finally {
if (em != null) {
em.close();
}
}
}
I finally understood the logic behind persisting one to many entities. The process is:
Create parent class
Persist it
Create child class
Associate child with parent
Persist child (the parent collection is updated)
With code:
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("wifi-dbPU");
DeviceJpaController deviceController = new DeviceJpaController(emf);
NetworkInterfaceJpaController netController = new NetworkInterfaceJpaController(emf);
Device device = new Device("laptop"); // 1
deviceController.create(device); // 2
NetworkInterface net = new NetworkInterface("eth0"); // 3
net.setDeviceId(device.getId()); // 4
netController.create(net); // 5
// The parent collection is updated by the above create
}
}
Now, I can find a device (with id for example) and I can get all its children using
Collection<NetworkInterface> netCollection = device.getNetworkInterfaceCollection()
In the device entity class, which I posted in the question, there is no need for the methods addNetworkInterface and removeNetwokrInterface.
#Dima K is correct in what they say. When you do this:
Device device = new Device("laptop");
NetworkInterface net = new NetworkInterface("eth0");
device.getNetworkInterfaceCollection().add(net);
deviceController.create(device);
The collection in device hasn't been initialized and so you get a NPE when trying to add to it. In your Device class, when declaring your Collection, you can also initialize it:
private Collection<NetworkInterface> networkInterfaceCollection = new CollectionType<>();
As for persisting, your assumptions are correct but I think the execution is wrong. When you create your device, make it persistent with JPA right away (doing transaction management wherever needed).
Device device = new Device("laptop");
getEntityManager().persist(device);
Do the same for the NetworkInterface:
NetworkInterface net = new NetworkInterface("eth0");
getEntityManager().persist(net);
Now since both your entities are persisted, you can add one to the other.
device.getNetworkInterfaceCollection().add(net);
JPA should take care of the rest without you having to call any other persists.
This is a known behavior of collection data members.
The easiest solution is to modify your collection getter to lazily create the collection.
#XmlTransient
public Collection<NetworkInterface> getNetworkInterfaceCollection() {
if (networkInterfaceCollection == null) {
networkInterfaceCollection = new Some_Collection_Type<NetworkInterface>();
}
return networkInterfaceCollection;
}
Also, remember to refer to this data member only through the getter method.
This exception means you're trying to locate an entity (probably by em.getReference()) that hasn't been persisted yet.
You cannot you em.getReference() or em.find() on entities which still don't have a PK.
In order to enable save ability on a #OneToMany relation e.g.
#OneToMany(mappedBy="myTable", cascade=CascadeType.ALL)
private List<item> items;
Then you have to tell to your #ManyToOne relation that it is allowed to update myTable like this updatable = true
#ManyToOne #JoinColumn(name="fk_myTable", nullable = false, updatable = true, insertable = true)

Relationships in Google App Engine

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!

Appengine JDO, how can I retrieve a child object knowing a child property and the parent key?

Using the peristance manager, how can I retrieve a child object knowing a child property and the parent key?
The Parent is defined like this:
public class User {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key id;
#Persistent(mappedBy = "user")
#Element(dependent = "true")
private List<Section> sections;
...
And the child is defined like this:
public class Section {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
#Persistent
private User user;
#Persistent
private String title;
...
Knowing the 'User' id and the 'Section' title, how can I retrieve the section?. I was trying to build a query to retrieve the section using something like this: 'where title == xxx AND user.id ¿? == xxx' but I'm not sure how to specify the user id. Is there any way to do it using queries or methods from the persistance manager?
Thanks.
I finally made it with this method:
public static Section getSectionByTitle(String title, Key user_key){
PersistenceManager pm = PMF.get().getPersistenceManager();
Query query = pm.newQuery("select from "+Section.class.getName()+" WHERE title == s && user == keyParam");
query.declareParameters("String s, String k");
query.setUnique(true);
Section section = (Section) query.execute(title, user_key.getId());
return section;
}
You can call this method on the query object:
q.setAncestor(ancestorKey);
Read this page for more information (Ancestor Queries).
I remember seeing something like 'where ANCESTOR = ' syntax but I cannot find any reference for it now.

Google App Engine not saving a list of objects in a class

I am using DataNeucleus JDO implementation which works fine on my test server but does not work well when deployed.I am new to GAE and I know I that not all DataNeucleus features are supported. Not sure how to make this work.
I have a class say :
#PersistenceCapable
public class MyInfoClass {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
Key key;
#Persistent(mappedBy = "myInfoClass")
private List <BuddyData> buddyList;
...
}
And the BuddyData
#PersistenceCapable
public class BuddyData {
...
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
Key key;
// the one-to-many rel to MyInfoClass -> BuddyData
#Persistent
private MyInfoClass myInfoClass;
....
}
I save the data to data store as below which works in test environment but does not work when deployed to app engine.
PersistenceManager pm = PMF.get().getPersistenceManager();
Transaction tx = pm.currentTransaction();
try {
tx.begin();
//NOTE : myInfo is the persistent MyInfoClass object retrieved from the datastore
myInfo.getBuddyList().add(new BuddyData(email));
tx.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (tx.isActive()) {
tx.rollback();
}
pm.close();
}
Owned relationships can be quite tricky in GAE. I remember battling with them for hours.
First of all, you have an owned relationship cycle. I doubt that is supported by GAE. So you will need to change one of the entities to use Key to reference the other one, instead of the direct object relationship (i.e. owned relationship).
For example:
#PersistenceCapable
public class BuddyData {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
Key key;
// use Key instead of MyInfoClass
#Persistent
private Key myInfoClass;
}
Secondly, when retrieving BuddyData list make sure the PersistenceManager is still open. If you do it like this:
PersistenceManager pm = PMF.get().getPersistenceManager();
MyInfoClass info = pm.getObjectById(MyInfoClass.class, key);
pm.close();
info.getBuddyList();
Then contents of buddyList will be empty.
In such case you either need to move pm.close() after getBuddyList(), or set it as the default fetch group (see below), which will eagerly prefetch all contents at a potential performance cost.
#Persistent(mappedBy = "myInfoClass", defaultFetchGroup = "true")
private List <BuddyData> buddyList;
Good luck.

One-to-Many relationship. Select objects from datastore

I've omitted some code(package declarations, imports, other fields)
for shortness.
I have here simple One-to-Many relation.
It worked fine till this moment.
#PersistenceCapable(identityType = IdentityType.APPLICATION,
detachable="true")
class Restaurant implements Serializable {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
Key id
#Persistent(mappedBy = "restaurant")
List<RestaurantAddress> addresses = new ArrayList<RestaurantAddress>()
}
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#PersistenceCapable(identityType = IdentityType.APPLICATION,
detachable="true")
class RestaurantAddress implements Serializable {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
Key id
#Persistent
Restaurant restaurant
}
Now i need to get(select) all the Restaurants from DB:
def getRestaurantsToExport(final String dst, final int count) {
String field = restaurantExportFields[dst]
return transactionExecute() { PersistenceManager pm ->
Query q = pm.newQuery(Restaurant.class)
q.filter = "$field == null"
q.setRange(0, count)
return q.execute()
}
}
But there are on problem - query gives me 12 restaurants(as in DB) but
every Restaurant has 0 Address but in Datastore every Restaurant has
minimum 2 addresses.
Have anyone the same problem or knows the solution ?
are you sure the Addresses are not lazy loaded? Just a guess... is there some way to force an "eager" loading of the objects
If someone will have the same problem:
Replace
#Persistent(mappedBy = "restaurant")
List<RestaurantAddress> addresses = new
ArrayList<RestaurantAddress>
with
#Persistent(mappedBy = "restaurant",defaultFetchGroup = "true")
List<RestaurantAddress> addresses = new
ArrayList<RestaurantAddress>
Another method is that you have to "touch" addresses property for
every Restaurant in the retrieved list before closing
PersistentManager. After PersistenManager being closed you cannot
retrieve anything from datastore and Restaurant keeps null.
Solution found with help of google-appengine-java users.

Categories