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?
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.
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:
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 themap(RowMapper<T>)method and pass the lambda directly to that: