0

When running my application (Spring Boot 2.5.7) - after updating to spring-data-r2dbc 1.3.7, I get the NoSuchMethodError RowMetadata.getColumnMetadatas().

(I have also tried this with 1.4.0 - 1.3.7 is where the problem first surfaces.)

In this spring-data-r2dbc release, a change has been made to move off of getColumnNames (See https://github.com/spring-projects/spring-data-r2dbc/issues/683) to getColumnMetaDatas().

What is odd is that I also have the dependencies

  • r2dbc-spi - 0.9.0.RELEASE
  • org.postgresql:r2dbc-postgresql - 0.9.0.RC1

The 0.9.0 RC1 of r2dbc-postgresql fulfills the spi contract. So, it seems that everything should be in place -

The correct version of spring-data-r2dbc (1.3.7) invokes getColumnMetaDatas():

public static boolean containsColumn(RowMetadata metadata, String name) {

        for (ColumnMetadata columnMetadata : metadata.getColumnMetadatas()) {
            if (name.equalsIgnoreCase(columnMetadata.getName())) {
                return true;
            }
        }

        return false;
    }

which is a method of the 0.0.9.RELEASE r2dbc-spi interface RowMetaData, which is fulfilled by the 0.9.0.RC1 version of r2dbc-postgresql PostgresqlRowMetadata.

I have looked at my maven dependency tree and see no competing versions of any of these.

I could assume that the binding of the implementation to the spi is bad, but I got far enough to make the call to the database and then try to extract the data, so that doesn't seem right. So, it appears that something else is off.

I added the command line option -verbose:class to see what classes/jars are being loaded and things look correct from there as well:

[30.756s][info][class,load] io.r2dbc.postgresql.message.frontend.CompositeFrontendMessage$$Lambda$2382/0x0000000800ee3840 source: io.r2dbc.postgresql.message.frontend.CompositeFrontendMessage
[30.756s][info][class,load] io.r2dbc.postgresql.message.frontend.Bind$$Lambda$2383/0x0000000800ee3c40 source: io.r2dbc.postgresql.message.frontend.Bind
[30.757s][info][class,load] io.r2dbc.postgresql.message.frontend.Bind$$Lambda$2384/0x0000000800ee4040 source: io.r2dbc.postgresql.message.frontend.Bind
[30.757s][info][class,load] io.r2dbc.postgresql.message.frontend.Bind$$Lambda$2385/0x0000000800ee4440 source: io.r2dbc.postgresql.message.frontend.Bind
[30.761s][info][class,load] io.r2dbc.postgresql.message.backend.ParseComplete source: file:/<removed>/.m2/repository/org/postgresql/r2dbc-postgresql/0.9.0.RC1/r2dbc-postgresql-0.9.0.RC1.jar
[30.762s][info][class,load] org.springframework.data.r2dbc.convert.RowMetadataUtils source: file:/<removed>/.m2/repository/org/springframework/data/spring-data-r2dbc/1.3.7/spring-data-r2dbc-1.3.7.jar

Stack Trace:

java.lang.NoSuchMethodError: 
io.r2dbc.spi.RowMetadata.getColumnMetadatas()Ljava/lang/Iterable;
  at org.springframework.data.r2dbc.convert.RowMetadataUtils.containsColumn(RowMetadataUtils.java:38)
  at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.extractGeneratedIdentifier(MappingR2dbcConverter.java:646)
... 2 frames excluded
  at io.r2dbc.postgresql.PostgresqlResult.lambda$map$2(PostgresqlResult.java:123)
  at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(...

I've also

  1. Updated my JDK to the latest version of Corretto 1.8
  2. Compiled spring-data-r2dbc 1.4.0 with that version and used it
  3. Inserted code in RowMetaDataUtils to reflect and log the methods found in the metadata parameter of containsColumn(...). The results include public java.util.List<io.r2dbc.postgresql.PostgresqlColumnMetadata> io.r2dbc.postgresql.PostgresqlRowMetadata.getColumnMetadatas(), which makes this even more perplexing.

I'll try and get a test that duplicates this - but hope that somebody has an idea for me to try to fix or debug with.

Alan Moffet
  • 139
  • 3
  • 13

1 Answers1

0

I've finally had time to come back to this. Looking at the byte code for RowMetadataUtils, we see

public static boolean containsColumn(io.r2dbc.spi.RowMetadata, java.lang.String);
    descriptor: (Lio/r2dbc/spi/RowMetadata;Ljava/lang/String;)Z
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=2
         0: aload_0
         1: invokeinterface #2,  1            // InterfaceMethod io/r2dbc/spi/RowMetadata.getColumnMetadatas:()Ljava/lang/Iterable;
         6: invokeinterface #3,  1            // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
        11: astore_2

In this we see that we are looking for an interface method RowMetadata.getColumnMetadatas that returns an iterator.
This is the signature of the 0.8.5 release of r2dbc-spi. In later versions, this has been changed to return a list which would be resolved to the appropriate method in r2dbc-postgresql. So, the 1.4.0 release of spring-data-r2dbc should be compiled against the 0.0.9.RELEASE version of r2dbc-spi.

Alan Moffet
  • 139
  • 3
  • 13
  • Resolved by reflectively invoking `RowMetadata.getColumnMetadatas`: https://github.com/spring-projects/spring-data-r2dbc/issues/699 – Alan Moffet Jan 03 '22 at 15:43