Skip to main content

DTO Language

1. Concepts

1.1. Pain Point

Jimmer provides dynamic entities that can solve a large part of the DTO explosion problem very well. Therefore, generally speaking, it is not necessary to define output DTO types to express query results.

However, not all DTO types can be eliminated. Input DTO objects are hard to remove.

For example, in GraphQL, although dynamic GraphQLObject data is returned for the client from the output perspective, static GraphQLInput data submitted by the client is accepted from the input perspective.

Why does the GraphQL protocol define GraphQLInput as a static type? Because API explicitness and system security are very important requirements, please refer to Problems with dynamic objects as input parameters.

The problems faced by the GraphQL protocol are also faced by Jimmer, which must provide a complete solution.

tip

The DTO language is designed for that part of the DTO types that cannot be eliminated, with the goal of making them extremely cheap.

1.2. Solution

As a comprehensive solution, Jimmer is not limited to ORM itself, but considers the whole project. To solve this problem, it provides the DTO language.

The DTO language is a very powerful feature provided by Jimmer, and is a concept highly similar to Object Fetcher, but at an earlier stage during compilation.

The DTO language is used to quickly define the shape of data structures. Based on these shapes, DTOs can be compile-time

  • Generate specific static DTO types corresponding to specific data structures

  • Generate mutual conversion logic between DTO static objects and Jimmer dynamic objects

  • Generate Object Fetchers that match perfectly with the DTO shape definitions

Using the DTO language to quickly build DTO types is a solution tailored for Jimmer with extremely high development efficiency, much faster than using mapstruct, and is the recommended approach.

1.3. Application Scenarios

The application scenarios of the DTO language:

2. Create File

The code of the DTO language is reflected in files with the extension dto. Once compilation is complete, these files have no value. So these files cannot be packaged into src/main/resources, but should be placed in the src/main/dto directory.

Therefore, the first thing is to create a dto subdirectory under src/main.

tip

Jimmer does not require the src/main/dto directory to be in the project where the entity type is located. In fact, you can create this directory in any project that can legally access the entity.

Jimmer only requires using jimmer-apt or jimmer-ksp in projects containing dto files. They are responsible for compiling dto files and generating related code.

For Java projects, in addition to jimmer-apt, it may be necessary to add an extra configuration. Please refer to Notes.

2.1. Two ways to create files

Multiple dto files can be defined under the src/main/dto directory, with each file corresponding to an original entity.

Assuming there is a Jimmer entity type whose full type name is com.yourcompany.yourproject.Book, and this class is decorated with @org.babyfish.jimmer.sql.Entity (DTO language only supports Entity types), there are two ways to create dto files.

  1. Not using export statement

    In this case, the dto file directory needs to correspond to the original entity package, and the name needs to correspond to the original entity name:

    1. Under the src/main/dto directory, create the directory com/yourcompany/yourproject, that is, create a directory consistent with the package name

    2. In the directory created in the previous step, create a new file Book.dto. This file must have the same name as the class and have the extension dto

  2. Using export statement

    The exportstatement is important, so we will explain it separately in a chapter.

2.2 Export statement

In this case, the dto file directory and name are arbitrary, because we will use the export statement inside the file to define which original entity the dto file corresponds to.

  • Since there is no requirement for the dto file directory, it is recommended to create the dto file directly under src/main/dto

  • Although there is no requirement for the dto file name either, for the maintainability of the project, it is still recommended that the file name adopts the name of the original entity, which is Book.dto in this case

  • The first line of code in the dto file is the export statement

    export com.yourcompany.yourproject.Book

    ...subsequent code...

After the DTO file is compiled, more Java/Kotlin types will be generated automatically. Their default package name is: entity package name + .dto.

If you use the export statement, you can further define the package where the generated code is located, for example:

export com.yourcompany.yourproject.Book
-> package com.yourcompany.yourproject.mydto

Users can edit the Book.dto file to define any number of DTO types with Book as the aggregate root. Here we first define a DTO type:

Book.dto
...export statement omitted if any...

BookView {
...omitted...
}
info

The first line of code in the dto file may be the export statement. For simplicity, export statements are omitted in subsequent sections.

After compilation, the Java/Kotlin type BookView will be generated. Assuming the default value of the package where the generated code is located has not been changed, the generated code is as follows:

BookView.java
package com.yourcompany.yourproject.dto;

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

public class BookView implements View<Book> {
...omitted...
}

2.3. Notes

warning
  • For Java projects (Kotlin developers please ignore):

    If the current project is not the project that defines the entity, you need to decorate any class in the current project with @org.babyfish.jimmer.sql.EnableDtoGeneration.

    Otherwise, the DTO file will not be compiled.

  • dto files are compiled by Jimmer's Annotation Processor (Java) or Ksp (Kotlin).

    Therefore, if you are using an IDE like Intellij to develop projects, then

    • In addition to dto files, if other Java/Kotlin files are modified, clicking the run button in the IDE directly can cause recompilation of dto files

    • However, if no other Java/Kotlin files are modified except dto files, 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

3. view, input and specification

It was mentioned earlier that the DTO language has three use cases:

So, the DTO language can define three types of DTOs:

  • view: Neither uses the input keyword nor the specification keyword, which can be understood as Output DTO.

  • input: Declared with the input keyword, which can be understood as Input DTO.

  • specification: Declared with the specification keyword. It is not closely related to DTO itself, but can be used as a query parameter to support super QBE queries.

Book.dto
BookView {
...omitted...
}

AnotherBookView {
...omitted...
}

input BookInput {
...omitted...
}

input AnotherBookInput {
...omitted...
}

specification BookSpecification {
...omitted...
}

specification AnotherBookSpecification {
...omitted...
}

This means:

  • BookView and AnotherBookView are used as query outputs. The generated Java/Kotlin types implement the org.babyfish.jimmer.View<E> interface

    note

    It is recommended that output DTOs end with View

  • BookInput and AnotherBookInput are used as save command inputs. The generated Java/Kotlin types implement the org.babyfish.jimmer.Input<E> interface

    note

    It is recommended that input DTOs end with Input

  • BookSpecification and AnotherBookSpecification are used as query parameters to support super QBE queries. The generated Java/Kotlin types implement the org.babyfish.jimmer.Specification<E> interface

    note

    It is recommended that query parameter DTOs end with Specification

3.1 Common functionalities of view and input

For view and input, their generated Java/Kotlin types can convert between entities, with the following capabilities:

  • Constructor taking original entity type as parameter: Convert Jimmer dynamic entity object to static DTO object

  • toEntity(): Convert static DTO object to Jimmer dynamic entity object

Take BookView as an example:

Book entity = ...omitted...;

// Entity -> DTO
BookView dto = new BookView(entity);

// DTO -> Entity
Book anotherEntity = dto.toEntity();

3.2 input-specific functionalities

Compared with Output DTO, Input DTO has the following differences:

  • If the entity ID property is configured with auto-increment strategy, the id property in the input DTO is nullable.

    Here is the translation into English:

    info

    The reason for this design is that saving the object does not necessarily require the id property when the entity's id property has an auto-increment strategy.

    However, this does not mean Jimmer will simply assume like other ORMs such as JPA that lacking an id property means an insert operation while having an id property means an update operation.

    Jimmer has smarter strategies in this regard, please refer to Save Commands/Save Modes. This article will not repeat the details.

    If the developer does not accept this default behavior, there are two ways to write the DTO code:

    • Make the DTO type have no id property at all

      input BookInput { 
      #allScalars(this)
      -id
      }
    • Make the DTO type's id property non-nullable

      input BookInput { 
      #allScalars(this)
      id!
      }
  • The input DTO can only define savable properties, such as simple properties, ordinary ORM associations, and id-view properties. Unsavable properties such as transient properties, formula properties, computed properties, and remote associations cannot be defined, otherwise it will cause compilation errors.

  • Input DTO provides comprehensive and powerful support for nullable properties.

    tip

    For properties that are allowed to be null in the original entity, how to map them through Input DTO is a complex topic. Jimmer provides comprehensive and powerful support.

    Please refer to Modification/Save Command/Input DTO/Handle Null Values.

3.3 specification-specific functionalities

The role of specification is similar to input, used to decorate input types, but specification does not provide the ability to convert between entity objects, but is used as a query parameter to support super QBE queries.

tip

Super QBE Query is a very powerful feature of Jimmer that is not explained here. Please refer to the relevant section.

4. Simple properties

Attributes can be defined for DTO types to map properties in the original entity type, for example:

Book.dto
BookView {
id
name
edition
}

This means that the DTO only maps the three properties of the entity: id, name and edition, as follows:

BookView.java
public class BookView implements View<Book> {

private long id;
private String name;
private String edition;

public BookView(Book book) {
...omitted...
}

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

...other members omitted...
}

5. allScalars

The DTO language supports a special macro property, #allScalars, which represents mapping all scalar properties in the entity.

Book.dto
BookView {
#allScalars
}

According to the definition of the Book type in the official example:

  • The scalar properties of Book itself are id, name, edition, and price

  • Book inherits TenantAware, and the TenantAware interface defines the tenant property

  • Book also inherits CommonEntity (multiple inheritance), and CommonEntity interface defines the createdTime and modifiedTime properties

#allScalars means automatically mapping all scalar properties of the entity, including inherited ones.

If this is not the behavior you want, then

  • You can use allScalars(Book) to indicate mapping only the scalar properties defined by the Book type itself, excluding inherited properties.

  • You can also use allScalars(TenantAware) and allScalars(CommonEntity) to indicate properties of specific super types.

tip

For the current type Book, #allScalars(Book) can also be written as #allScalars(this).

In fact, #allScalars supports multiple parameters, for example:

Macro ExpressionAutomatically Mapped Attributes
#allScalarscreatedTime, modifiedTime, tenant, id, name, edition, price
#allScalars(this)id, name, edition, price
#allScalars(TenantAware)tenant
#allScalars(CommonEntity)createdTime, modifiedTime
#allScalars(this, TenantAware)tenant, id, name, edition, price
#allScalars(this, CommonEntity)createdTime, modifiedTime, id, name, edition, price
#allScalars(TenantAware, CommonEntity)createdTime, modifiedTime, tenant
#allScalars(this, TenantAware, CommonEntity)createdTime, modifiedTime, tenant, id, name, edition, price
info
  • If the #allScalars macro is used, it must be defined as the first property, otherwise it will cause compile errors.

  • If parameters are specified for the #allScalars macro, each parameter must be the current entity or its base type, otherwise it will cause compile errors.

6. Negative Attributes

The #allScalars macro mentioned above will batch map multiple properties. Negative properties can remove some properties.

Book.dto
BookView {
#allScalars
-tenant
}

Similar to above, if we understand according to the inheritance relationship of the Book type in the official example, #allScalars will add the properties createdTime, modifiedTime, tenant, id, name, edition and price.

-tenant removes the tenant property from it, finally adding the properties createdTime, modifiedTime, id, name, edition and price.

info

What is specified by the negative property is not the property name of the entity, but the name of the corresponding property in the DTO. Here the two are the same and there is no difference.

The Renaming section later will reveal the difference between the two.

It is not difficult to find that for this example, there is another equivalent writing:

Book.dto
BookView {
// Exclude `TenantAware`
#allScalars(Book, CommonEntity)
}

7. Nullability

By default:

  • All properties in specification are null by default.

  • Otherwise, the nullability of DTO properties is the same as the nullability of original properties in entities.

We can use ? or ! to modify DTO properties to change the nullability of DTO properties.

7.1. ?

We can use ? to modify DTO properties to make them nullable.

warning

If the original property in the entity is already nullable, it will cause compile errors.

For example:

Book.dto
input UpdateBookInput { 
#allScalars
price?
}

You can even apply ? to allScalars to make all automatically mapped non-associative properties nullable:

Book.dto
input UpdateBookInput {
#allScalars?
}
  • Specification types do not allow ?

    warning

    Since all properties in specification are nullable by default, specifying the modifier ? for properties within specification will cause compile errors.

  • When the original property in the entity does not allow null

    For non-nullable original properties in an entity, if the corresponding property in the DTO object is null, the original property in the entity object will not be assigned after converting the DTO object to the entity object.

  • When the original property in the entity allows null

    tip

    For properties that are allowed to be null in the original entity, how to map them through Input DTO is a complex topic. Jimmer provides comprehensive and powerful support.

    Due to length limitations, this issue forms a separate article. Please refer to Modification/Save Command/Input DTO/Handle Null Values.

7.2. !

We can use ! to modify DTO properties to make them non-null.

warning

If the original property in the entity is already non-null, it will cause compile errors.

Its use is limited and can only be used in the following three cases:

  • Modify the id property in input type

    If the id property is configured with auto-increment strategy, the corresponding input DTO type will make the id property nullable.

    However, this behavior does not always meet user expectations. You can use ! to modify the id property in the input type to make it non-null, for example:

    Book.dto
    input BookUpdateInfo {
    #allScalars
    id!
    }
    warning

    Using ? to modify other properties will cause compile errors.

  • If input is modified by the unsafe keyword, any nullable entity property can be converted to non-null, for example:

    Book.dto
    unsafe input BookUpdateInfo {
    #allScalars
    store! {
    ...omitted...
    }
    }
    info

    For an entity object, if the corresponding value of this property is null, when using the Input DTO constructor to convert it to an Input DTO object, an exception will occur because the corresponding property in DTO does not allow null.

    This is the meaning of the unsafe keyword.

  • For specification, since all properties are null by default, any nullable entity property can be converted to non-null, for example:

    Book.dto
    specification BookSpecification {
    #allScalars
    edition!
    }

8. Renaming

The as keyword can be used to set aliases for properties so that entity property names are different from DTO property names.

as has two usages, it can precisely set an alias for a single property, and it can also vaguely set aliases for multiple properties.

8.1. Renaming a single property

Book.dto
BookView {
name as bookName
}

8.2. Renaming multiple properties

Assume there is an entity type called Robot:

Robot.dto
OrderView {
as (^ -> oldSystem) { ❶
prop1
prop2
}
as ($ -> ForNewSystem) { ❷
prop3
prop4
}
}

Where:

  • ❶ Adds the prefix oldSystem to prop1 and prop2, where ^ indicates the start position

  • ❷ Adds the suffix ForNewSystem to prop3 and prop4, where $ indicates the end position

The generated RobotView class is as follows:

BookView.java
public class RobotView implements View<Robot> {

private String oldSystemProp1;
private String oldSystemProp2;

private String prop3ForNewSystem;
private String prop4ForNewSystem;

...other members omitted...
}

You can define most properties in the as() {...} block, of course including #allScalars, for example:

Robot.dto
RobotView {
as(^ -> robot) {
#allScalars
}
}

Below are all the usages of the as() {...} block:

Code ExampleDescription
as(^ -> prefix)Add prefix
as(^prefix ->)Remove prefix
as(^prefix -> newPrefix)Replace prefix
as($ -> suffix)Add suffix
as($suffix ->)Remove suffix
as($suffix -> newSuffix)Replace suffix
as(infix -> )Remove any content
as(infix -> newInfix)Replace any content
caution

^ and $ cannot appear at the same time, because it means precise renaming of a single property, which duplicates existing functionality.

9. Enum Mapping

By default, enum type properties in entities are still enum types after being mapped to DTO properties.

You can map enum types to numbers or strings:

  • Map to numbers

    AuthorView {
    #allScalars
    gender -> {
    MALE: 100
    FEMALE: 101
    }
    }

    The following code will be generated:

    BookView.java
    public class AuthorView implements View<AuthorView> {

    private int gender;

    ...other members omitted...
    }
  • Map to strings

    AuthorView {
    #allScalars
    gender -> {
    MALE: "Male"
    FEMALE: "Female"
    }
    }

    The following code will be generated:

    BookView.java
    public class AuthorView implements View<AuthorView> {

    private String gender;

    ...other members omitted...
    }

10. Association Attributes

The handling of association properties is a very powerful feature of the DTO language with three usages:

  • Direct use
  • Recursive association
  • Call id function
  • Call flat function

10.1. Ordinary Association

  • Basic usage

    Robot.dto
    input CompositeBookInput {

    #allScalars(Book)

    store {
    #allScalars(BookStore)
    -id
    }

    authors {
    #allScalars(Author)
    -id
    }
    }

    The generated code is:

    CompositeBookInput.java
    public class CompositeBookInput implements Input<Book> {

    @Nullable
    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 Input<BookStore> {

    private String name;

    @Nullable
    private String website;

    ...other members omitted...
    }

    public static class TargetOf_authors implements Input<Author> {

    private String firstName;

    private String lastName;

    private Gender gender;

    ...other members omitted...
    }

    ...other members omitted...
    }
  • Renaming

    If you want to rename association properties, the as clause should be before the association definition block, for example:

    Book.dto
    input CompositeBookInput {

    authors as authorList {
    ...omitted...
    }

    ...omitted...
    }

10.2 Recursive Association

For recursively associative properties in entities, such as TreeNode.parent or TreeNode.childNodes in the attached example, the corresponding associative property in the DTO can be marked as recursive using an asterisk *.

TreeNode.dto
TreeNodeView {

#allScalars

childNodes*
}

10.3. id Function

Here is the translation into English:

For

, while of course we can choose to define @IdView properties on the entities and simply reference them in DTOs, we can't always rely on entity types having declared @IdView properties. A better approach should not make that assumption.

Therefore, the DTO language supports the id function on associated properties to achieve the same purpose:

Book.dto
BookView {

id(store)

id(authors) as authorIds

...omitted...
}

This code has the exact same function as the previous example, but no longer assumes the entity type always declares @IdView properties, which is a better solution.

10.4. flat Function

  • For view and input, the flat function can only be used for referential associations (one-to-one or many-to-one), not collection associations (one-to-many or many-to-many). It is used to promote the properties of associated DTOs to the current DTO type to obtain a flattened object without associations.

  • For specification, the flat function does not have the above restrictions and can be used for collection associations. Please check (../../query/super_qbe).

Take the TreeNode entity in the official example (in fact, this example is wrong, we will discuss this issue later):

TreeNode.dto
FlatTreeNodeView {

#allScalars(TreeNode)

flat(parent) {
#allScalars(TreeNode)
}
}

As mentioned above, this example is wrong, because the aggregate root and the associated object both have the id and name properties. Unconditionally extracting the properties of the associated object to the aggregate root will inevitably lead to name conflicts, eventually causing DTO language compilation errors.

The right approach is to combine it with the user's multi-property fuzzy renaming as(...) {} block. Let's look at a new example:

TreeNode.dto
FlatTreeNodeView {

#allScalars(TreeNode) // id, name

flat(parent) {
as(^ -> parent) {
// parentId, parentName
#allScalars(TreeNode)
}
}
}

Thus, we get a flattened DTO type with 4 properties: id, name, parentId and parentName.

You can even nest the flat function to convert multi-level associations into flattened objects:

TreeNode.dto
FlatTreeNodeView {

#allScalars(TreeNode)

flat(parent) {
as(^ -> parent) {
#allScalars(TreeNode)
}
flat(grandParent) {
as(^ -> parent) {
#allScalars(TreeNode)
}

}
}
}

Thus, we get a flattened DTO type with 6 properties: id, name, parentId, parentName, grandParentId, and grandParentName.

TreeNode treeNode = Immutables.createTreeNode(cola -> {
cola.setId(4L);
cola.setName("Coca cola");
cola.applyParent(drinks -> {
drinks.setId(3L);
drinks.setName("Drinks");
drinks.applyParent(food -> {
food.setId(2L);
food.setName("Food");
})
})
});

// Convert hierarchical Entity to flattened DTO
FlatTreeNodeView view = new FlatTreeNodeView(treeNode);

System.out.println(view);

The print result is (formatted here for readability):

com.yourcompany.yourproject.FlatTreeNodeView(
id = 4,
name = Coco cola,
parentId = 3,
parentName = Drinks,
grandParentId = 2,
grandParentName = "Food"
)

The flat solution also has drawbacks. For output DTOs, the flat solution is controversial and is not recommended to be blindly adopted.

Please refer to

11. Custom Fields

11.1 Introduction to Custom Fields

In the examples we have discussed before, all DTO properties are mapped from entity properties and become mapped properties.

In addition to mapped properties, DTO types also support mapping custom properties:

BookInput.dto
BookInput {

#allScalars(Author)? - id

remark: String
}

Here, BookInput has a custom field remark.

info

The difference between custom properties and mapped properties is that custom properties require type definitions.

11.2 Built-in Types

Custom fields can be defined as built-in types, which are types built into the DTO language that do not require import via import.

  • Primitive and boxed types

    DTO Language TypeGenerated Java TypeGenerated Kotlin Type
    BooleanbooleanBoolean
    Boolean?BooleanBoolean?
    CharcharChar
    Char?CharacterChar?
    BytebyteByte
    Byte?ByteByte?
    ShortshortShort
    Short?ShortShort?
    IntintInt
    Int?IntegerInt?
    LonglongLong
    Long?LongLong?
    FloatfloatFloat
    Float?FloatFloat?
    DoubledoubleDouble
    Double?DoubleDouble?
  • Any and String types

    DTO Language TypeGenerated Java TypeGenerated Kotlin Type
    AnyObjectAny
    StringStringString
  • Array types

    DTO Language TypeGenerated Java TypeGenerated Kotlin Type
    Array<Boolean>boolean[]BooleanArray
    Array<Boolean?>Boolean[]Array<Boolean?>
    Array<Char>char[]CharArray
    Array<Char?>Character[]Array<Char?>
    Array<Byte>byte[]ByteArray
    Array<Byte?>Byte[]Array<Byte?>
    Array<Short>short[]ShortArray
    Array<Short?>Short[]Array<Short?>
    Array<Int>int[]IntArray
    Array<Int?>Integer[]Array<Int?>
    Array<Long>long[]LongArray
    Array<Long?>Long[]Array<Long?>
    Array<Float>float[]FloatArray
    Array<Float?>Float[]Array<Float?>
    Array<Double>double[]DoubleArray
    Array<Double?>Double[]Array<Double?>
    Array<UserType>UserType[]Array<UserType>
    Array<UserType?>UserType[]Array<UserType?>
    Array<*>Object[]Array<*>
  • Collection types

    DTO Language TypeGenerated Java TypeGenerated Kotlin Type
    Iterable<E>Iterable<? extends E>Iterable<E>
    MutableIterable<E>Iterable<E>MutableIterable<E>
    Collection<E>Collection<? extends E>Collection<E>
    MutableCollection<E>Collection<E>MutableCollection<E>
    List<E>List<? extends E>List<E>
    MutableList<E>List<E>MutableList<E>
    Set<E>Set<? extends E>Set<E>
    MutableSet<E>Set<E>MutableSet<E>
    Map<K, V>Map<? extends K, ? extends V>Map<K, V>
    MutableMap<K, V>Map<K, V>Mutable<K, V>

11.3 Generic Parameter Modifiers

Except for generic class types (arrays and collections) in built-in types, other generic class types support using in or out to modify generic parameters, for example:

DTO Language TypeGenerated Java TypeGenerated Kotlin Type
UserType1<UserType2>UserType1<UserType2>UserType1<UserType2>
UserType1<out UserType2>UserType1<? extends UserType2>UserType1<out UserType2>
UserType1<in UserType2>UserType1<? super UserType2>UserType1<in UserType2>

11.4 Import Statements

Except for built-in types, other types must be fully qualified or imported at the beginning of the file when referenced, otherwise they will be considered to belong to the same package as the current entity.

The DTO language supports various styles of import statements, for example:

  • Single class import

    import java.time.LocalDateTime
  • Single class renamed import

    import java.time.LocalDateTime as LDT
  • Multi-class import

    import java.time.{
    LocalDateTime,
    OffsetDataTime,
    ZonedDataTime
    }
  • Multi-class renamed import

    import java.time.{
    LocalDateTime as LDT,
    OffsetDataTime as ODT,
    ZonedDataTime as ZDT
    }

12. Annotations

12.1 Basic Usage

You can use annotations to decorate DTO types, mapped properties, and custom properties in the DTO language:

BookInput.dto
import org.babyfish.jimmer.client.Doc 
import javax.validation.constraints.Size

@Doc("BookInput without associations") ❶
BookInput {

#allScalars

@Size(min = 4, max = 20) ❷
name

@Size(min = 10, max = 50) ❸
remark: String
}

Where:

  • ❶ Decorates type

  • ❷ Decorates mapped properties

  • ❸ Decorates custom properties

tip

There is a detail to note:

BookView
...import statements omitted...

BookView {

...other properties omitted...

@A authors @B {
...associated object properties omitted...
}

...other DTO types omitted...
}

Where:

  • @A decorates the authors property

  • @B decorates the anonymous associated type

12.2 Annotation Replacement

  • As mentioned above, the DTO language supports annotations to directly control the annotations of the generated DTO types.

  • If a DTO property is not decorated by any annotation, the annotations of the corresponding property in the original entity will be copied (if the DTO property is mapped, not custom). Among them, any non-jimmer annotations under the org.babyfish.jimmer.client package will be copied to the generated DTO type, except for those.

12.3 value Parameter

The value parameter in annotations can be abbreviated, for example:

@UserAnnotation(value = "a", value2 = "b", value3 = "c")

can be abbreviated as:

@UserAnnotation("a", value2 = "b", value3 = "c")

Unlike Java, no matter how many parameters an annotation has, the value parameter can be abbreviated, as long as the abbreviated property is configured first.

12.4 Mixing Java and Kotlin Syntax

There are some subtle differences between Java and Kotlin annotation syntax. The DTO language mixes the features of both, for example:

  • @UserAnnotation(
    "key1",
    items = {
    @Item("subKey1", description = "Detail information for subKey1 ..."),
    @Item("subKey2", description = "Detail information for subKey2 ..."),
    @Item("subKey3", description = "Detail information for subKey3 ...")
    }
    )
  • @UserAnnotation(
    "key1",
    items = [
    @Item("subKey1", description = "Detail information for subKey1 ..."),
    @Item("subKey2", description = "Detail information for subKey2 ..."),
    @Item("subKey3", description = "Detail information for subKey3 ...")
    ]
    )
  • @UserAnnotation(
    "key1",
    items = {
    Item("subKey1", description = "Detail information or subKey1 ..."),
    Item("subKey2", description = "Detail information for subKey2 ..."),
    Item("subKey3", description = "Detail information for subKey3 ...")
    }
    )
  • @UserAnnotation(
    "key1",
    items = [
    Item("subKey1", description = "Detail information for subKey1 ..."),
    Item("subKey2", description = "Detail information for subKey2 ..."),
    Item("subKey3", description = "Detail information for subKey3 ...")
    ]
    )

The above four styles are equivalent. You can freely choose the style you like.

13. Implementing Interfaces

DTO types are low-value information, and if different DTO types are allowed to reference each other, it would lead to a problem: the early effect is convenience, but the later effect is difficulty in maintenance. Therefore, Jimmer prohibits users from seeking reusability in low-value DTO types.

However, sometimes there is indeed a need to find commonalities between different DTO types (especially for internal classes of DTO), in order to facilitate abstraction and design. For this purpose, the DTO language provides a compromise: DTO types are allowed to implement existing Java/Kotlin interfaces, provided that the abstract properties in the interface are a subset of the properties in the DTO and can be implemented by the DTO type.

DTO code as follows

export com.yourcompany.yourproject.model.Book
-> com.yourcompany.yourproject.model.dto

import com.yourcompany.yourcompany.common.{
Shape1, Shape2, Shape3, Shape4, Shape5, Shape6
}

BookView implements Shape1<String>, Shape2 {
#allScalars
store implements Shape3<String>, Shape4 {
#allScalars
}
authors implements Shape5<String>, Shape6 {
#allScalars
}
}

Compiled, generates the following code

BookView.java
package com.yourcompany.yourproject.dto;

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

public class BookView
implements View<Book>, Shape1<String>, Shape2 {

@Nullable
private TargetOf_store store;

private List<TargetOf_authors> authors;

...other members omitted...

public static class TargetOf_store
implements View<BookStore>, Shape3<String>, Shape4 {

...members omitted...
}

public static class TargetOf_authors
implements View<Author>, Shape5<String>, Shape6 {

...members omitted...
}
}
caution

If a property in the DTO overrides an abstract property in the interface, then in the auto-generated Java/Kotlin type, that property will be annotated with @Override (Java) or override keyword (Kotlin).

Apart from this, the DTO language does not perform much validation on interface implementation. If the user makes other mistakes, it will result in generating incorrect Java/Kotlin types, which will be handled by the Java/Kotlin compiler.

As mentioned earlier, the DTO language has two more powerful features that are not discussed in depth in this article due to length limitations, but have been separated into other documents. Here we emphasize them again: