I'm using the Google App Engine in combination with the Google Web Toolkit to write a bug tracker (to see what the technologies are capable of).
Modelled after Google Code's issue tracker, I decided that an issue can have 0 or more labels, that can be defined beforehand (let's say in the settings).
The label class (CustomLabel):
#PersistenceCapable(identityType = IdentityType.APPLICATION)
public class CustomLabel implements Serializable {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
#Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
private String encodedKey;
#Persistent
#Extension(vendorName="datanucleus", key="gae.pk-id", value="true")
private Long keyId;
/**
* label caption.
*/
#Persistent
private String caption;
// Unimportant getters / setters
}
Now the parent class (Issue):
#PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Issue implements Serializable {
private static final long serialVersionUID = 1L;
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
// Replacing the Long key by this key doesn't make a difference
// #PrimaryKey
// #Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
// #Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
// private String encodedKey;
#Persistent
private String summary;
#Persistent
private String description;
#Persistent
private ArrayList<CustomLabel> labels;
// Other details
}
When I'm trying to persist a new Issue with existing CustomLabels I get the following exception:
org.datanucleus.exceptions.NucleusUserException: Detected attempt to establish Issue(11) as the parent of CustomLabel(1) but the entity identified by CustomLabel(1) has already been persisted without a parent. A parent cannot be established or changed once an object has been persisted.
How can this be solved? I cannot use Key's and create an unowned relationship, since I'm sending the objects to the GWT front-end (which is compiled to Javascript and com.google.appengine.api.datastore.Key isn't supported). Besides that would break referential integrity, which is undesirable.
You can't assign an already existing CustomLabel as a child element to a new Issue. Each CustomLabel entity can only belong to one Issue object, due to the way datanucleus handles relationships. It puts both the parent and child object into the same Entity Group. An entity can only belong to one Entity Group. So let's say you create a Custom Label called "nastybug" and persist it. It now belongs to some entity group X. When you create a new Issue, and go to persist that, it will belong to some Entity Group Y. Datanucleus (and the actual google datastore) will not let you try to store an entity from group X into group Y.
If you want labels to be shared among issues, you will need to use an unowned relationship. You are correct that you cannot pass a Key through GWT's RPC mechanism, so you may need to translate your JDO objects into some other form before sending them.
You can read about entity groups here
Use #Unowned for your list:
import com.google.appengine.datanucleus.annotations.Unowned;
...
#Persistent
#Unowned
private List<Category> categories;
And PrimaryKey Long for your CustomLabel id:
#Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
#PrimaryKey
private Long id;
Related
I am wondering about best practices in database design with Hibernate.
I have a User entity that is going to have a lot of different settings. For each set of settings, I have to either add them as extra columns in the User table or create a separate entity and connect them with a #OneToOne relationship. It is my understanding that #OneToMany and #ManyToOne relationships should generally take place in separate tables because you should not have columns that are optional.
But it is kind of unclear for #OneToOne relationships. I think there is a case for using #OneToOne because ORMs will select all single attributes by default and having a lot of columns will slow down that process.
An example of what I am talking about can be illustrated by
#Entity
public class User{
#OneToOne
private ForumSettings forumSettings;
#OneToOne
private AccountSettings accountSettings;
#OneToOne
private SecuritySettings securitySettings;
}
vs
#Entity
public class User{
#Column
private boolean showNSFWContent; //Forum Setting
#Column
private int numberOfCommentsPerPage; //Forum Setting
#Column
private boolean subscribedToNewsLetter; //Account Setting
#Column
private boolean isAccountBanned; //Account Setting
#Column
private boolean isTwoFactorAuthenticationEnabled; //Security Setting
#Column
private boolean alertForSuspiciousLogin; //Security Setting
}
The above is a simple example to show the concept, but in practice there would be many more columns in the 2nd portion.
I know that this might be opinion based, but I am hoping someone could share the pros/cons of both choices.
Thank you very much
Your question is in general about Data normalization. Normalization is itself extensive field of study and basically is a way of structuring database tables avoiding redundancy and making sure that updates don’t introduce anomalies.
And first rule of normalization says a table shall contain no repeating groups. In your case it does.
SOLUTION 1 : Store UserSettings as Entity as map as OneToMany relationship
#Entity
public class User
#OneToMany
private List<UserSettings> userSettings;
And then you can query for particular setting type by joining User and UserSettings entities.
For example (JPQL)
SELECT user u
JOIN u.settings us
WHERE us.settings_type = 'account_settings'
and us.settings_value = 'secure' // or any other logic
Advantage of this approach is that UserSettings will have it is own persistence identity and can be queried by it's own. It it is not dependent on parent.
For example :
SELECT q from Query q where ...
Solution 2 : Store settings in a collection of basic elements
You can store User Settings in the collection (Each user will have it's own set of settings)
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String name;
...
#ElementCollection
#CollectionTable(name="USER_SETTINGS")
#MapKeyColumn(name="SETTINGS_TYPE")
#Column(name="SETTINGS_VALUE")
Map<String, Boolean> userSettings = new HashMap<>();
UserSettings collection will be stored in a separate table with foreign key to User table. UserSettings does not have it is own persistence ID, is dependent on User entity and can be queried only through it is parent ('User')
Solution 3: Store User Settings as Embedded type
Embedded type is not an entity, it does not have it is own persistence ID and is depends on parent type, stored as part of parent record in database (in User table)
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String name;
...
#Embedded
private UserSettings userSettings;
UserSettings is in separate class, but stored in User table.
#Embeddable
public class UserSettings {
private List<String> securitySettings; // or any other collection type
private List<Boolean> forumSettings;
I'm mapping a Filter ---< FilterColumn where Filter presents cardinality one and FilterColumn N. So the mapped classes are:
#Entity
public class Filter implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private String name;
private String caption;
#OneToMany(cascade = CascadeType.MERGE, targetEntity = FilterColumn.class)
private Set<FilterColumn> columns;
// setters and getters
}
#Entity
public class FilterColumn implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private FilterColumnId id;
private String caption;
// getters and setters
#Embeddable
public static class FilterColumnId implements Serializable {
private static final long serialVersionUID = 1L;
#ManyToOne
private Filter filter;
#Column
private String name;
// getters and setters
}
}
But when I start the application with drop-create instruction the following 3 tables are created:
Filter PK(name)
FilterColumn PK(filter_name, name)
Filter_FilterColumn PK(filter_filter_name, filterColumn_filter_name, filterColumn_name)
What I really want is just two tables like:
Filter PK(name)
Filter_Column PK(name, filter_name)
Why do I receive this result? Is there something wrong with my mapping? What should I change?
Thanks in advance.
I think you need a mappedBy on the #OneToMany. Without that, the mapper doesn't know that it can look at the filtercolumn table to find the entities associated with a Filter, so it generates the filter_filtercolumn table.
Not sure off the top of my head how you to a mappedBy with a composite key. Given that you're using an #EmbeddedId, i think it's simply mappedBy = "id".
Can you use a #ManyToOne in a key class like that? Is that a Hibernate extension over and above the JPA spec? Wouldn't you normally need a #MapsId in there somewhere?
Try adding a #JoinColumn annotation on the Filter member of your composite id. The actual column would be whatever the id of the of the Filter table is (or just leave it without a name if you let hibernate generate it all).
Let me know if this works as I had a similar problem and solved it using the above so I do know it's possible. The only other thing mine has is a #ForeignKey annotation but I think hibernate will take care of that for you -- I just did mine because I wanted to stick to a naming convention.
I need to have two objects of the same type. By default appengine doesn't allow it, but I found this parameter: datanucleus.appengine.allowMultipleRelationsOfSameType, so I can save the two same type objects.
In debug mode, before calling the makePersistent method I checked a value inside each object and they were differents, however, when I tried to recover the values from the datastore, they were the same. Both had the value of the second object?
This code is to save the object FaseGAE:
manager = GAEDAOFactory.get().getPersistenceManager();
Key faseKey = KeyFactory.stringToKey(grupo.getFaseKey());
FaseGAE faseGAE = manager.getObjectById(FaseGAE.class, faseKey);
faseGAE.addGrupoGAE(grupoGAE);
faseGAE = manager.makePersistent(faseGAE);
manager.close();
This code is to get the object:
manager = GAEDAOFactory.get().getPersistenceManager();
FaseGAE faseGAE2 = manager.getObjectById(FaseGAE.class, faseKey);
FaseGAE object:
#PersistenceCapable
public class FaseGAE {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent private List<GrupoGAE> grupos;
GrupoGAE object:
#PersistenceCapable
public class GrupoGAE {
#PrimaryKey
#Persistent (valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent private List<MyClass1> list;
MyClass1 object:
#PersistenceCapable
public class MyClass1 {
#PrimaryKey
#Persistent (valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent private MyClass2 sameTypeObject1;
#Persistent private MyClass2 sameTypeObject2;
#Persistent private String testValue1;
#Persistent private String testValue2;
MyClass2 Object:
#PersistenceCapable
public class MyClass2{
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
testValue1 and testValue2 keeps different values, but sameTypeObject1 and sameTypeObject2 have the value of sameTypeObject2. I checked the datastore and both objects were created with different values. It seems like both point to the same reference.
Am I doing something wrong?
Something it's missing to work with same type relations?
Definitely AppEngine doesn't allow same type relations?
I've encountered a similar problem before, I"m not too sure what's your exact problem and whether it's the same. But hope this answer will at least point you in the right direction
However, there are a couple of "best practices" you can adopt when using java with GAE.
1) implement Serializable for classes
i.e. public class FaseGAE implements Serializable
- this will enable persistent capable classes to be stored and retrieve with session objects.
2) you could try using objectify for GAE datastore
http://code.google.com/p/objectify-appengine/
I intend questions not to be a child since I had to manipulate it independently, and I don't want to persist the questions field, I would to fill it up by retrieving the questions manually. Here is the code.
Questionnaire.java
#PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Questionnaire{
//supposedly non-persistent
public List<Question> questions = new ArrayList<Question>();
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
public Long questionnaireID;
#Persistent
public String title;
#Persistent
private int items;
#Persistent
public String description;
Question.java
#PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Question{
//non-persistent as well
public ArrayList<Choice> choiceList = new ArrayList<Choice>();
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
public Long questionID;
#Persistent
public String text;
#Persistent
public long questionnaireID;
public Question(){
}
would spit out this error:
org.datanucleus.store.appengine.MetaDataValidator$DatastoreMetaDataException: Error in meta-data for com.ivanceras.server.Question.questionID: Cannot have a java.lang.Long primary key and be a child object (owning field is com.ivanceras.server.Questionnaire.questions).
Adding a #NotPersistent might help.
The GAE/J docs are totally misleading; they suggest that you need #Persistent on every field and that is totally wrong. All fields have a default persistent flag ... things like String, primitives, Collection, List, Set, Map are by default persistent so no need for #Persistent on those. This point has been made to Google several times yet the docs still have this.
Use the DataNucleus docs if you want clear information as per the JDO spec
Adding "transient" may help too
#PersistenceCapable(identityType = IdentityType.APPLICATION, detachable ="false")
public class Foo implements IsSerializable {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
#Persistent
private Long revision;
#Persistent
private String information;
}
The problem is this object keeps overwriting itself when persisted, not creating a new 'record' with the next revision.
In a traditional RDBMS it'd be a two-column primary key.
How do I accomplish this with Google App Engine Datastore?
You need to create and write a new record for each update if you want to keep a revision history. The id uniquely identifies the record - the Datastore has no way of knowing that you consider the revision to be part of the id too.
I think this is the best way to solve it.
#PersistenceCapable(identityType = IdentityType.APPLICATION,detachable = "false")
public class Foo implements IsSerializable {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long _internalId;
#Persistent
private Long id;
#Persistent
private Long revision;
#Persistent
private String information;
}
where id and revision are treated as the primary key in the application.