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.
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.
I am using google cloud SQL with JDO. When I try to use the JDO PersistenceManager to store new objects with a new key it works fine, however I get an error when I try to update entities already inserted in the db:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '503062001-43661003' for key 'PRIMARY'
The key is indeed duplicated, but I want to update that object.
Is it possible to do this with the PersistentManager.makePersistentAll() method or in an another way that avoids to write the UPDATE query manually?
More details:
The object I am trying to persist is defined like this:
PersistenceCapable(table = "xxx")
public class XXX {
#PrimaryKey
#Index(name = "xxx_id")
private Long userId;
#PrimaryKey
#Index(name = "xxx_idx")
#Column(length = 128)
private String otherId;
...
}
If you want to update the object then you retrieve the object first, and update it (either in the same txn, or detach it and update it whilst detached). All of that would be described in the JDO spec
For example https://db.apache.org/jdo/pm.html and page down to "Update Objects"
I am totally new at this, I am sorry if it is stupid question.
I am trying to design database model for Google App Engine in JPA, but I am unable to get it right. When I find the way I can't get annotations right or I am getting error about M:N not supported in Google App Engine.
I need entity user to have multiple groups and groups have multiple users and there are users who are also group admins.
My basic model was User -> usergroup(user; group; (bool)isAdmin) <-Group
Can somebody give a clean and simple example of how to define relationships?
Please try this.
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private UserGroup usergroup;
}
class userGroup
#Entity
public class UserGroup {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key id;
private String name;
private boolean admin;
#OneToMany(mappedBy = "usergroup", cascade = CascadeType.ALL)
private List<User> users = new ArrayList<User>();
}
please be noticed GAE have limitation on JPA you can read more here
I don't know anything about Google App Engine, but I can help with JPA though.
The problem here is the "isAdmin" column, which prevents the data model to be a simple #ManyToMany relationship with a joiner table.
With the introduction of this field, in the data model you need a Map on the User entity with key=Group and value=isAdmin, similarly you need a corresponding Map in the Group entity in order to know if each User is an admin.
This is modeled with #ElementCollection in the following way:
#Entity
#Table(name="User")
public class User
{
#Id
#GeneratedValue(strategy= GenerationType.TABLE)
private int id;
private String name;
#ElementCollection
#CollectionTable(name="Users_Groups", joinColumns={#JoinColumn(name="userId")})
#MapKeyJoinColumn(name="groupId")
#Column(name="isAdmin")
private Map<Group, Boolean> groups;
}
#Entity
#Table(name="Group")
public class Group
{
#Id
#GeneratedValue(strategy= GenerationType.TABLE)
private int id;
private String name;
#ElementCollection
#CollectionTable(name="Users_Groups", joinColumns={#JoinColumn(name="groupId")})
#MapKeyJoinColumn(name="userId", insertable=false, updatable=false)
#Column(name="isAdmin", insertable=false, updatable=false)
private Map<User, Boolean> users;
}
The important annotation is #ElementCollection, the other annotations are just to name the specific columns of the collection table and make sure they match from both entities: #CollectionTable gives the name of the table and the name of the column representing the id in the current entity. #MapKeyJoinColumn gives the name of the column representing the id of the "key" element in the Map, and #Column gives the name of the "value" element in the map.
I'm not sure if the insertable=false and updatable=false are needed in one of the entities, might avoid adding duplicate rows due to the cyclic dependency between User and Group.
Also you need to manually create the collection table, because at least EclipseLink tries to create it with two "groupId" and "isAdmin" columns. You might consider reviewing the design if it is absolutely needed a cyclic dependency between User and Group.
I've been trying to get a relationship working with 2 entities in AppEngine, using JPA, and am currently running into this error:
java.io.IOException: com.google.appengine.repackaged.org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain
My entities look like this:
#Entity
public class MyUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
private List<MyMessage> messages;
}
and this:
#Entity
public class MyMessage {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#ManyToOne(optional=false)
private MyUser user;
}
The user already exists, and here is where I'm inserting a new message and get the recursion error:
EntityManager mgr = getEntityManager();
MyUser myuser = mgr.find(MyUser.class, KeyFactory.createKey("MyUser", user.getEmail()));
mymessage.setUser(myuser);
myuser.addMessage(mymessage);
mgr.persist(myuser);
mgr.persist(mymessage);
How am I supposed to setup this relationship within JPA and AppEngine guidelines? Thank you!
UPDATE
My problem was involving Jackson, not JPA. The JPA relationship is fine, but I needed to remove the relationship and manage it through the code as it was causing infinite recursion in serializing messages referring to users referring to messages and so on. I've also had to make sure that I annotated the user property in MyMessage as #Transient to avoid persistence complaining about persisting a parent owned by a child which already existed.
I'm unaware of a reasonable way for Endpoints to serialize these classes. The JSON resulting from your current code would look smiilar to the following:
// 1
{
"key": "foo",
"messages": [
{
"key": "bar",
"user": {
// repeat 1
},
// and so on...
}
Your best bet is to define a class (or classes) to send over the wire, instead of your JPA entities, which define JSON an infinite number of levels deep.
Obviously your message is nothing to do with JPA persistence, it's to do with Jackson (so presumably related to how you pass those objects back?). Only you know where that is invoked from. Whether your actual persistence operation succeeds or not is not clear from your post since you don't produce the stack trace, and rest of persistence code (like where that Jackson call originates)
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.