First, one house keeping item before we begin...
I see that you referenced Pivotal GemFire 9.7
in your GemFire documentation reference above, but then reference Spring Data GemFire (SDG) 1.3.3.RELEASE
reference docs. I certainly hope you are not trying to use SDG 1.3.3.RELEASE
with Pivotal GemFire 9.7
. SDG 1.3.3 is quite date, based on Pivotal GemFire 7.0.1, and is no longer supported.
I suspect you are not using SDG 1.3.x
, therefore you should probably always reference the latest documentation, available here, and specifically, here. Even a Google Search reveals the latest docs.
Also, you can refer to the Spring Data for Pivotal GemFire Version Compatibility Matrix for more details. Anyway...
So, I wrote a test class to better understand your UC.
In my test, I modeled a Person class with a queryable age
field/property. I have a PersonRepository for the Person
type, and I wrote a very similar query to your example above, to query a person by age, in an usual as well as null-safe manner.
The thing is, your desire to want to protect against the absence of results by returning null
or 0
is ambiguous in the case of Repositories.
For 1, if you had returned multiple results (e.g. as in SELECT a.num FROM /SomeRegion a
; i.e. without a predicate), and the results were not ordered, then you would have no way to know which value was null
for which key unless you also returned the key in the result set.
Of course, that is not the case here and you did qualify the query by ID using a predicate (i.e. ... WHERE a.id = $1
). However, in this case, it is not clear when looking at the query (i.e. SELECT a.num FROM ...
), whether there was no result for the given ID (i.e. Key) or that simply num was null
. You really don't know.
My test goes on to illustrate this point.
I have 2 people ["Jon Doe", "Jane Doe"]
, here. Jon has a declared age and Jane does not. Of course, both people exist in the cache (i.e. "People" Region).
When I query for Jon and assert his age, I get the expected result.
Alternatively, when I query for Jane and assert her age, I can get 1 of two values, either null
or 0
, as expected here (currently 0
because I am using the null-safe query). If I change the query to the usual query, then the return value for Jane's age
would in fact be null
, and I could assert as such.
However, when we get down to querying for a non-existing person, the behavior of GemFire is clear; there are no results. I illustrated this 2 different ways (other than using a Repository), using GemFire's QueryService
API directly, and then again using SDG's convenient GemfireTemplate
class, which simply wraps the GemFire Query API (it is also the method used by the Repository under the hood; really, the value Repositories add is the mapping/conversion capabilities).
As you can see, I must handle the presence of no results, for example.
In the the case of the Repository, because you have used a custom query, it is not immediately apparent to the Repository infrastructure, which part (i.e. intermediate result of the query) was null
.
What if you had SELECT person.address.city.name FROM ...
?
Is person null
, address null
or city null
? How should this be handled consistently? The person might exit but the address might be null
, which might warrant different behavior than if either person or city were null
.
In fact, the Repository abstraction does handle null
return values, as can be seen here.
So, where do we go from here?
We have several options:
You could perform and existence check, first...
return personRepository.existsById(bobDoe.getName())
? personRepository.getAge(bobDoe.getName())
: 0;
You could handle this outside of a Repository in a DAO (perhaps adapting/decorating the Repository and delegating for simple query cases, rather than complex queries that involve using the @Query
annotation, which should be used sparingly).
@Repository
class PersonDao {
@Autowired
private PersonRepository personRepository;
Person findById(String name) {
return this.personRepository.findById(name).orElse(null);
}
Integer getAge(String name) {
// Either use the approach in #1, or do https://github.com/jxblum/contacts-application/blob/master/tests-example/src/test/java/example/tests/spring/data/gemfire/NullValueReturningRepositoryQueryMethodIntegrationTests.java#L143-L151.
}
...
}
Still
Another option would be for us to provide additional support in SD, something along the lines of...
@Nullable Integer getAge(String name);
Or perhaps...
Optional<Integer> getAge(String name);
Still this approach does not solve the ambiguity problem and will require more thought.
What would be really nice is if OQL handle a Elvis operator, something like (in my more complex example above)...
SELECT person?.address?.city?.name FROM /People ...
Anyway, I hope this gives you some ideas. I will need to think on #3 above more before moving forward.
If you have additional questions/feedback/ideas, please provide them in the comments or feel free to file a JIRA ticket.
Thanks!