I am trying to query the App Engine Data Store through Objective in Java.
I have stored some dummy data locally buy I can't achieve to get the result ordered by key.
These are the classes:
Parent Class:
#Entity
public class Parent
{
#Getter
#Setter
#Id
long id;
#Getter
#Setter
String type;
public Parent() {
}
}
Main Class
#Entity
#Cache
#Index
public class MainObject
{
#Getter
#Setter
#Id
long id;
#Getter
#Setter
#Unindex
String url;
#Getter
#Setter
Date date;
#Parent
#Getter
#Setter
Key<Parent> type;
public MainObject() {
}
}
The thing is that I want to get this query:
Key<Parent> parent = Key.create(Parent.class, 1);
MainObjectlastUrl = OfyService.ofy().load().type(MainObject.class).ancestor(parent).order("-key").first().now();
This returns null.
List<MainObject> list = OfyService.ofy().load().type(MainObject.class).ancestor(parent).order("-key").list();
This returns an empty list.
But if I remove the order query, I get all entities.
list = OfyService.ofy().load().type(MainObject.class).ancestor(parent).list();
Any ideas?
I have checked at Objectify web page, but I couldn't find much.
Thanks in advance.
The magic Google field that means key is __key__ (two underscores on each side). This is built in to GAE, so you want order("-__key__").
Objectify could provide an orderKey(boolean) method on query to make this slightly more convenient. If you add it to the issue tracker, I'll implement it.
As of Objectify 5.0.1, you can use orderKey(boolean descending) instead of order("__key__") when sorting by key. See Javadoc at http://static.javadoc.io/com.googlecode.objectify/objectify/5.1.14/com/googlecode/objectify/cmd/SimpleQuery.html#orderKey-boolean-
What you are trying to do is fundamentally wrong. Your desire is to have your query return results ordered by key; the very same thing that uniquely identifies your entity within the datastore. I cannot understand why you would want to do this since the key is derived using the Kind, Id and optionally the parent, if your class has one, as such I can't see how this ordering by key could ever be useful, but I am sure you have your reasons for wanting this. Perhaps you could expand on your question by explaining fully what you're trying to achieve.
Now I will attempt to answer your questions on why your queries aren't returning your desired results and suggest some solutions:
Your first query:
MainObjectlastUrl = OfyService.ofy().load().type(MainObject.class).ancestor(parent).order("-key").first().now();
The reason this query is returning null is because the key property you are passing as the condition to the order method to sort against is not a field of your MainObject entity. It does not exist and will always return null when objectify tries to apply the sort order.
The same applies to your second query. It returns an empty list because there are not entities of type MainObject with a key field. The only difference to the first query is that you are specifically requesting a list of entities rather than calling first().
The third query
list = OfyService.ofy().load().type(MainObject.class).ancestor(parent).list();
This query works, of course, because you are querying for all entities of type MainObject that are ancestors of specified `parent' entity. Since such entities exist the query returns the expected results.
As you can see, the assumption that an entity's "key" somehow intrinsically exists as a property of your entity is incorrect. In order to use it sort by Key you would need to add, say a property key to your entity 'MainObject' to hold the value of entity's generated key, which would not make sense and definitely not recommended.
Caveat: there may be a way of getting hold of the key since we know it exists but I am not aware. Perhaps some datastore expert can shed light on this.
I suggest you sort using the indexed properties on your class which make sense within the domain of your application. For example sort by id, since it isn't auto-generated and is likely to have some meaning; ditto for the date property as they're both likely to have some domain value, as opposed to the key. Hope this helps!
Related
I'm happening to end up with something really weird in Spring Data JDBC (using Spring Boot 2.1 with necessary starters) aggregate handling. Let me explain that case (I'm using Lombok, the issue might be related, though)...
This is an excerpt from my entity:
import java.util.Set;
#Data
public class Person {
#Id
private Long id;
...
private Set<Address> address;
}
This is an associated Spring Data repository:
public interface PersonsRepository extends CrudRepository<Person, Long> {
}
And this is a test, which fails:
#Autowired
private PersonsRepository personDao;
...
Person person = personDao.findById(1L).get();
Assert.assertTrue(person.getAddress().isEmpty());
person.getAddress().add(myAddress); // builder made, whatever
person = personDao.save(person);
Assert.assertEquals(1, person.getAddress().size()); // count is... 2!
Fact is that with debug I found out that the address collection (which is a Set) is containing TWO references of the same instance of the attached address.
I don't see how two references end up in, and most importantly how a SET (actually a LinkedHashSet, for the record) could handle the same instance TWICE!
person Person (id=218)
address LinkedHashSet<E> (id=228)
[0] Address (id=206)
[1] Address (id=206)
Does anybody have a clue on this situation ? Thx
A (Linked)HashSet can (as a side effect) store the same instance twice when this instance has been mutated in the meantime (quote from Set):
Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.
So here's what probably happens:
You create a new instance of Address but its ID is not set (id=null).
You add it to the Set, and its hash code is calculated as some value A.
You call PersonsRepository.save which most likely persists the Address and sets on it some non-null ID.
The PersonsRepository.save probably also calls HashSet.add to ensure that the address is in the set. But since the ID changed, the hash code is now calculcated as some value B.
The hash codes A and B map to different buckets in the HashSet, and so the Address.equals method does not even get called during HashSet.add. As a result, you end up with the same instance in two different buckets.
Finally, I think your entities should rather have equals/hashCode semantics based on the ID only. To achieve it using Lombok, you'd use #EqualsAndHashCode as follows:
#Data
#EqualsAndHashCode(of = "id")
public class Person {
#Id
private Long id;
...
}
#Data
#EqualsAndHashCode(of = "id")
public class Address {
#Id
private Long id;
...
}
Still, this will not solve the problem you have because it's the ID that changes, so the hash codes will still differ.
One way of handling this would be persisting the Address before adding it to the Set.
Tomasz Linkowski's explanation is pretty much spot on. But I'd argue for a different resolution of the problem.
What happens internally is the following: the Person entity gets saved. This might or might not create a new Person instance if Person is immutable.
Then the Address gets saved and thereby gets a new id which changes it's hashcode. Then the Address gets added to the Person since again it might be a new Address instance.
But it is the same instance yet now with a changed hashcode, which results in the single set containing the same Address twice.
What you need to do to fix this is:
Define equals and hashCode so that both are stable when saving the instance
i.e. the hashCode must not change when the instance gets saved, or by anything else done in your application.
There are multiple possible approaches.
base equals and hashCode on a subset of the fields excluding the Id. Make sure that you don't edit these fields after adding the Address to the Set. You essentially have to treat it like an immutable class even if it isn't. From a DDD perspective this treats the entity as a value class.
base equals and hashCode on the Id and set the Id in the constructor. From a domain perspective this treats the class as a proper entity which is identified by its ID.
I'm on Hibernate (5.1) and I need to add an element to a collection field on all entities matching a criteria, without the need to fetch them all.
#Entity
public class Order {
#Id
public Long id;
public Date placedOn;
#ManyToMany
public List<Item> items;
}
I need to add the same instance of a new Item to all Orders placed after a certain date. I need to use the Criteria API, not JPQL.
Is that possible? I can't find documentation on this.
P.S. The order/items case is just an example, I'm looking for a generalized method and I'll also need to adapt it to remove items from collections.
You cannot do that using CriteriaUpdate because it's not an update but many inserts because you have a ManyToMany relationship that relays on a relationship table in the database.
So you must query all Orders and then add the Item that will then generate the insert statements.
I have a model class that references another model class and seem to be encountering an issue where the #OneToOne annotation fixes one problem but causes another. Removing it causes the inverse.
JPA throws "multiple assignments to same column" when trying to save changes to model. The generated SQL has duplicate columns and I'm not sure why.
Here's a preview of what the classes look like:
The parent class references look like this:
public class Appliance {
public Integer locationId;
#Valid
#OneToOne
public Location location;
}
The child Location class has an id field and a few other text fields -- very simple:
public class Location {
public Integer id;
public String name;
}
When I attempt to perform a save operation, does anyone know why JPA is creating an insert statement for the Appliance table that contains two fields named "location_id"?
I need to annotate the reference to the child class with #OneToOne if I want to be able to retrieve data from the corresponding database table to display on screen. However, If I remove #OneToOne, the save works fine, but it obviously won't load the Location data into the child object when I query the db.
Thanks in advance!
It appears you did not define an #InheritanceType on the parent Class. Since you did not, the default is to combine the the parent and the child class into the same Table in the Single Table Strategy.
Since both entities are going into the same table, I think that #OneToOne is trying to write the id twice - regardless of which side it is on.
If you want the parent to be persisted in its own table, look at InheritanceType.JOINED.
Or consider re-factoring so that you are not persisting the parent separately as JOINED is not considered a safe option with some JPA providers.
See official Oracle Documentation below.
http://docs.oracle.com/javaee/7/tutorial/doc/persistence-intro002.htm#BNBQR
37.2.4.1 The Single Table per Class Hierarchy Strategy
With this strategy, which corresponds to the default InheritanceType.SINGLE_TABLE, all classes in the hierarchy are mapped to a single table in the database. This table has a discriminator column containing a value that identifies the subclass to which the instance represented by the row belongs.
In OpenJPA, according to the docs (http://openjpa.apache.org/builds/1.0.1/apache-openjpa-1.0.1/docs/manual/jpa_overview_mapping_field.html), section 8.4, the foreign key column in a one-to-one mapping:
Defaults to the relation field name, plus an underscore, plus the name
of the referenced primary key column.
And the JPA API seems to concur with this (http://docs.oracle.com/javaee/6/api/javax/persistence/JoinColumn.html)
I believe this means that in a one-to-one mapping, the default column name for properties in a dependent class is parentClassFieldName_dependentClassFieldName (or location_id in your case). If that's the case, the location_id column you are defining in your Appliance class is conflicting with the location_id default column name which would be generated for your Location class.
You should be able to correct this by using the #Column(name="someColumnName") annotation and the #JoinColumn annotation on your #OneToOne relationship to force the column name to be something unique.
Ok gang, I figured it out.
Here's what the new code looks like, followed by a brief explanation...
Parent Class:
public class Appliance {
public Integer locationId;
#Valid
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="location_id", referencedColumnName="id")
public Location location;
}
Child Class:
public class Location {
public Integer id;
public String name;
}
The first part of the puzzle was the explicit addition of "cascade = CascadeType.ALL" in the parent class. This resolved the initial "multiple assignments to same column" by allowing the child object to be persisted.
However, I encountered an issue during update operations which is due to some sort of conflict between EBean and JPA whereby it triggers a save() operation on nested child objects rather than a cascading update() operation. I got around this by issuing an explicit update on the child object and then setting it to null before the parent update operation occurred. It's sort of a hack, but it seems like all these persistence frameworks solve one set of problems but cause others -- I guess that's why I've been old school and always rolled my own persistence code until now.
I am new to both stackoverflow and JPA so I will try to explain this the best i can.
In an entity I want to set the foreign key by giving the int value but also I want to set it by giving an object. Here is some code to explain it better.
#Entity
public class Thread implements Serializable {
#ManyToOne
#JoinColumn(name = "accountId", referencedColumnName = "id", nullable = false)
public Account getAccount() {
return account;
}
#Column(name = "accountId")
#Basic
public int getAccountId() {
return accountId;
}
}
I have tried several ways but the code above is the best example for what I am trying to achieve. I understand that setting insert = false and update = false, in either of the 2 methods, makes this code work as far as compiling and running. But I want to be able to insert the accountId by using an Account object AND by setting the actual int accountId.
The reason for this is because sometimes, in my server, I only have the accountId and sometimes I have the Account object.
I also understand that the best solution is probably to use account.getId() when creating the Thread and setting the accountId. But it would be logically nice in my server to be able to just use the object.
Thanks in advance!
I think you have hit a conceptual problem in your application. You should stick to set the entity and do not use any foreign key values when using JPA. The cause of the problem is that your application is only providing the accountId at some point.
This may be due to different reasons. If this is because the part of the application only providing the accountId is legacy, than I would think it is perfectly fine to have an adapter that converts the accountId into an Account entity and then set that entity. Also not that the adapter could create a JPA proxy so that no actual database access is required at that point. Another reason I can think of, is that the application is loosing information at some point during processing. This may be the case when the application is using the Account in some place and only hands over it's Id to the code in question. Then such code should be refactored to hand over the entity.
In your specific case you are also able to use both, account as entity and the foreign key as attribute with both being insertable and updatable. You just have to make sure, that the accountId attribute value is consistent with the foreign key pointing to the row represented by the account entity. JPA providers should be able to handle this (I know OpenJPA does for example). However you are a bit restricted with this. For example you are only able to read the accountId attribute value, because setting it to a different value would cause an inconsistency between the account entity value.
I'm new to JPA/Hibernate and I'm wondering, what is usually the best way of updating a complex entity?
For example, consider the entity below:
#Entity
public class Employee {
#Id
private long id;
#Column
private String name;
#ManyToMany
private List<Positions> positions;
// Getters and setters...
}
What is the best way to update the references to positions? Currently a service is passing me a list of positions that the employee should have. Creating them is easy:
for (long positionId : positionIdList) {
Position position = entityManager.find(positionId);
employee.getPositions.add(position);
}
entityManager.persist(employee);
However, when it comes to updating the employee, I'm not sure what the best way of updating the employees positions would be. I figure there is two options:
I parse through the list of position id's and determine if the position needs to be added/deleted (this doesn't seem like a fun process, and may end up with many delete queries)
I delete all positions and then re-add the specified positions. Is there a way in JPA/Hibernate to delete all children (in this case positions) with one sql command?
Am I thinking about this the wrong way? What do you guys recommend?
How about
employee.getPositions.clear(); // delete all existing one
// add all of them again
for (long positionId : positionIdList) {
Position position = entityManager.find(positionId);
employee.getPositions.add(position);
}
although it may not be the most efficient approach. For a detail discussion see here.
Cascading won't help here much because in ManyToMany relation the positions may not get orphaned as they may be attached to other employee (s), or even they shouldn't be deleted at all, because they can exists on their own.
JPA/Hibernate has support for this. It's called cascading. By using #ManyToMany(cascade=CascadeType.ALL) (or limit the cascade type to PERSIST and MERGE), you specify that the collection should be persisted (merged/deleted/etc) when the owning object is.
When deletion is concerned, there is a special case, when objects become "orphans" in the database. This is handled by setting orphanRemoval=true