Insert different type of entities in GAE using JPA - google-app-engine

I am new to GAE and Datastore
I am trying to insert different types of entities in GAE Datastore using JPA.
For example i have to insert Employee,EmployeePersonalInfo(Having a Employee Key),EmployeeAddressInfo(Having a EmployeePersonalInfo key). Here i am not creating any foreign key relationships between entities.My entities will looks like follows
public class Employee{
private String name;
private Key key;
}
public class EmployeePersonalInfo{
private String emailAddress;
private Key key;
private Key employeeKey;
}
public class EmployeeAddressInfo{
private String cityName;
private Key key;
private Key employeePersonalInfoKey;
}
i am trying to insert around 5 record on each table like
public class EmployeeController{
/*This method will be called for 5 times*/
public void insertEmployeeDetails(Employee emp,EmployeePersonalInfo perInfo,EmployeeAddressInfo addressInfo){
employeeDao.save(emp);
employeePersonalInfoDao.save(perInfo);
employeeAddressInfoDao.save(addressInfo);
}
}
Every time When a call goes to Save method of the DAO classes i will open the EntityManager object , and close after the operation.
public void save(Employee emp){
EntityManager em = EMFService.get().createEntityManager();
em.save(emp);
em.close();
}
Here sometimes i am getting an exception like
Caused by: java.lang.IllegalArgumentException: cross-group transaction need to be explicitly specified, see TransactionOptions.Builder.withXG
I have seen my so many solutions to this problem but i am not able to understand what is the real problem and solution. Please help me

Related

How to use dynamic schema in spring data with mongodb?

Mongodb is a no-schema document database, but in spring data, it's necessary to define entity class and repository class, like following:
Entity class:
#Document(collection = "users")
public class User implements UserDetails {
#Id private String userId;
#NotNull #Indexed(unique = true) private String username;
#NotNull private String password;
#NotNull private String name;
#NotNull private String email;
}
Repository class:
public interface UserRepository extends MongoRepository<User, String> {
User findByUsername(String username);
}
Is there anyway to use map not class in spring data mongodb so that the server can accept any dynamic JSON data then store it in BSON without any pre-class define?
First, a few insightful links about schemaless data:
what does “schemaless” even mean anyway?
“schemaless” doesn't mean “schemafree”
Second... one may wonder if Spring, or Java, is the right solution for your problem - why not a more dynamic tool, such a Ruby, Python or the Mongoshell?
That being said, let's focus on the technical issue.
If your goal is only to store random data, you could basically just define your own controller and use the MongoDB Java Driver directly.
If you really insist on having no predefined schema for your domain object class, use this:
#Document(collection = "users")
public class User implements UserDetails {
#Id
private String id;
private Map<String, Object> schemalessData;
// getters/setters omitted
}
Basically it gives you a container in which you can put whatever you want, but watch out for serialization/deserialization issues (this may become tricky if you had ObjectIds and DBRefs in your nested document). Also, updating data may become nasty if your data hierarchy becomes too complex.
Still, at some point, you'll realize your data indeed has a schema that can be pinpointed and put into well-defined POJOs.
Update
A late update since people still happen to read this post in 2020: the Jackson annotations JsonAnyGetter and JsonAnySetter let you hide the root of the schemaless-data container so your unknown fields can be sent as top-level fields in your payload. They will still be stored nested in your MongoDB document, but will appear as top-level fields when the ressource is requested through Spring.
#Document(collection = "users")
public class User implements UserDetails {
#Id
private String id;
// add all other expected fields (getters/setters omitted)
private String foo;
private String bar;
// a container for all unexpected fields
private Map<String, Object> schemalessData;
#JsonAnySetter
public void add(String key, Object value) {
if (null == schemalessData) {
schemalessData = new HashMap<>();
}
schemalessData.put(key, value);
}
#JsonAnyGetter
public Map<String, Object> get() {
return schemalessData;
}
// getters/setters omitted
}

GAE jpa database model example

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.

Google App Engine JPA getting com.google.appengine.datanucleus.EntityUtils$ChildWithoutParentException

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.

GAE Datastore with GWT, making more friendly/smaller keys

I am currently working with GWT, GAE and using JPA as my ORM. I have an issue where the keys that GAE is generating are too large reasonably to be used on a mobile device with RequestFactory. The amount of data in a small list is overwhelming due to the size of the ID/KEY when converted to String.
I am using String for my key's so that I can handle inheritence.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
protected String key;
This creates a key that is very long example "agxzbWFydGJhcnNpdGVyFAsSDUVzdGFibGlzaG1lbnQYuAIM" and gets larger due to storing object type and parent in the key.
I need a way to create a smaller unique id but still have the ability to handle inheritence in GAE. I tried Long as the #Id/key but was not able to use a #OneToMany relationship on my objects due to the relationship that is built into the String/Key key.
The other option is to create a sequence for each class and use a Long property for that id. There is an example below but I am not sure how to handle a generated Long sequence in app engine.
#GeneratedValue
private Long friendlyClassSpecificKey;
Any advice would be appreciated. If there is another option other than using the sequence for each class type I am interested but if not is there an example of creating a sequence (That is not the #ID) for a specific class?
I came up with a good solution for smaller keys. I think the best way to do this cleanly is to use jpa/jdo 2 for app engine with an unowned relationship. This way you can fetch the keys from (Long) id using just their type and not have to use the parent relationship.
This is the base datstore object and notice I am using the app engine key.
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class DatastoreObject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
public Long getId() {
return key.getId();
}
}
This class will use the #Unowned attribute supported in jpa 2 so that the inventory's key does not contain the parent establishment key. Otherwise you would have to pass the parent id in also and resolve that to a key based on type. This is because in an owned relationship the child key contains the parent key also.
#Entity
public class Establishment extends DatastoreObject {
#Unowned
#OneToOne(cascade = CascadeType.ALL)
private Inventory inventory;
}
Then in my dao base class I use the class
public class DaoBase<T extends DatastoreObject> {
protected Class<T> clazz;
#SuppressWarnings("unchecked")
public DaoBase() {
clazz = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
/**
* Find an object by it's shortened long id
* #param id
* #return
* #throws EntityNotFoundException
*/
public T find(Long id) {
if (id == null) {
return null;
}
EntityManager em = ThreadLocalPersistenceManager.getEntityManager();
Key key = getKey(id);
T obj = em.find(clazz, key);
return obj;
}
protected Key getKey(Long id) {
return KeyFactory.createKey(clazz.getSimpleName(), id);
}
}

Why does JDO Class break Guava MultiMap index?

I'm unable to create a multimap index with the JDO Score class below. If I substitute Object[] for Score everything works fine. I thought the issue was that the Score class was not serializable? What am I missing from the Score class?
Score Class:
#PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true")
#javax.jdo.annotations.Version(strategy=VersionStrategy.VERSION_NUMBER,column="VERSION",
extensions={#Extension(vendorName="datanucleus", key="field-name",value="version")})
public class Score implements Serializable {
private static final long serialVersionUID = -8805789255398748271L;
#PrimaryKey
#Persistent(primaryKey="true", valueStrategy=IdGeneratorStrategy.IDENTITY)
private Key id;
private Long version;
#Persistent
private String uid;
#Persistent
private Integer value;
}
Multimap index:
List<Score> rows = new ArrayList(scores);
Multimap<Key, Score> grouped = Multimaps.index(rows,
new Function<Score, Key>() {
public Key apply(Score item) {
return (Key) item.getObjKey();
}
});
First of all, if you're going to use Guava you should probably use a real release of Guava rather than code that's repackaged for internal use in app engine.
That said, it looks like (assuming the repackaged code works the same as the current released Guava code) at least one of your Score objects' getObjKey() method must be returning null. ImmutableMultimaps don't allow null keys or values.

Resources