Probably a very trivial problem.
I have an object that looks like this:
#PersistenceCapable
public class Parent {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private String _id;
#Persistent
private List<Child> _children;
//...
}
... the nested entity looks like this (I am forced to declare primary key as Key otherwise it won't persist):
#PersistenceCapable
public class Child {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key _id;
#Persistent
private String _whatever;
//...
}
When I persist everything gets persisted OK (including Child entities), but I would like to get back everything by getting the parent object (e.g. getObjectById), but the collection comes back as null.
Owned One-to-Many Relationships seem to be what I am looking for -- but I am having trouble to see how it can help me to get back the parent object with the populated collection of children entities.
Any help appreciated!
#Persistent(defaultFetchGroup = "true")
Does the trick, you're right.
The content of your _children attribute is loaded only when you access it (before pm.close !) for the first time. It's called lazy-loading. If you want to have the child Entity or Collection of Child Entities to be directly loaded by default, apply the above "trick".
In my app, in case of a Collection of child Entities, it generates an Error message (Datastore does not support joins..) on the Dev Server, but you can ignore this wrong error, it is working fine in Dev and Prod Environments.
Be aware that fetching a Collection through it's parent Entity costs 1 datastore fetch per Child Entity.
this seems to do the trick:
#Persistent(defaultFetchGroup = "true")
True, while setting
#Persistent(defaultFetchGroup = "true")
at the field is the way to auto-load the related object in main object during fetch, things may not work as expected for nested objects if not supported with right configuration. If your have class with related object hosting other related object down to level 2 or 3, then configuring maxFetchDepth appropriately is critical.
<property name="datanucleus.maxFetchDepth" value="2"/>
is the configuration element in your JDOconfig.xml file to configure how deep you want your default fetch group objects to be loaded with main fetch.
Related
I want to save both child and parent entities whenever a POST call is made. I have an Item entity with a one to one mapping to a parent table Attribute:
#Entity
#Table(name="Item")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Item
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="attr_id")
private Attribute attribute;
#OneToMany(mappedBy = "item", cascade = CascadeType.ALL, orphanRemoval=true)
private List<ValueSet> valueSets = new ArrayList<>();
// Other fields, getters, setters, overriding equals and hashcode using Objects.equals() and Objects.hashCode() for all the fields, helper for adding and removing ValueSet
}
The Attribute entity looks like this:
#Entity
#Table(name="Attribute")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Attribute
{
#Id
#Column(name="id")
private Long id;
// Other fields, getters, setters, NOT overriding equals hashCode
}
Whenever an Item gets saved I need the Attribute to get saved as well. I've my postman sending JSON POST data as follows:
{
"attribute":{
"id":"6"
},
"valueSets":[
{
"value":"basic"
}
]
}
My handler looks like this:
#PostMapping("/item")
public void postItems(#RequestBody Item item)
{
itemRepository.save(item);
}
ItemRepository is just a one liner with #Repository annotation:
public interface ItemRepository extends CrudRepository<Item, Long>
When I try to save the Item I run into - Cannot insert the value NULL into column 'attr_id', table 'Item'; column does not allow nulls. INSERT fails.
I can't figure out why is it unable to take the id value of 6 that I am supplying as part of my POST invocation. The id value 6 already exists on the Attribute table. I have also tried making the relationship bi-directional using mappedBy and CASCADE.ALL but still get the same error.
Any thoughts/suggestions on what I'm messing/missing? Also, is there a better approach to handle nested entities? Should I try to save each of them individually? In that case can the #RequestBody still be the parent entity?
I have built an example project, and try to replicate your situation, not successful. I am able to insert the "item" without any issue.
I placed the project under this repository https://github.com/hepoiko/user-5483731-1
Hope this help you to troubleshooting further or let me know If I miss anything in there.
Im trying to query an object by ID that dosent have an ID field.
Doing key.getId() does return a number but will not work when using
//will not work due to parenting object
pm.getObjectById("EventModel", 5322);
public class EventModel extends Model
{
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
protected Key key;
...
}
NOTE: this is object is parented by a different object
i tried to query using "key.id == idParam" and it fails because the object key is not embedded is there any way to make this work so i can query the object by id not just only the key
A workaround will be to save the an id field after the key is initiazlied and query it
but this adds 2 writes each time i create an object.
If you have any idea on how to make this work please share :)
Thanks
Update: I found out the problem in my case is that I am generating the FbUser primary key by myself using keyfactory.createKey() method. If I change it to auto generate it works fine. But the problem is I don't want to because my data is in String format for the key. So I need to change the type from String to Key manually and then persist it.
I am using Google App Engine JPA and trying to have a oneToMany relationship amongst my entities.
#Entity
public class DummyParent{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
//#Unowned
#OneToMany(targetEntity=FbUser.class, mappedBy="dummyP", fetch=FetchType.LAZY, cascade = CascadeType.ALL)
private ArrayList<FbUser> users;
}
And here FbUser as the child :
#Entity
public class FbUser {
#Id
private Key id;
private String name;
#ManyToOne(fetch=FetchType.LAZY)
private DummyParent dummyP;
}
So after that I instantiate the parent class set its id and set the users. But I get the following exception:
Caused by: com.google.appengine.datanucleus.EntityUtils$ChildWithoutParentException: Detected attempt to establish DummyParent(no-id-yet) as the parent of FbUser("1322222") but the entity identified by FbUser("1322222") has already been persisted without a parent. A parent cannot be established or changed once an object has been persisted.
at com.google.appengine.datanucleus.EntityUtils.extractChildKey(EntityUtils.java:939)
at com.google.appengine.datanucleus.StoreFieldManager.getDatastoreObjectForCollection(StoreFieldManager.java:967)
at com.google.appengine.datanucleus.StoreFieldManager.storeFieldInEntity(StoreFieldManager.java:394)
Any idea why this is happening?
P.s. HRD is already enabled.
So you persisted FbUser without a parent entity and then try to change it at a later date, and GAE Datastore doesn't allow that (as the message says pretty clearly). You present no persistence code so no comment is possible other than guesswork.
Solution : persist it correctly (parent first, then child), or persist them as Unowned.
Let's say I have a very easy, classic setup: GAE(1.7.4) + GWT(2.5.0) Application, running on local Jetty (Development Server), using JDO for persistence.
Let's also say I have just 2 #PersistenceCapable classes: Person and Color. Every Person has exactly one favourite Color, but it does not mean that this Person owns this Color - many different Persons can have the same favourite Color. There is a limited number of well-known Colors and a Color may exist even if it is not anyone's favourite.
To model this I should use #Unowned relationship - please correct me if I am wrong:
#PersistenceCapable
public class Color { // just the most regular Entity class
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
String rgb;
// getter, setter, no constructor
}
#PersistenceCapable
public class Person {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
String surname;
#Persistent
#Unowned // here is the tricky part
Color color;
// getters, setters, no constructor
}
With some simple, well-known, PersistentManager-based code, I am able to successfully create and persist an instance of a Color class. I see it in GAE Development Console -> Datastore Viewer, having nice generated Key and ID/Name of (13), and my assigned RGB.
With very similar code, I am able to create an instance of Person class (in another request), assign a pre-existing Color as his favourite color (it pre-existed, I obtained it by pm.getObjectById()) and persist it. I see it in Datastore Viewer, with my nice generated Key and ID/Name of (15) and my assigned surname, and color_key_OID of (13). This looks very promising.
But then, when I fetch the Person(15) back from the DB (simple pm.getObjectById(), no transactions), it has my assigned surname correctly, but has null instead of Color(13)! Right - the Datastore Viewer gets it ok, but my code does not.
Oh, the problematic code? "Person p = pm.getObjectById(Person.class, key);".
(side notes: I am also having the same problem with #Unowned collections (nice list of values in Datastore Viewer, but null Collection field in my code.) My JDO jars on classpath are "datanucleus-api-jdo-3.1.1.jar" and "jdo-api-3.0.1.jar" so I assume they support #Unowned. There is no problem with not-#Unowned fields. I get no exceptions upon persisting or fetching, just plain nulls as field values.)
Either mark the color to be "eagerly fetched"
#Persistent(defaultFetchGroup="true")
#Unowned
Color color
or define your own fetchgroup like this:
#FetchGroup(name="eager", members={#Persistent(name="color")})
#PersistenceCapable
public class Person {
and use it if required by specifying the group to be fetched:
PersistenceManager pm = pmf.getPersistenceManager();
pm.getFetchPlan().addGroup("eager");
I was facing the same issue in one of my #Unowned Lists. I had more other two, which the Array is fetched perfectly.
What solved this issue for me was to change the name of property for a bigger one. In your case is like change the property name from "color" to something bigger, like "myfavoritecolor".
I have the same issue what you describe. How DataNucleus said you need to describe the whole lifecycle of the objects. In my case the problem was solved forcing getting the color, from the person object, before closing the PersistenceManager with the close() function.
Remember JDO uses the lazy-load technique to get objects.
I was able to solve this problem by adding fetch groups to the query and not to persistent manager.
PersistenceManager pm = PMF.get().getPersistenceManager();
logger.info("EVENTS FETCH GROUPS : " + pm.getFetchPlan().getGroups());
/*pm.getFetchPlan().addGroup("eventFetchGroup");
pm.getFetchPlan().setMaxFetchDepth(2);*/
Query q = pm.newQuery(Event.class);
q.getFetchPlan().addGroup("eventFetchGroup");
logger.info("EVENTS FETCH GROUPS : " +q.getFetchPlan().getGroups());
q.setFilter("date >= fromDate && date <= toDate");
q.declareParameters("java.util.Date fromDate, java.util.Date toDate");
I have an entity in my app engine datastore. There's actually only one instance of this entity. I can see it in my admin console. Is it possible to add a new attribute to the entity via the admin console (using gql perhaps)?
Right now it looks something like:
Entity: Foo
Attributes: mName, mAge, mScore
and I'd like to add a new boolean attribute to this entity like "mGraduated" or something like that.
In the worst case I can write some code to delete the entity then save a new one, but yeah was just wondering.
Thanks
-------- Update ---------
Tried adding the new attribute to my class (using java) and upon loading from the datastore I get the following:
java.lang.NullPointerException:
Datastore entity with kind Foo and key Foo(\"Foo\") has a null property named mGraduated.
This property is mapped to com.me.types.Foo.mGraduated, which cannot accept null values.
This is what my entity class looks like, I just added the new attribute (mGraduated), then deployed, then tried loading the single entity from the datastore (which produced the above exception):
#PersistenceCapable
public class Foo
{
#PrimaryKey
private String k;
/** Some old attributes, look like the following. */
#Persistent
#Extension(vendorName = "datanucleus", key = "gae.unindexed", value="true")
private String mName;
...
/** Tried adding the new one. */
#Persistent
#Extension(vendorName = "datanucleus", key = "gae.unindexed", value="true")
private boolean mGraduated;
The only way to implement this is to use Boolean as the type for the new property..
Than in set method you can accept boolean value, that's no issue.
If you want the get method to also return boolean.. you also can, but be sure to check if the value is null and if so.. return default value (e.g. true)
so
private Boolean newProp = null; // can also assing default value .. e.g. true;
public void setNewProp(boolean val)
{
this.newProp = val;
}
public boolean getNewProp()
{
if(this.newProp == null)
return true; // Default value if not set
return this.newProp.booleanValue();
}
I recommend you not to migrate your data in this case - it can be very costly and can deplete your quota easily (read old data, create new, delete old = 3 operations for every entry in you data store)
You can't do this through the admin console, but you shouldn't have to delete the entity. Instead just update it- the Datastore does not enforce schemas for Kinds.
E.g., if Foo is a subclass of db.Model (Python), change your model subclass to include the new property; fetch the model instance (e.g., by its key), update the instance, including setting the value of the new field; and save the modified instance. Since you just have one instance this is easy. With many such instances to update you'd probably want to do this via task queue tasks or via a mapreduce job.
You have declared the new mGraduated field using the primitive type boolean, which cannot be null. The existing entity can't be loaded into the model class because it doesn't have this property. One option is to declare this property using the Boolean class, which can accept a null value.
The Admin Console only knows about properties in existing entities. You cannot use the Admin Console directly to create a new property with a name not used by any existing entities. (This is just a limitation of the Console. App code can do this easily.)