Skip to main content

Querying DTOs

Previously we introduced using object fetchers to flexibly control the shape of queried data structures.

Now we introduce an equivalent capability: querying DTO objects.

Jimmer provides a DTO language

tip

This language is essentially another way to express object fetchers.

Using this language, developers can quickly define various data structure shapes with an entity type as the aggregate root. The compiler will generate corresponding Java/Kotlin DTO classes for each defined shape. Each DTO type contains mutual conversion logic with the original dynamic type, and an object fetcher matching its own shape.

In some cases, after the server queries data in a certain shape, it is not meant to be returned as an HTTP response, but rather used internally to drive subsequent complex business logic. This is an ideal scenario for this approach.

caution

Note that if the server queries data in a certain shape not for its own use, but directly as the HTTP response, it is more recommended to directly return the dynamic entity object and use the solutions in Generate Client Code to automatically generate client code with a great developer experience.

Defining DTO Shapes

This article focuses on explaining how to query static DTO types, not a systematic introduction to the DTO language itself. Please refer to Object section/DTO Mapping/DTO Language for the complete DTO language.

Assuming the fully qualified name of the Book class is com.yourcompany.yourproject.model.Book, you can

  1. In the project where the entity is defined, create the directory src/main/dto

  2. Under src/main/dto, create subdirectories following the package path of the entity type com/yourcompany/yourproject/model

  3. In the directory created in the previous step, create a file Book.dto. The file name must match the entity class name, and the extension must be dto

  4. Edit this file, use the DTO language to define various DTO shapes for the Book entity

    Book.dto
    BookDetailView {

    #allScalars

    store {
    #allScalars
    }

    authors {
    #allScalars
    }
    }

    SimpleBookView { ...omitted... }

    ...Omit definitions of other DTO shapes...

Auto-generated DTO Types

Jimmer is responsible for compiling dto files and automatically generating DTO types matching these shapes.

caution

If Java/Kotlin source code files other than dto files are modified, running the application directly from the IDE can trigger recompilation of dto files.

However, if no Java/Kotlin files other than dto files are modified, simply clicking the run button in the IDE does not cause dto files to be recompiled unless explicitly rebuilding!

If you are using Gradle as your build tool, you can also use the third-party Gradle plugin provided by the community to solve this problem: jimmer-gradle

Taking BookDetailView in the above code as an example, after the dto file is successfully compiled by Jimmer, the following DTO type will be automatically generated:

BookDetailView.java
package com.yourcompany.yourproject.model.dto;

import com.yourcompany.yourproject.model.Book;
import org.babyfish.jimmer.View;

@GeneratedBy(file = "<your_project>/src/main/dto/Book.dto")
public class BookDetailView implements View<Book> {

private long id;

private String name;

private int edition;

private BigDecimal price;

private TargetOf_store store;

private List<TargetOf_authors> authors;

public static class TargetOf_store implements View<BookStore> {

private long id;

private String name;

@Nullable
private String website;

...other members omitted...
}

public static class TargetOf_authors implements View<Author> {

private long id;

private String firstName;

private String lastName;

private Gender gender;

...other members omitted...
}

...other members omitted...
}
info
  • The generated DTO classes are in the dto subpackage of the entity package, not the entity package itself.

  • For Java, it is assumed lombok is already in use.

Querying DTO Objects

Using findById

BookDetailView view = sqlClient.findNullable(
BookDetailView.class,
1L
);
System.out.println(view);

The printed result is as follows (formatted manually for readability):

BookDetailView(
id=1,
name=Learning GraphQL,
edition=1,
price=50.00,
store=BookDetailView.TargetOf_store(
id=1,
name=O'REILLY,
website=null,
version=0
),
authors=[
BookDetailView.TargetOf_authors(
id=2,
firstName=Alex,
lastName=Banks,
gender=MALE
),
BookDetailView.TargetOf_authors(
id=1,
firstName=Eve,
lastName=Procello,
gender=FEMALE
)
]
)

It's easy to see that although dynamic entity objects are no longer returned, the functionality is exactly the same as object fetchers. Why is this?

The DTO types generated from the DTO language all contain object fetchers matching their own shapes, as follows:

BookDetailView.java
@Data
public class BookDetailView implements View<Book> {

public static final ViewMetadata<Book, BookDetailView> METADATA =
new ViewMetadata<Book, BookDetailView>(
Fetchers.BOOK_FETCHER
.name()
.edition()
.price()
.store(TargetOf_store.METADATA.getFetcher())
.authors(TargetOf_authors.METADATA.getFetcher()),
BookDetailView::new
);

@Data
public static class TargetOf_store implements View<BookStore> {

public static final ViewMetadata<BookStore, TargetOf_store> METADATA =
new ViewMetadata<BookStore, TargetOf_store>(
Fetchers.BOOK_STORE_FETCHER
.name()
.website()
.version(),
TargetOf_store::new
);

...other members omitted...
}

@Data
public static class TargetOf_authors implements View<Author> {

public static final ViewMetadata<Author, TargetOf_authors> METADATA =
new ViewMetadata<Author, TargetOf_authors>(
Fetchers.AUTHOR_FETCHER
.firstName()
.lastName()
.gender(),
TargetOf_authors::new
);

...other members omitted...
}

...other members omitted...
}
tip

This is why this article says at the beginning that the DTO language is essentially another way to express object fetchers.

Using Custom Queries

BookTable table = Tables.BOOK_TABLE;

List<Book> books = sqlClient
.createQuery(table)
.where(table.name().eq("GraphQL in Action"))
.orderBy(table.name())
.orderBy(table.edition().desc())
.select(
table.fetch(BookDetailView.class)
)
.execute()

Here we see that table.fetch(fetcher) in previous code is replaced with table.fetch(BookDetailView.class).

tip

All low-level query APIs can replace fetcher with viewType, again proving that the DTO language is essentially another way to express object fetchers, as stated at the beginning.