In Hibernate 6, Steve wanted to make a breaking change where a query like this:
{code:sql}from Author a join a.books b where b.title = :title{code}
would be interpreted as returning a single {{Author}} entity, rather than returning an array with an {{Author}} and a {{Book}}.
A priori, this is a completely legit and natural interpretation, but I was _very_ worried about breaking the semantics of existing code by changing something that has had a stable interpretation as far back as Hibernate 1.0.
After some discussion, we settled on what I think was an excellent compromise: to use the result type as a hint to how such queries should be interpreted. So:
{code:java}s.createSelectionQuery("from Author a join a.books b where b.title = :title", Author.class){code}
would have the interpretation Steve prefers, whereas:
{code:java}s.createSelectionQuery("from Author a join a.books b where b.title = :title", Object[].class){code}
would have the same interpretation as always.
I’m completely happy with this, because it doesn’t break old code, and simply turns a case which was previously an _error_ into something useful. Perfect. We released this change in Hibernate 6. I’ve continued along a similar line of thought in [https://hibernate.atlassian.net/browse/HHH-16710|https://hibernate.atlassian.net/browse/HHH-16710|smart-link].
But, while working on 16710, I noticed a hole in what has actually been implemented. The issue is that we have an overload of {{createSelectionQuery()}} which does not accept a result class. (I’m not sure why we even need this.) There’s also a similar legacy {{createQuery()}} method required for JPA compatibility that is marked deprecated . (That’s the method old code is all using.)
And in this case, the current implementation does not the first thing, not the second thing, but a secret (i.e. undocumented) third thing in certain more complicated cases. Consider:
{code:java}s.createSelectionQuery("from Author a join a.books b, Author c"){code}
This is currently interpreted to return instances of {{Object[]}}containing two {{Author}}s. But if I were to _explicitly_ pass the result type {{Object[].class}} like so:
{code:java}s.createSelectionQuery("from Author a join a.books b, Author c", Object[].class){code}
Then I would get instances of {{Object[]}} with _three_ elements. That’s pretty inconsistent!
Furthermore, if I write the query with a {{cross join}} instead of a comma:
{code:java}s.createSelectionQuery("from Author a join a.books b cross join Author c"){code}
then now I get a single {{Author}} as the result. So {{cross join}} and {{,}} are suddenly pretty different things.
Now, this behavior is at best buglike, and since it has no tests asserting that it was _intentional,_ I’m going to go ahead and call it a bug.
Frankly, in light of all this, I would prefer that we threw an actual exception in the case of a query with no {{select}} clause, multiple identification variables, and no explicit result type. I don’t think there even is any “natural” interpretation there. So I would like to have some reasonable way to simply “deprecate” this usage. (But I can’t think of anything better than a damn {{WARN}}.)
Anyway, the only straightforward “fix” that I can see here that doesn’t make things worse is to revert to the behavior of H5 when there’s no explicit result class. That breaks no tests, ensures that old code continues to work, and eliminates the “secret third thing” interpretation that’s undocumented and quite buglike. |
|