Skip to main content

Using DTO Language

Jimmer provides DTO language.

Developers can use this language to quickly define the shape of the Input DTO to be saved. After compilation:

  • The corresponding Java/Kotlin Input DTO classes will be generated automatically

  • The mutual conversion logic between Input DTOs and entities will be generated automatically

  • The object fetcher that matches its shape will be automatically generated. (This feature is unrelated to input DTOs, so it is not discussed in this article)

Developers use the auto-generated Input DTOs as method parameters, and persist them in one line of code (Internal logic: Call the auto-generated conversion logic to convert the Input DTO to a dynamic entity object, then directly persist using the save command).

info

This is the most efficient way to eliminate the pain of Input DTO explosion.

Defining DTO Shape

This article focuses on explaining how to save static DTO types, not a systematic introduction to DTO language. Please refer to Object/DTO conversion/DTO Language to know more.

Assume the fully qualified name of the Book class is com.yourcompany.yourproject.model.Book. You can:

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

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

  3. Under the directory created in previous step, create the file Book.dto. The file must have the same name as the entity class and the extension must be dto.

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

    Book.dto
    input BookInput {

    #allScalars(Book)

    id(store)

    authors {
    #allScalars(Author)
    -id
    }
    }

    input SimpleBookInput { ... }

    ...Other Input DTO shapes omitted...
    info

    Input DTO shapes used as input parameters must be decorated with the input modifier.

    The specific reasons are elaborated in Object/DTO conversion/DTO Language and not repeated here.

Auto-Generated DTO Types

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

caution

If Java/Kotlin source files other than dto files are modified, directly clicking the Run button in 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 IDE will not trigger dto file recompilation unless rebuild is explicitly triggered!

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

Take BookInput in the above code as an example. After successful compilation of this dto file, the following DTO types will be auto-generated:

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

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

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

@Nullable
private Long id;

private String name;

private int edition;

private BigDecimal price;

@Nullable
private Long storeId;

private List<TargetOf_authors> authors;

public BookInput(Book book) {
...
}

@Override
public Book toEntity() {
...
}

@Data
public static class TargetOf_authors {

private String firstName;

private String lastName;

private Gender gender;

...Other members omitted...
}

...Other members omitted...
}
  • ❶ If the id is designated some auto-generation strategy, it is not required. This is also a feature of save commands, see details in Mutation/Save Command/Save Mode/Summary.

    info
    • For Jimmer entities, id cannot be null. Missing id (i.e. not assigning it) represents the object does not have an id.

    • For Input DTOs, static POJO types do not have the concept of missing properties. null represents missing id.

    This seems contradictory, but can be handled simply: If Input DTO's id is null, the converted entity object will not have an id (although id of entity cannot be assigned null, dynamic objects allow unspecified properties).

  • ❷ Explicitly specifies this Input DTO wants to edit the entity's many-to-one association Book.store using the

    mode.

  • ❸ Explicitly specifies this Input DTO wants to edit the entity's many-to-many association Book.authors using the

    mode. The type of associated objects is also fixed to the nested Input DTO type BookInput.TargetOf_authors.

  • ❹ Convert dynamic entity to static Input DTO

  • ❺ Convert static Input DTO to dynamic entity

HTTP API

The DTO language generates relatively complete code, so we can quickly expose HTTP APIs to persist data:

@PutMapping("/book")
public void saveBook(
@RequestBody BookInput input
) {
// `save(input)` is equivalent to `save(input.toEntity())`
bookRepository.save(input);
}

Best Practices

In real projects, we often face the problem that entities may have many properties, and

  • Relatively more properties need to be specified during insertion

  • Relatively fewer properties need to be specified during modification

The Book entity we consistently use as example has few properties and is inconvenient to demonstrate this.

So I make up an entity type Product with more properties:

Product.java
@Entity
public interface Product {

...Many entity properties, omitted...
}
  • For insertion which requires specifying relatively more properties, define CreateProductInput.

  • For modification which requires specifying relatively fewer properties, define UpdateProductInput.

So the Product.dto code looks like:

input CreateProductInput {
...More properties, omitted...
}

input UpdateProductInput {
...Fewer properties, omitted...
}

After compilation, CreateProductInput and UpdateProductInput Java/Kotlin classes will be auto-generated. So we can quickly implement the following two HTTP APIs:

@PostMapping("/product")
public void createProduct(
// `CreateProductInput` has relatively more properties
@RequestBody CreateProductInput input
) {
productRepository.insert(input);
}

@PutMapping("/product")
public void updateProduct(
// `UpdateProductInput` has relatively fewer properties
@RequestBody UpdateProductInput input
) {
productRepository.update(input);
}
tip

As you can see, no matter how many different Input DTO types need to be defined for the same entity according to project requirements, we can quickly generate Input DTO types and related data conversion logic using DTO language, then persist in one line of code calling save commands.

Even if the project is more complex, e.g. people with different identities can edit data structures of different shapes, this pattern can still be applied repeatedly to handle such scenarios easily.