DATAJPA-1657 - @Procedure annotation doesn't work with cursors (NULL when using REF_CURSOR) and ResultSets that don't come from cursors #406
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Objectives:
Make it possible to return ResultSets from @procedure (not ref_cursor)
Make it possible to return cursors from @procedure
Remove the need to use NamedStoredProcedureQuery in your entities to return cursors. We should be able to return it with a simple @procedure annotation
Details:
I made a change so you can call procedures that return ResultSets and REF_CURSORs with the @procedure annotation. I tested this in this secondary project with MySQL, Postgres, Oracle and SQL Server databases
What did I do to make it work for any of those 4 databases? I added a new parameter called "refCursor" to express that the procedure in your database is using a REF_CURSOR. Here is an Example that would work with Oracle:
However if you're using MySQL or SQL Server, you don't need that parameter. This will work:
You can also return a single Entity from your procedure. For Example:
One more... You can also return a list of generic objects. For example:
Nothing else is needed to call your procedure with ResultSets. I made sure you don't have to use Hibernate's @Namedstoredprocedurequery in your entities, making them dirty. I also made sure you can still return regular output parameters. This still works:
Okay... So if I use MySQL, how do you know that my procedure is returning a ResultSet if I don't use the refCursor parameter? Here is the answer. I coded thinking first about databases that don't support REF_CURSOR but do support ResultSets, when I made sure that @procedure is finally able to return ResultSets, then I introduced the refCursor parameter.
How is the ResultSet parsed to the method return type, for example, an Entity? I check if your method's return type is an Entity (don't worry, i re-used some logic used by @query), if it is, then I pass the class type to StoredProcedureQuery when instantiating it, if not, then I omit the class type.
To return ResultSets the code calling the @procedure method needs to have an active transaction. I re-used some logic I found to check if no transaction is active, if not, then I throw InvalidDataAccessApiUsageException
I added 6 unit tests in spring-data-jpa covering the following cases:
However, spring-data-jpa uses HSQL for the integration tests and the HSQL Dialect for Hibernate doesn't support returning REF_CURSORs so there's no way to make integration tests for this. That's why I made another project to test this with MySQL, Oracle, Postgres and SQL Server dockerized databases. All the tests passed! ResultSets with or without REF_CURSORs are working perfectly.
I found this text somewhere in the spring-data-jpa code that justifies not supporting REF_CURSORs:
That is not true. Hibernate works with it (maybe the support was added after that comment) and all the major database dialects support it (I mentioned 4 databases that I tested with, maybe there are more). If you try to use REF_CURSOR with MySQL it will tell you the dialect doesn't support it, but as I said before, you don't need the cursor, just make a normal select inside your procedure and @procedure will pick the result. That's why i changed that text inside the code to:
I know I touched many classes, but I needed to refactor some code to make this change leaving the code easily maintainable. For example I made a new class (ProcedureParameter) to store the parameter properties because passing around the properties was not clean now that I also needed to pass the parameter type (if it’s out or refCursor). I couldn’t name it StoredProcedureParameter since there’s an annotation names like that already.