You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When using a class as a DTO for a @Query-based projection, it appears the DTO conversion is performed twice. This causes issues with DTOs that use/require custom converters, as the second conversion seems to ignore them.
Initializing...
Checking inserted entity...
Getting automatic DTO projection...
AutoDto constructor called
AutoDto constructor called
Getting custom DTO projection...
CustomDtoReadConverter called
WARNING: An illegal reflective access operation has occurred
//SNIP: Illegal reflection warning output
2021-03-04 14:56:36.757 ERROR 20369 --- [tor-tcp-epoll-1] r.n.channel.ChannelOperationsHandler : [id: 0xeb237d5e, L:/0:0:0:0:0:0:0:1%0:55338 - R:localhost/0:0:0:0:0:0:0:1:5432] Error was received while reading the incoming data. The connection will be closed.
java.lang.InstantiationError: com.example.demo.CustomDto
at com.example.demo.CustomDto_Instantiator_ddeq8t.newInstance(Unknown Source) ~[main/:na]
at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:238) ~[spring-data-commons-2.4.5.jar:2.4.5]
at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:87) ~[spring-data-commons-2.4.5.jar:2.4.5]
at org.springframework.data.relational.repository.query.DtoInstantiatingConverter.convert(DtoInstantiatingConverter.java:82) ~[spring-data-relational-2.1.5.jar:2.1.5]
at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:236) ~[spring-data-commons-2.4.5.jar:2.4.5]
at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.lambda$and$0(ResultProcessor.java:222) ~[spring-data-commons-2.4.5.jar:2.4.5]
at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:236) ~[spring-data-commons-2.4.5.jar:2.4.5]
//SNIP: Mostly Project Reactor stack frames
It appears that the DTO class is directly read from the result set, as expected, but the resulting object is then sent through the codepath used for entity-based projections, which does not short-circuit on the value already being the desired type. If the type can be instantiated via reflection, it simply converts the object into another instance of the same type (thus the two constructor calls in the output). If the type cannot be properly instantiated that way (for example, if it's an abstract class), it fails.
I'm not sure where would be best to do it, but I'm guessing the fix for this is as simple as short-circuiting this process when the desired type has been read directly.
The Stream-based codepath is guarding the converter on a typecheck, but the Reactive codepath is not. There might be a better place to fix this, but that jumped out to me.
Thanks so much for all the hard work on R2DBC!
The text was updated successfully, but these errors were encountered:
It appears that the DTO class is directly read from the result set
That's exactly what's happening here. While we don't create partially materialized entity objects, we lose column customizations and potential converters that are applied on the entity level before creating a projection on top of the entity.
The ResultProcessor however should not attempt to convert the DTO again which seems a bug.
mp911de
changed the title
@Query projection DTOs are converted twice
ResultProcessor should back off for objects of the target type
Apr 6, 2021
mp911de
transferred this issue from spring-projects/spring-data-r2dbc
Apr 6, 2021
I moved this ticket to Spring Data Commons. The issue arises from the fact that the declared return type is abstract so Spring Data cannot instantiate that type. However, we should back off if the result object is already an instance of the target type.
When using a class as a DTO for a
@Query
-based projection, it appears the DTO conversion is performed twice. This causes issues with DTOs that use/require custom converters, as the second conversion seems to ignore them.I've uploaded a reproduction project here.
When run, it produces the following output:
It appears that the DTO class is directly read from the result set, as expected, but the resulting object is then sent through the codepath used for entity-based projections, which does not short-circuit on the value already being the desired type. If the type can be instantiated via reflection, it simply converts the object into another instance of the same type (thus the two constructor calls in the output). If the type cannot be properly instantiated that way (for example, if it's an abstract class), it fails.
I'm not sure where would be best to do it, but I'm guessing the fix for this is as simple as short-circuiting this process when the desired type has been read directly.
These few lines in Data Commons jump out to me:
spring-data-commons/src/main/java/org/springframework/data/repository/query/ResultProcessor.java
Lines 162 to 168 in 7480fcf
The Stream-based codepath is guarding the converter on a typecheck, but the Reactive codepath is not. There might be a better place to fix this, but that jumped out to me.
Thanks so much for all the hard work on R2DBC!
The text was updated successfully, but these errors were encountered: