I'm designing an application in which I hope to use projection queries to retrieve Entity objects from the App Engine Datastore to save on latency and cost. I have two questions about this:
1) If one calls the Entity.getProperty(String propertyName) method on a returned Entity where propertyName does not correspond to one of the properties selected for by the Projection, will the return value be null or will Java throw an exception? The Entity documentation doesn't indicate what happens if propertyName does not exist. A corollary, do the not-selected-for properties no longer exist in the returned Entity or do they simply have no or null values assigned to them?
2) Is the Key of the truncated Entity that is returned the same as the Key of the original full Entity that's actually in the datastore? From what I understand, the Key is a hash of the kind, name/id property, and ancestor path of an Entity. Name/id also appears to be a property, so if one doesn't select for it, does the Key of the returned Entity differ from that of the actual Entity?
These questions seem like they would be fairly easy to answer with some testing once I'm up and running, but I'm new to App Engine and am still just designing my project, so I won't be in a position to do so for a while. Was hoping someone out there already knows the answer.
If a property does not exist in an entity, .getPropery() returns null. No exception is thrown.
If a property is not named in a projection query, the retrieved entity does not have this property even if the full entity does.
The key of an entity returned in a projection query is the same as in any other query. The key is created when an entity is first inserted in the datastore, and it does not change after that.
A little explanation. When you insert a new entity in the datastore, the datastore creates an entity and, separately, creates an entry for each indexed property, or a combination of properties (custom index), in a respective index. Projection query is just another combination of indexed properties. It retrieves all the data it needs directly from its own index without retrieving the entity itself.
Related
In the description of the delete method in org.springframework.data.repository,CrudRepository interface it is written only that it deletes a given entity and that it accepts entity object itself.
It specifies nothing about entity's id needing to be unique.
However, this method works only when entity has an unique id. If the table may have multiple rows with the same id, this method fails when entity whose id is not unique is attempted to be deleted (at least in my case).
Now I understand that it is a very bad thing that an id is not unique in the table, but, in theory, it should work since this method accepts the entire entity as a parameter, and every entity (when all its columns are combined) in my table is unique. There are no two identical rows in a table since table has an unique constraint on a combination of all its columns.
Sure, there are other methods, like deleteById which would fail since they only accept id as a parameter and not the whole entity and since id is not unique, Spring does not know which entity to delete. Sure.
But why delete method fails when it should be able to distinguish between entities with the same id since it accepts the whole entitiy as a parameter (thus giving it access to all other columns of the entity and not just the id column)?
The requirement for the JPA id is that it is a primary key i.e. uniquely identifies the row. If you do not follow this, bad things will happen. You can have a composite primary key though, as it seems your row is identified by multiple columns. Look into #Embeddable/#EmbeddedId mappings for this purpose.
I know there are several ways that we can follow to update an existing entity. I will mention two ways below.I need to clarify if my opinion regarding these two methods is correct. The ultimate goal is to find the most optimal way that we can follow to update an entity by ensuring the primary key is valid before the updation. So feel free to state any other mechanism.
Method 1:
First get and proxy object related to updating entity with the aid of getOne() method.
Then setting the necessary fields to be updated by setters to that proxy object.
Use save method to update the entity.
In here I am using getOne() method before the save to ensure that I am updating an existence entity.Otherwise according to my knowledge if the entity's primary key is not an auto generated field any new primary key inserted to the save() method will create a new entity in the database. So by following the getOne() method I can have an EntityNotFound exception in the end of the save() method call if the inserted id is not an existing one.
So basically following this way I can omit a database hit which will trigger generally to find() the given id is existing before saving the entity.According to my opinion this is the most optimal way that we can follow to update a given entity by ensuring the given id is always existing .The problem is I didn't see this method in any tutorial or website before.This was implemented by self.So I need to know if there any disadvantage that we can have by following this mechanism over the method two.
try {
CustomerCategory customerCategory = customerCategoryRepository.getOne(customerCategoryRequestDto.getCode());
customerCategory.setStatus(customerCategoryRequestDto.getStatus());
CustomerCategory savedCustomerCategory = customerCategoryRepository.save(customerCategory);
CustomerCategoryResponseDto customerCategoryResponseDto = modelMapper.map(savedCustomerCategory, CustomerCategoryResponseDto.class);
return customerCategoryResponseDto;
} catch (EntityNotFoundException e) {
throw new EntityNotFoundException(ExceptionMessage.MSG_ENTITY_NOT_FOUND);
}
Method 2:
First see if the given id is existing in the database.Otherwise inform the end user that the given id is not existing in the database.
Then perform the updation. In here I am using an query to update the entity .But this can be easily achieve by setting the necessary fields to the found entity from the previous find() method call and by calling the save() method.
But the problem here I see is we need one additional query to ensure that the given id is valid.So I think this will definitely decrease the database performance.But in most websites and tutorials most of the authors follow this mechanism.I don't see any practical use case or need that we need this mechanism over the first one.
Optional<CustomerCategory> searchedCustomerCategory = customerCategoryRepository.findById(customerCategoryRequestDto.getCode());
if (!searchedDbpCustomerCategory.isPresent()) {
throw new EntityNotFoundException(ExceptionMessage.MSG_ENTITY_NOT_FOUND);
}
customerCategoryRepository.updateCustomerCategory(CustomerCategoryStatus.DELETED.toString(), customerCategoryRequestDto.getCode());
Actually, there are cases and cases.
When you only need a reference (e.g. to maintain a relationship), then use JpaRepository#getOne(ID id).
When you need to update the entity, use CrudRepository#findById(ID id).
But why?
Be aware that doing this:
Entity ref = repo.getOne(1l);
ref.setAttribute("value");
has only one difference from doing this:
Entity entity = repo.findById(1l);
entity.setAttribute("value");
The difference is that the load operation is Lazy in getOne and Eager in findById.
But there's no performance gain when you need to update a column from the entity.
When you call a setter or a getter on the reference, the persistence provider DOES hit the database in order the bring the data. This is what Lazy load mean - only load WHEN YOU NEED. By calling a setter, you do need the data, then the persistence provider will perform a select on the database.
So there's no real gain on using getOne over findById when your goal is to update the entity.
Also, be aware that getOne is deprecated in favor of getById, which does just the same.
I'm getting the "No data type for node" error when I run this query:
session.createQuery("select nextval( 'next_num_seq' )")
which I know means that I need to make it a property of a class, but I haven't been able to find a way to add a sequence to a class, only how to make a sequence generate IDs for a class.
Is there a way to include a sequence in a Hibernate mapping that isn't an ID generator?
As such, this question is valid, yet the path to the solution is headed in the wrong direction. Mapping a Sequence into a managed domain entity is not a "good" idea, as these are two separate concepts.
Sequences, such as the one you are trying to query from a PostgreSQL backend, are a central concept for the generation of unique IDs for primary key values of tuples or - from an ORM application perspective - Java objects. Thus, it is not clever to map their current state to a domain entity. Instead, one sets a single, particular value drawn from such a sequence - e.g. next_num_seq - into one particular object to be persisted in a relational database. Therefore, the related class of such an domain object is linked to this sequence by, for instance, dedicated ORM annotations (or via similar approaches).
In the JavaDoc of the Session interface we find the method createNativeQuery(String sql) which is inherited from the EntityManager interface, see also here.
It is described as follows:
Query createNativeQuery(java.lang.String sqlString)
Create an instance of Query for executing a native SQL statement, e.g., for update or delete.
Parameters:
sqlString - a native SQL query string
Returns:
the new query instance
Thus, you could modify your code to execute the native query against your PostgreSQL database as follows:
Query q = session.createNativeQuery("select nextval( 'next_num_seq' )");
This gives you the option to read the next valid sequence value as a long or Number instance for your programming purposes.
Note well: Be careful not to reuse this value multiple times (for several objects), as this might cause consistency trouble in your backend when used, for instance, in the context of separate threads.
Hope this helps.
Does Endpoints (Java) require a persistable class to have an id field?
Before endpoints, my JDO model itself did not have an id (primary key) field. Datastore has its own id field, and it generated a value upon inserting a new record. The model works, and I could insert records (with datastore successfully inserting and generating an id value).
I converted it to Endpoints (using Google Plugin for Eclipse), and made slight adjustments. The generated code is referencing an id that's not in the model. So I switched the parameter to another unique identifier (email address).
It compiles and deploys. But when I run API explorer, I can't insert. I'm getting "The class [class name] is not persistable."
But when I put an id field as primary key, now my inserts are asking for a value in id (which is not ideal for my situation).
Does endpoints require a class to have an id (unique identifier)? If so, is there a way to make appengine/datastore generate it for me? Thanks!
My assumption is yes, all such persistable classes need an id field. I add mine via Objetify with #Id. Here is the relevant documentation, also about autogenerating ids:
Entities must have have one field annotated with #Id. The actual
name of the field is irrelevant and can be renamed at any time, even
after data is persisted. This value (along with the kind 'Car')
becomes part of the Key which identifies an entity.
The #Id field can be of type Long, long, or String. If you use
Long and save an entity with a null id, a numeric value will be
generated for you using the standard GAE allocator for this kind. If
you use String or the primitive long type, values will never be
autogenerated.
If so, how?
I set the property as a type :List, which at runtime is just a List I believe anyways.
However, when adding a projection, I get the error that List is not supported
query.addProjection(new PropertyProjection("ListofLongs", List.class);
java.lang.IllegalArgumentException: Unsupported type: interface java.util.List
at com.google.appengine.repackaged.com.google.common.base.Preconditions.checkArgument(Preconditions.java:96)
at com.google.appengine.api.datastore.RawValue.asType(RawValue.java:58)
I've also tried passing in the class of the projection property as null than tried to cast RawValue into a List... no go. (and supported by the stacktrace on the other method due to RawValue.asType triggering the exception)
Is there some sort of undocumented limitation on projection queries against properties which are multivalue/collections?
GAE Datastore definitely supports projection queries on multi-valued properties.
But as docs state: you will not get back properties as a whole list, but only the list values that matches your query as separate Entities.
Rationale: projection query is a "fake" query that only uses index and recreates Entities returned from data in index. It never touches the actual Entities (that's the whole point - to be fast). Since every value in multi-valued property (collection, array) creates a separate index entry, the result of projection query is separate (fake) entities.
You have to use the class that the list contains. For example, if you have a list of strings property, you should pass String.class instead of List.class.
Then, as mentioned in a different answer, you will get a separate copy of the entity for each matching value in the list property.
https://cloud.google.com/appengine/docs/standard/java/datastore/projectionqueries#Java_Projections_and_multiple_valued_properties