Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When updating to version 3.38.1, an error message "No row mapper registered for map key class java.lang.String" was prompted. #2342

Closed
buzzxu opened this issue May 4, 2023 · 10 comments

Comments

@buzzxu
Copy link

buzzxu commented May 4, 2023

Hello, when updating to version 3.38.1, an exception occurred: org.jdbi.v3.core.mapper.NoSuchMapperException: No row mapper registered for map key class java.lang.String. This issue did not occur in version 3.37.1.

@stevenschlansker
Copy link
Member

Hi @buzzxu , can you share a bit more detail about how you set up Jdbi? We didn't run into this problem when upgrading.
Also, it would be helpful if you could test version 3.38.0, to tell specifically which version you start having troubles with. Thanks!

@buzzxu
Copy link
Author

buzzxu commented May 8, 2023

Sorry, I just saw your reply。
I registered many Arguments and ColumnMappers, and I'm not sure if they are the cause of the problem. Without changing the original configuration, version 3.37.1 works fine, but upgrading directly to version 3.38.1 results in an exception.

private void customizeJdbi(Jdbi jdbi){
        jdbi.registerColumnMapper(new GenericType<List<IdName>>() {
        }, (r, columnNumber, ctx) -> {
            String val = r.getString(columnNumber);
            return Strings.isNullOrEmpty(val) ? Collections.emptyList() : Jackson.json2List(val, IdName.class);
        });
        jdbi.registerColumnMapper(new GenericType<List<Map<String, Object>>>() {
        }, (r, columnNumber, ctx) -> {
            String val = r.getString(columnNumber);
            return Strings.isNullOrEmpty(val) ? null : Jackson.json2Object(val, Jackson.buildCollectionType(List.class, Map.class));
        });
        jdbi.registerColumnMapper(new GenericType<Map<String, Object>>() {
        }, (r, columnNumber, ctx) -> {
            String val = r.getString(columnNumber);
            return Strings.isNullOrEmpty(val) ? Collections.emptyMap() : Jackson.json2Map(val);
        });
        jdbi.registerColumnMapper(new GenericType<List<String>>() {
        }, (r, columnNumber, ctx) -> {
            String val = r.getString(columnNumber);
            return Strings.isNullOrEmpty(val) ? Collections.emptyList() : Jackson.isJSON(val) ? Jackson.json2Object(val, new TypeReference<List<String>>() {
            }) : Splitter.on(",").splitToList(val);
        });
        jdbi.registerColumnMapper(new GenericType<List<Integer>>() {
        }, (r, columnNumber, ctx) -> {
            String val = r.getString(columnNumber);
            return Strings.isNullOrEmpty(val) ? Collections.emptyList() :  Jackson.isJSON(val) ? Jackson.json2Object(val, new TypeReference<List<Integer>>() {
            }) : Splitter.on(",").splitToStream(val).map(Integer::valueOf).toList();
        });
        jdbi.registerColumnMapper(new GenericType<List<Long>>() {
        }, (r, columnNumber, ctx) -> {
            String val = r.getString(columnNumber);
            return Strings.isNullOrEmpty(val) ? Collections.emptyList() : Jackson.isJSON(val) ? Jackson.json2Object(val, new TypeReference<List<Long>>() {
            }): Splitter.on(",").splitToStream(val).map(Long::valueOf).toList();
        });

        jdbi.registerColumnMapper(new GenericType<String[]>(){}, (r, columnNumber, ctx) -> {
            String val = r.getString(columnNumber);
            return Strings.isNullOrEmpty(val) ? null : Jackson.isJSON(val) ? Jackson.json2Object(val,String[].class) :  Splitter.on(",").splitToStream(val).toArray(String[]::new );
        });
        jdbi.registerColumnMapper(new GenericType<Integer[]>(){},(r, columnNumber, ctx) -> {
            String val = r.getString(columnNumber);
            return Strings.isNullOrEmpty(val) ? null : Jackson.isJSON(val) ? Jackson.json2Object(val,Integer[].class) :Splitter.on(",").splitToStream(val).map(Integer::valueOf).toArray(Integer[]::new);
        });
        jdbi.registerColumnMapper(new GenericType<Long[]>(){},(r, columnNumber, ctx) -> {
            String val = r.getString(columnNumber);
            return Strings.isNullOrEmpty(val) ? null : Jackson.isJSON(val) ? Jackson.json2Object(val,Long[].class) :Splitter.on(",").splitToStream(val).map(Long::valueOf).toArray(Long[]::new);
        });

        jdbi.registerArgument(new AbstractArgumentFactory<List<IdName>>(Types.VARCHAR) {
            @Override
            protected Argument build(List<IdName> val, ConfigRegistry config) {
                return (position, statement, ctx) -> statement.setString(position, Jackson.object2Json(val));
            }
        });
        jdbi.registerArgument(new AbstractArgumentFactory<List<Map<String, Object>>>(Types.VARCHAR) {
            @Override
            protected Argument build(List<Map<String, Object>> val, ConfigRegistry config) {
                return (position, statement, ctx) -> statement.setString(position, Jackson.object2Json(val));
            }
        });
        jdbi.registerArgument(new AbstractArgumentFactory<Map<String, Object>>(Types.VARCHAR) {
            @Override
            protected Argument build(Map<String, Object> val, ConfigRegistry config) {
                return (position, statement, ctx) -> statement.setString(position, Jackson.object2Json(val));
            }
        });
        jdbi.registerArgument(new AbstractArgumentFactory<int[]>(Types.VARCHAR) {
            @Override
            protected Argument build(int[] value, ConfigRegistry config) {
                return (position, statement, ctx) -> statement.setString(position, Jackson.object2Json(value));
            }
        });
        jdbi.registerArgument(new AbstractArgumentFactory<Integer[]>(Types.VARCHAR) {
            @Override
            protected Argument build(Integer[] value, ConfigRegistry config) {
                return (position, statement, ctx) -> statement.setString(position,Jackson.object2Json(value));
            }
        });
        jdbi.registerArgument(new AbstractArgumentFactory<Long[]>(Types.VARCHAR) {
            @Override
            protected Argument build(Long[] value, ConfigRegistry config) {
                return (position, statement, ctx) -> statement.setString(position,Jackson.object2Json(value));
            }
        });
        jdbi.registerArgument(new AbstractArgumentFactory<String[]>(Types.VARCHAR) {
            @Override
            protected Argument build(String[] value, ConfigRegistry config) {
                return (position, statement, ctx) -> statement.setString(position, Jackson.object2Json(value));
            }
        });
        jdbi.registerArgument(new AbstractArgumentFactory<List<Integer>>(Types.VARCHAR) {
            @Override
            protected Argument build(List<Integer> value, ConfigRegistry config) {
                return (position, statement, ctx) -> statement.setString(position,Jackson.object2Json(value));
            }
        });
        jdbi.registerArgument(new AbstractArgumentFactory<List<Long>>(Types.VARCHAR) {
            @Override
            protected Argument build(List<Long> value, ConfigRegistry config) {
                return (position, statement, ctx) -> statement.setString(position, Jackson.object2Json(value));
            }
        });

        jdbi.registerArgument(new AbstractArgumentFactory<List<String>>(Types.VARCHAR) {
            @Override
            protected Argument build(List<String> value, ConfigRegistry config) {
                return (position, statement, ctx) -> statement.setString(position,Jackson.object2Json(value));
            }
        });
    }

I'll keep testing.Thank you very much for your reply.

@buzzxu
Copy link
Author

buzzxu commented May 8, 2023

This is my code in the DAO:

default List<ProductMini> findAllOrderBy(int limit, OrderBy orderBy, Map<String, Object> params){
...
}

This is the exception information:

org.jdbi.v3.core.mapper.NoSuchMapperException: No row mapper registered for map key class java.lang.String
	at org.jdbi.v3.core.mapper.MapEntryMapper.lambda$getKeyMapper$1(MapEntryMapper.java:72) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at java.util.Optional.orElseThrow(Optional.java:403) ~[?:?]
	at org.jdbi.v3.core.mapper.MapEntryMapper.getKeyMapper(MapEntryMapper.java:72) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at org.jdbi.v3.core.mapper.MapEntryMapper.lambda$factory$0(MapEntryMapper.java:58) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at org.jdbi.v3.core.mapper.RowMappers.lambda$findFor$0(RowMappers.java:171) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:273) ~[?:?]
	at java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:1002) ~[?:?]
	at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129) ~[?:?]
	at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527) ~[?:?]
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513) ~[?:?]
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[?:?]
	at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150) ~[?:?]
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[?:?]
	at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647) ~[?:?]
	at org.jdbi.v3.core.mapper.RowMappers.findFor(RowMappers.java:172) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at org.jdbi.v3.core.mapper.Mappers.findFor(Mappers.java:107) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at java.util.Optional.ifPresent(Optional.java:178) ~[?:?]
	at org.jdbi.v3.sqlobject.statement.internal.ResultReturner.warm(ResultReturner.java:121) ~[jdbi3-sqlobject-3.38.2.jar:3.38.2]
	at org.jdbi.v3.sqlobject.statement.internal.ResultReturner$CollectedResultReturner.warm(ResultReturner.java:308) ~[jdbi3-sqlobject-3.38.2.jar:3.38.2]
	at org.jdbi.v3.sqlobject.statement.internal.SqlQueryHandler.warm(SqlQueryHandler.java:48) ~[jdbi3-sqlobject-3.38.2.jar:3.38.2]
	at org.jdbi.v3.core.extension.ExtensionMetadata$ExtensionHandlerInvoker.<init>(ExtensionMetadata.java:307) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at org.jdbi.v3.core.extension.ExtensionMetadata.createExtensionHandlerInvoker(ExtensionMetadata.java:123) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at org.jdbi.v3.core.extension.ExtensionFactoryDelegate.lambda$attach$5(ExtensionFactoryDelegate.java:141) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at java.util.HashMap$KeySet.forEach(HashMap.java:1007) ~[?:?]
	at java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1092) ~[?:?]
	at org.jdbi.v3.core.extension.ExtensionFactoryDelegate.attach(ExtensionFactoryDelegate.java:140) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at org.jdbi.v3.core.extension.Extensions.lambda$findFor$0(Extensions.java:170) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at java.util.Optional.map(Optional.java:260) ~[?:?]
	at org.jdbi.v3.core.extension.Extensions.findFor(Extensions.java:170) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at org.jdbi.v3.core.Jdbi.callWithExtension(Jdbi.java:503) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at org.jdbi.v3.core.Jdbi.withExtension(Jdbi.java:493) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at org.jdbi.v3.core.internal.OnDemandExtensions.lambda$createProxy$3(OnDemandExtensions.java:79) ~[jdbi3-core-3.38.2.jar:3.38.2]
	at jdk.proxy2.$Proxy203.findAllOrderBy(Unknown Source) ~[?:?]
	at com.yuanmai.mall.product.services.DefaultProductSearchService.findAllOrderBy(DefaultProductSearchService.java:208) ~[yuanmai-mall-product-23.0.jar:23.0]
	at com.yuanmai.b2c.product.search.MySQLProductSearchApi.findAllOrderBy(MySQLProductSearchApi.java:115) ~[yuanmai-b2c-product-23.0.jar:23.0]
	at com.mxbc.xwjd.controllers.IndexController.hotsProduct(IndexController.java:65) ~[classes/:?]
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]
	at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
	at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.27.jar:5.3.27]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.27.jar:5.3.27]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.27.jar:5.3.27]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.27.jar:5.3.27]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.27.jar:5.3.27]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.27.jar:5.3.27]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072) ~[spring-webmvc-5.3.27.jar:5.3.27]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965) ~[spring-webmvc-5.3.27.jar:5.3.27]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.27.jar:5.3.27]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.27.jar:5.3.27]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:503) ~[jboss-servlet-api_4.0_spec-2.0.0.Final.jar:2.0.0.Final]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.27.jar:5.3.27]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:590) ~[jboss-servlet-api_4.0_spec-2.0.0.Final.jar:2.0.0.Final]
	at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at com.yuanmai.servlets.filters.XssFilter.doFilter(XssFilter.java:54) ~[yuanmai-servlets-23.0.jar:23.0]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:458) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:373) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90) ~[shiro-core-1.11.0.jar:1.11.0]
	at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83) ~[shiro-core-1.11.0.jar:1.11.0]
	at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387) ~[shiro-core-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:370) ~[shiro-web-1.11.0.jar:1.11.0]
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154) ~[shiro-web-1.11.0.jar:1.11.0]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.27.jar:5.3.27]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.27.jar:5.3.27]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) ~[spring-boot-actuator-2.7.11.jar:2.7.11]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.27.jar:5.3.27]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]
	at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) ~[undertow-servlet-2.2.24.Final.jar:2.2.24.Final]

org.jdbi.v3.core.mapper.MapEntryMapper.getKeyMapper
image

@stevenschlansker
Copy link
Member

Thanks for the additional information. Is it easy for you to test whether 3.38.0 works with your application? There were significant changes between the releases, and it would help us isolate which code causes the regression you see.

@hgschmie
Copy link
Contributor

(the stack trace is actually from 3.38.2)

@hgschmie
Copy link
Contributor

So I can reproduce this (with a test case) and for methods that return a Map<String, Object> this behavior existed at least back to 3.34.0. I would be curious in which version that has worked before.

The stack trace and the method you listed confuse me. The method returns a List<ProductMini>. So there is no Map anywhere in here. But the stack trace goes straight into MapEntryMapper (correct) and then tries to find a RowMapper for String (which is questionable and most likely a bug). But it has worked that way for ages. This code is basically unchanged since 2017.

Are you sure that the code you showed us may not be a "working" version and there is another version where the code does not return a List<ProductMini> but maybe a Map<String, ProductMini> ?

@hgschmie
Copy link
Contributor

so here is what I can reproduce. This is the test code:

@Test
public void testRoundTripDeclarative() throws Exception {
    Map<String, JsonBean> result = pgExtension.getJdbi().withExtension(MapDao.class, dao -> dao.getValues(1));

    assertThat(result).isNotNull().hasSize(1);

    JsonBean bean = result.get("test");
    assertThat(bean).isNotNull().extracting("id").isEqualTo(1);
    assertThat(bean).extracting("key").isEqualTo("test");
    assertThat(bean).extracting("value").isEqualTo(content);
}

public static final class JsonBean {
    private final int id;
    private final String key;
    private final Map<String, Object> value;

    public JsonBean(int id, String key, Map<String, Object> value) {
        this.id = id;
        this.key = key;
        this.value = value;
    }

    public int getId() {
        return id;
    }

    public String getKey() {
        return key;
    }

    public Map<String, Object> getValue() {
        return value;
    }
}

public interface MapDao {
    @SqlQuery("SELECT * FROM json_data WHERE id = :id")
    Map<String, JsonBean> getValues(int id);
}

I run this code, I get this:

org.jdbi.v3.core.mapper.NoSuchMapperException: No row mapper registered for map key class java.lang.String

	at org.jdbi.v3.core.mapper.MapEntryMapper.lambda$getKeyMapper$1(MapEntryMapper.java:72)
	at java.base/java.util.Optional.orElseThrow(Optional.java:403)
	at org.jdbi.v3.core.mapper.MapEntryMapper.getKeyMapper(MapEntryMapper.java:72)
	at org.jdbi.v3.core.mapper.MapEntryMapper.lambda$factory$0(MapEntryMapper.java:58)
	at org.jdbi.v3.core.mapper.RowMappers.lambda$findFor$0(RowMappers.java:171)
	at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:273)
	at java.base/java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:1002)
	at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129)
	at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647)
	at org.jdbi.v3.core.mapper.RowMappers.findFor(RowMappers.java:172)
	at org.jdbi.v3.core.mapper.Mappers.findFor(Mappers.java:107)
	at java.base/java.util.Optional.ifPresent(Optional.java:178)
	at org.jdbi.v3.sqlobject.statement.internal.ResultReturner.warm(ResultReturner.java:121)
	at org.jdbi.v3.sqlobject.statement.internal.ResultReturner$CollectedResultReturner.warm(ResultReturner.java:308)
	at org.jdbi.v3.sqlobject.statement.internal.SqlQueryHandler.warm(SqlQueryHandler.java:48)
	at org.jdbi.v3.core.extension.ExtensionMetadata$ExtensionHandlerInvoker.<init>(ExtensionMetadata.java:307)
	at org.jdbi.v3.core.extension.ExtensionMetadata.createExtensionHandlerInvoker(ExtensionMetadata.java:123)
	at org.jdbi.v3.core.extension.ExtensionFactoryDelegate.lambda$attach$5(ExtensionFactoryDelegate.java:141)
	at java.base/java.util.HashMap$KeySet.forEach(HashMap.java:1008)
	at java.base/java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1092)
	at org.jdbi.v3.core.extension.ExtensionFactoryDelegate.attach(ExtensionFactoryDelegate.java:140)
	at org.jdbi.v3.core.extension.Extensions.lambda$findFor$0(Extensions.java:170)
	at java.base/java.util.Optional.map(Optional.java:260)
	at org.jdbi.v3.core.extension.Extensions.findFor(Extensions.java:170)
	at org.jdbi.v3.core.Jdbi.callWithExtension(Jdbi.java:503)
	at org.jdbi.v3.core.Jdbi.withExtension(Jdbi.java:493)
	at org.jdbi.v3.jackson2.TestJsonMap.testRoundTripDeclarative(TestJsonMap.java:119)

This is how it should be. Just the error message is bad. What it should say is "Jdbi does not know what result column you would like to use for your map key". You can fix this by adding the @KeyColumn annotation to your DAO class:

public interface MapDao {
    @SqlQuery("SELECT * FROM json_data WHERE id = :id")
    @KeyColumn("key")
    Map<String, JsonBean> getValues(int id);
}

makes the test pass. Now, Jdbi knows that the key for your map should be taken from the key column and the value is mapping the whole row onto a single bean. Then create a Map.Entry from the key and the value and put everything into a map.

@hgschmie
Copy link
Contributor

ok, so I think while writing tests, I stumbled onto the actual problem. :-)

"Is there another method in the DAO that returns a Map? That method may not even be in use.

The 3.38.0 code changed the warming up of SQL Object methods to be more aggressive (basically at metadata creation time). So if there is another method in that DAO, it would be warmed as well as soon as the DAO is loaded. And it seems that you have a method (maybe one that you do not use at all) that has the problem described above.

So, yes, it actually is a behavior change (I am hesitant to call it a bug because it is a problem with your code) that now surfaces this problem much earlier (at init time, not at call time).

@hgschmie
Copy link
Contributor

The next release restores the old behavior.

@buzzxu
Copy link
Author

buzzxu commented Jun 9, 2023

I'm sorry for the late reply to your message. The issue has been resolved, and thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

3 participants