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);
}
}
Related
I'm using Objectify on Google's AppEngine.
I have the following Entity-Model:
#Entity
public class ChallengeEntity {
#Id
private Long id;
#Index
public List<ChallengeParticipant> participants;
}
The Participant (not an entity... should it be one?)
public class ChallengeParticipant {
#Load
public Ref<UserEntity> user;
// ... participant-specific attributes
}
And the User-Entity:
#Entity
public class UserEntity {
#Id
Long id;
#Index
public String email = "";
}
Now how would I find all challenges for a given user-email?
Something along:
ofy().load().type(ChallengeEntity.class).filter("participants.user.email", "test#local.foo")
I am willing to adapt my entity-model to GAE's needs... how may I support this query efficiently and keep a nice model?
Thanks alot
Assuming your list of ChallengeParticipant is reasonably bounded (a few hundred at most) and you aren't at risk of hitting the 1M per-entity size limit, you're probably best leaving it as embedded.
To perform your query, first lookup the person by email, then filter by person:
UserEntity user = // load user (or get the key) by email
ofy().load().type(ChallengeEntity.class).filter("participants.user", user);
Note that you need to #Index the ChallengeParticipant.user field, not the ChallengeEntity.participants list.
Assuming that email is unique for a user, I'd keep ChallengeParticipant as a separate entity and maintain 2 way relationship with ChallangeEntity:
public class ChallengeParticipant {
#Id
String email; // must be able to uniquely identify a user.
List<Ref<ChallengeEntity>> challenges;
// ... participant-specific attributes
}
ChallengeEntity will exist as is but without any #Index
#Entity
public class ChallengeEntity {
#Id
private Long id;
public List<Ref<ChallengeParticipant>> participants;
}
When you want to add a new participant to a challenge, update both entities (Participant & Challenge) in one transaction. As there are no indexes involved, you'll always get consistent results.
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
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 am using JPA annotations and when i have relation OneToMany - ManyToOne, when i see my entity in the ManyToOne, the joinColumn is always with null value.
Next i will show my example, i have Product:
#Entity
#Table(name = "PC_PRODUCT")
public class Product extends LaunchEntity {
private static final long serialVersionUID = 1L;
#XmlElement(name = "Product_Name", required = true)
protected String productName;
#XmlElement(name = "Product_Description")
protected String productDescription;
#XmlElement(name = "Product_To_Charge")
#OneToMany(mappedBy = "product", cascade=CascadeType.MERGE)
protected List<ChargeRelation> productToCharge;
And, this is my ChargeRelation class:
#Entity
#Table(name="PC_CHARGE_RELATION")
public class ChargeRelation
extends RelationEntity
{
private static final long serialVersionUID = 1L;
#XmlElement(name = "Charge", required = true)
#OneToOne(cascade = CascadeType.MERGE)
protected Charge charge;
#XmlTransient
#ManyToOne(cascade=CascadeType.MERGE)
#JoinColumn(name="PRODUCT_ID")
protected Product product;
I am reading a xml file, convert data for a string, make unmarshall for my root object and persist this object.
The problem is when i found a charge relation in my string, the values are inserted on the charge relation table but the column with the product_id is always null.
I have all setters and getters defined. How i can force this to make the manual insert? thanks
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
EclipseLink JAXB (MOXy) has an extension called #XmlInverseReference that allows you to map the back-pointer.
Product
#Entity
#Table(name = "PC_PRODUCT")
public class Product extends LaunchEntity {
#XmlElement(name = "Product_To_Charge")
#OneToMany(mappedBy = "product", cascade=CascadeType.MERGE)
protected List<ChargeRelation> productToCharge;
}
ChargeRelation
The #XmlInverseReference annotation is used where you previously had #XmlTransient. #XmlInverseReference acts like #XmlTransient during the marshal operation, and will populate the back-pointer during an unmarshal operation.
#Entity
#Table(name="PC_CHARGE_RELATION")
public class ChargeRelation extends RelationEntity {
#XmlInverseReference(mappedBy = "productToCharge")
#ManyToOne(cascade=CascadeType.MERGE)
#JoinColumn(name="PRODUCT_ID")
protected Product product;
}
For More Information
http://blog.bdoughan.com/2010/07/jpa-entities-to-xml-bidirectional.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
Note that JPA provider reflects the state of many-to-one side of relationship when saving it to the database.
However, JAXB only populates one-to-many side during XML unmarshalling, therefore you need to populate many-to-one side manually after unmarshalling.
I am having trouble understanding how the entity groups and relationships work with GAE using JDO.
The scenario I run is basically:
PersistenceManager pm = this.pmf.get();
Player player = new Player();
player.setRanking(new Ranking());
Player persistent = pm.makePersistent(player);
// Here the detached copy is returned, and contains
// all the persistent fields (including the Ranking)
Player detached = pm.detachCopy(persistent);
// In the real code, a lot of processing goes here and manipulates
// The detached copy
// The outcome is basically an updating ranking. What I want to do is
// to assign this new ranking to the player, and persist
// it in the datastore
detached.setRanking(new Ranking());
// An exception is thrown here
pm.makePersistent(detached );
The output of the above code is an exception:
Detected attempt to establish Player(10) as the parent of Ranking(12) but the entity identified by Ranking(12) has already been persisted without a parent. A parent cannot be established or changed once an object has been persisted.
What I understand from that is that the Ranking entity is first persisted (with no parent, so as a root entity), and later on the player is persisted as the parent of the first Ranking entity. Since a parent cannot be changed, this results in an exception being thrown.
However I would like the application to work on the detached copy and manipulate it as it sees fit, and have the entities properly created when the Player entity is persisted in the datastore.
My classes are annotated as follow:
Player.java
#PersistenceCapable(detachable = "true")
#Inheritance(customStrategy = "complete-table")
public class Player implements Serializable, IPlayer {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
#Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
private String id;
#Persistent(defaultFetchGroup="true", dependent="true")
private Ranking ranking;
}
And Ranking.java
#PersistenceCapable(detachable = "true")
#Inheritance(customStrategy = "complete-table")
public class Ranking implements Serializable {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
#Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
private String id;
// If I could I'd rather get rid of this reference to the parent
#Persistent(mappedBy = "ranking", defaultFetchGroup="true")
private Player player;
}
There is something in the entity / entity group paradigm that I obviously don't get, and would be happy to get any hint you may be able to offer on this.
[EDIT]
I have put together a test case to reproduce the issue.
[/EDIT]
Sébastien