Jdbi: When using lambda: "Must use a concretely typed RowMapper here"

Created on 9 Apr 2018  路  3Comments  路  Source: jdbi/jdbi

First of all: Thanks for this awesome library! We are slowly migrating from Hibernate to jdbi and are very satisfied with it so far ;)

Suppose I have the following code:

getHandle().createQuery("SELECT ...")
    .bind("id", id)
    .registerRowMapper((RowMapper<CatalogueJdbi>) (rs, ctx) -> {
        final CatalogueJdbi catalogue = FieldMapper.of(CatalogueJdbi.class, "c").map(rs, ctx);
        if (rs.getObject("mid") != null) {
            catalogue.setImage(FieldMapper.of(MediaFileJdbi.class, "m").map(rs, ctx));
        }

        return catalogue;
    })
    .mapTo(CatalogueJdbi.class)
    .collect(Collectors.toSet());

When registering the RowMapper as shown I get

java.lang.UnsupportedOperationException: Must use a concretely typed RowMapper here
    at org.jdbi.v3.core.mapper.InferredRowMapperFactory.lambda$new$0(InferredRowMapperFactory.java:38)
    at java.base/java.util.Optional.orElseThrow(Optional.java:385)
    at org.jdbi.v3.core.mapper.InferredRowMapperFactory.<init>(InferredRowMapperFactory.java:38)
    at org.jdbi.v3.core.mapper.RowMappers.register(RowMappers.java:63)
    at org.jdbi.v3.core.config.Configurable.lambda$registerRowMapper$16(Configurable.java:248)
    at org.jdbi.v3.core.config.Configurable.configure(Configurable.java:74)
    at org.jdbi.v3.core.config.Configurable.registerRowMapper(Configurable.java:248)
    at ...

but when using

.registerRowMapper(new RowMapper<CatalogueJdbi>() {
                    @Override
                    public CatalogueJdbi map(final ResultSet rs, final StatementContext ctx) throws SQLException {
                        final CatalogueJdbi catalogue = FieldMapper.of(CatalogueJdbi.class, "c").map(rs, ctx);
                        if (rs.getObject("mid") != null) {
                            catalogue.setImage(FieldMapper.of(MediaFileJdbi.class, "m").map(rs, ctx));
                        }

                        return catalogue;
                    }
                })

instead everything works fine.

I thought lambdas were only syntactic sugar and the compiler would expand the code anyway?

question

Most helpful comment

Due to generic erasure, the <CatalogueJdbi> parameter is lost at runtime, so Jdbi doesn't know what type that mapper maps to.

There is an alternative method you can use, where you pass the type as a separate parameter:

.registerRowMapper(CatalogueJdbi.class, (rs, ctx) -> { ... })

One thing to note: registering a row mapper and then calling mapTo(type) is wasted effort if that mapper is not being reused anywhere else. You could just call the map(RowMapper<T>) method and pass the lambda directly to that:

.map((rs, ctx) -> { ... })

All 3 comments

Due to generic erasure, the <CatalogueJdbi> parameter is lost at runtime, so Jdbi doesn't know what type that mapper maps to.

There is an alternative method you can use, where you pass the type as a separate parameter:

.registerRowMapper(CatalogueJdbi.class, (rs, ctx) -> { ... })

One thing to note: registering a row mapper and then calling mapTo(type) is wasted effort if that mapper is not being reused anywhere else. You could just call the map(RowMapper<T>) method and pass the lambda directly to that:

.map((rs, ctx) -> { ... })

I knew I was doing something wrong :) Thanks a lot!

As a follow up: when you create an anonymous inner class, you _are_ creating a concretely typed mapper.

What we mean by concretely typed is that Jdbi can get the mapper's class and reflectively discover the generic parameter T for RowMapper<T>. Whether you do this via a full-fledged class like class FooMapper implements RowMapper<Foo> { ... } or via an anonymous inner class e.g. new RowMapper<Foo>() { ... }, it looks the same to Jdbi.

Was this page helpful?
0 / 5 - 0 ratings