Skip to content

ResultProcessor should back off for objects of the target type #2347

Closed
@ada-waffles

Description

@ada-waffles

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:

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.

These few lines in Data Commons jump out to me:

if (source instanceof Stream && method.isStreamQuery()) {
return (T) ((Stream<Object>) source).map(t -> type.isInstance(t) ? t : converter.convert(t));
}
if (ReactiveWrapperConverters.supports(source.getClass())) {
return (T) ReactiveWrapperConverters.map(source, converter::convert);
}

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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions