Skip to main content

APT/KSP

Basic Concepts

Jimmer heavily relies on the precompilation technology in the JVM ecosystem:

info

Some code generated by APT/KSP is necessary for using Jimmer.

Therefore, if you open any Java/Kotlin project from the official examples in IntelliJ, you will find that some code that should have been automatically generated is missing. For this, you can choose any of the following methods:

  • First, execute the ./mvnw install (for Java examples only) or ./gradlew build command in the directory of the project you want to open from the command line to complete the code generation, then open the project with IntelliJ.

  • Open the project directly with IntelliJ, ignore the IDE errors temporarily, and after the dependencies are downloaded, run the project's main method or unit test (save-command/save-command-kt demonstrates features using unit tests). All IDE errors will automatically disappear, and the application will start correctly.

How to Use

pom.xml
...other code omitted... 

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.babyfish.jimmer</groupId>
<artifactId>jimmer-apt</artifactId>
<version>${jimmer.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

...other code omitted...
info
  • The first three: Standard configurations
  • The last two: Community-provided Gradle plugin for further simplifying configurations
note

KSP only supports gradle, It has been proven that KSP's third-party Maven plug-in support cannot keep up with the version iteration of 'kotlin/KSP' itself, and often encounters many problems during the upgrade process.

Eventually, Jimmer dropped Maven support for Kotlin and asked Kotlin developers to use Gradle.

For all projects in the official examples:

  • All Java examples have both pom.xml and build.gradle, i.e., they support both Maven and Gradle.

    When opening these projects for the first time, IntelliJ will ask you how you want to open them, and you can make your choice.

    If you want to switch the opening method, exit IntelliJ, delete the hidden directory .idea in the project, and then open the project with IntelliJ again and reselect the method.

  • All Kotlin examples only have the build.gradle.kts file, i.e., they only support Gradle. The reason has been explained earlier.

IntelliJ has some rash over-optimization measures for integrating annotation processors introduced via Maven, resulting in a better development experience when using Gradle with the IDE than with Maven.

Where to Use

Business projects are rarely a single project, but more often split into multiple subprojects using build tools like Maven and Gradle.

So, in which subprojects should we use the configurations mentioned earlier?

Subproject TypeUsageNotes
Projects defining entitiesGenerate necessary code based on entity definitions, such as Draft, SQL DSL, Fetcher
Projects defining DTO files under src/main/dtoGenerate DTO classes based on the DTO language codeFor Java, unless the current subproject has entity definitions, you need to find any class and annotate it with @EnableDtoGeneration
Projects using Spring Web annotationsAutomatically generate OpenAPI documentation and TypeScript code, writing Java/Kotlin documentation comments into the documentation and client code; support for remote exceptions

Two Styles of Java Code

Unlike the Kotlin API, the Java API cannot avoid to directly use automatically generate types in user code. Let's compare the following:

FeatureJavaKotlin
DraftUse the generated type BookDraftUse the original entity type Book
Book book = 
BookDraft.$.produce(b -> {
b.setName("SQL");
b.addIntoAuthors(a -> {
a.setName("Jessica");
});
b.addIntoAuthors(a -> {
a.setName("Bob");
});
});
val book =
new(Book::class).by {
name = "SQL in Action"
authors().addBy {
name = "Jessica"
}
authors().addBy {
name = "Bob"
}
}
SQL DSLUse the generated type BookTableUse the original entity type Book
BookTable table = BookTable.$;

List<Book> books = sqlClient
.createQuery(table)
.where(table.storeId().isNull())
.orderBy(table.name())
.select(table)
.execute();
List<Book> books = sqlClient
.createQuery(Book::class) {
where(table.storeId.isNull())
orderBy(table.name)
select(table)
}
.execute()
FetcherUse the generated types BookFetcher, BookStoreFetcher, and AuthorFetcherUse the original entity type Book
Fetcher<Book> fetcher =
BookFetcher.$
.allScalarFields()
.store(
BookStoreFetcher.$
.allScalarFields()
)
.authors(
AuthorFetcher.$
.allScalarFields()
)
val fetcher = 
newFetcher(Book::class).by {
allScalarFields()
store {
allScalarFields()
}
authors {
allScalarFields()
}
}

As you can see, the abstraction capabilities of Java and Kotlin are different, resulting in different effects that the API design can achieve:

  • For Kotlin, regardless of the scenario, you only need to use the original entity type Book.

  • For Java, you have to use the types automatically generated by the Annotation Processor, such as BookDraft, BookTable, BookFetcher, etc.

    In the above Java code, .$ appears frequently, $ are static read-only fields of these classes.

Actually, using $ is the simplest way. However, considering that some Java developers have subjective biases against $, for Java subprojects defining entity types, Jimmer's APT also generate four summary types:

  • Objects class

  • Tables interface

  • TableExes interface

  • Fetchers interface

The package where these four types reside is the common package for all entities.

These four types provide another coding style for Java code by defining static constants, and the two styles are compared as follows:

Style Accepting $Style Not Accepting $
BookDraft.$.produceObjects.createBook
BookTable.$Tables.BOOK_TABLE
BookTableEx.$TableExes.BOOK_TABLE_EX
BookFetcher.$Fetchers.BOOK_FETCHER

Additionally, Tables, TableExes, and Fetchers are interfaces, and you can use the implements statement to further simplify the code (since IntelliJ does not support static imports well, implementing interfaces that define constants is still a recommended technique). For example:

public interface FetcherConstants
implements Fetchers {

Fetcher<Book> BOOK_DETAIL_FETCHER =
BOOK_FETCHER
.allScalarFields()
.store(
BOOK_STORE_FETCHER
.allScalarFields()
)
.authors {
AUTHOR_FETCHER
.allScalarFields()
};
}

Integration with Lombok

Java projects often use Lombok.

By default, if the project does not have any APT other than Lombok, you only need to import the Lombok dependency.

However, once you introduce other APT configurations (not necessarily Jimmer's APT, but any other APT), you must explicitly configure Lombok's APT.

pom.xml
...omitted other code...

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.babyfish.jimmer</groupId>
<artifactId>jimmer-apt</artifactId>
<version>${jimmer.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

...omitted other code...