Home > Mobile >  JOOQ problem with mapping (DataTypeException)
JOOQ problem with mapping (DataTypeException)

Time:11-22

I get this exception when mapping record to entity:

org.jooq.exception.DataTypeException: No Converter found for types java.util.UUID and java.util.List
at org.jooq.impl.Tools.converterOrFail(Tools.java:1208) ~[jooq-3.15.0.jar:na]
at org.jooq.impl.Tools.converterOrFail(Tools.java:1217) ~[jooq-3.15.0.jar:na]
at org.jooq.impl.AbstractRecord.get(AbstractRecord.java:351) ~[jooq-3.15.0.jar:na]
at org.jooq.impl.DefaultRecordMapper$ImmutablePOJOMapper.set(DefaultRecordMapper.java:1146) ~[jooq-3.15.0.jar:na]
at org.jooq.impl.DefaultRecordMapper$ImmutablePOJOMapper.mapNonnested(DefaultRecordMapper.java:1137) ~[jooq-3.15.0.jar:na]
at org.jooq.impl.DefaultRecordMapper$ImmutablePOJOMapper.map(DefaultRecordMapper.java:1124) ~[jooq-3.15.0.jar:na]
at org.jooq.impl.DefaultRecordMapper.map(DefaultRecordMapper.java:610) ~[jooq-3.15.0.jar:na]
at org.jooq.impl.DelayedRecordMapper.map(DelayedRecordMapper.java:69) ~[jooq-3.15.0.jar:na]
at org.jooq.RecordMapper.apply(RecordMapper.java:80) ~[jooq-3.15.0.jar:na]
at org.jooq.RecordMapper.apply(RecordMapper.java:65) ~[jooq-3.15.0.jar:na]
at java.base/java.util.stream.Collectors.lambda$mapping$13(Collectors.java:469) ~[na:na]
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169) ~[na:na]
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133) ~[na:na]
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1845) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) ~[na:na]
at org.jooq.impl.AbstractCursor.collect(AbstractCursor.java:78) ~[jooq-3.15.0.jar:na]
at org.jooq.impl.ResultQueryTrait.collect(ResultQueryTrait.java:358) ~[jooq-3.15.0.jar:na]
at org.jooq.impl.ResultQueryTrait.fetchInto(ResultQueryTrait.java:1423) ~[jooq-3.15.0.jar:na]
at com.example.jooqsample.posts.JOOQPostRepository.getAll(JOOQPostRepository.java:45) ~[classes/:na]
at com.example.jooqsample.posts.PostFacade.getAllPosts(PostFacade.java:34) ~[classes/:na]
at com.example.jooqsample.posts.rest.PostController.getAllPosts(PostController.java:27) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.23.jar:5.3.23]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.23.jar:5.3.23]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:670) ~[tomcat-embed-core-9.0.68.jar:4.0.FR]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.23.jar:5.3.23]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:779) ~[tomcat-embed-core-9.0.68.jar:4.0.FR]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.68.jar:9.0.68]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.23.jar:5.3.23]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.23.jar:5.3.23]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.23.jar:5.3.23]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

Entities:

@AllArgsConstructor(access = AccessLevel.PACKAGE)
@Getter
@ToString
@EqualsAndHashCode(of = "id")
class Post {

    private UUID id;

    private String content;

    private Instant createdAt;

    private List<Comment> comments;

PostDTO toDTO() {
    return PostDTO
            .builder()
            .id(this.id)
            .content(this.content)
            .comments(this.comments.stream().map(Comment::toDTO).toList())
            .build();
    }
 }

@AllArgsConstructor(access = AccessLevel.PACKAGE)
@Getter
@ToString
@EqualsAndHashCode(of = "id")
class Comment {
    private UUID id;

    private String content;

    private Instant createdAt;

    UUID postId;

CommentDTO toDTO() {
    return new CommentDTO(this.id, this.content);
  }
}

Method:

public List<Post> getAll() {
    return dslContext
            .select()
            .from(POSTS)
            .leftJoin(COMMENTS)
            .onKey()
            .fetchInto(Post.class);
}
  • JOOQ version: 3.15
  • Spring Boot version: 2.7.5
  • Java version: 17

CodePudding user response:

Why your approach doesn't work

You're creating a flat result set from your one-to-many relationship between POSTS and COMMENTS, and then, you're expecting jOOQ's DefaultRecordMapper (via the fetchInto(Class) method) to de-duplicate those flat results again automatically for you. But that's not how things work in jOOQ. While you know the relationship between POSTS and COMMENTS in your query, a flat jOOQ Result does not. There's no obvious way how to auto-deduplicate stuff. Imagine, you had several nested collections in Post, how would that even work?

Using MULTISET to nest collections

The most jOOQ and SQL idiomatic way to nest collections is to use MULTISET, along with ad-hoc converters, e.g. like this:

List<Post> result =
dslContext
    .select(
        POSTS.ID,
        POSTS.CONTENT,
        POSTS.CREATED_AT,
        multiset(
            select(COMMENTS.ID, COMMENTS.CONTENT, COMMENTS.CREATED_AT)
            .from(COMMENTS)
            .where(COMMENTS.POST_ID.eq(POSTS.ID))
        ).convertFrom(r -> r.map(Records.mapping(Comment::new))))
    .from(POSTS)
    .fetch(Records.mapping(Post::new));

CodePudding user response:

@Lukas Eder

I've written this method using multisetAgg. It works ok but i'm not sure it should look like this:

  List<Post> getAllPost() {
    List<Post> result =
            dslContext
                    .select(
                            POSTS.ID,
                            POSTS.CONTENT,
                            POSTS.CREATED_AT,
                            multisetAgg(
                                    COMMENTS.ID, COMMENTS.CONTENT, COMMENTS.CREATED_AT, COMMENTS.POST_ID

                            ).as("comments").convertFrom(r -> r.into(Comment.class))
                    )
                    .from(POSTS)
                    .leftJoin(COMMENTS).on(COMMENTS.POST_ID.eq(POSTS.ID))
                    .groupBy(POSTS.ID)
                    .fetchInto(Post.class);
    return result;
}
  • Related