APT/KSP
Basic Concepts
Jimmer heavily relies on the precompilation technology in the JVM ecosystem:
-
For Java, it's
APT
, i.e., Annotation Processor Tool -
For Kotlin, it's
KSP
, i.e., Kotlin Symbol Processing
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
- Java (Maven)
- Java (Gradle)
- Kotlin (Gradle.kts)
- Java (Gradle Plugin)
- Kotlin (Gradle Plugin)
...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...
dependencies {
...other dependencies omitted...
annotationProcessor "org.babyfish.jimmer:jimmer-apt:${jimmerVersion}"
}
plugins {
// Add KSP plugin
id("com.google.devtools.ksp") version "1.7.10-1.0.6"
...other plugins omitted...
}
dependencies {
// Apply Jimmer's KSP code generator
ksp("org.babyfish.jimmer:jimmer-ksp:${jimmerVersion}")
...other dependencies omitted...
}
// Add generated code to compile path.
// Gradle build works without this, but IntelliJ won't find generated code.
kotlin {
sourceSets.main {
kotlin.srcDir("build/generated/ksp/main/kotlin")
}
}
plugins {
// Starting from Gradle 7.0, you can use "latest.release" instead of a specific version number to represent the use of the latest version.
// You can also use the '+' character to indicate matching the latest version number starting from the '+' character.
id "tech.argonariod.gradle-plugin-jimmer" version "latest.release"
...other plugins omitted...
}
jimmer {
// Set the Jimmer dependency version, where you can also use version range expressions such as "latest.release" or "0.+"
version = "${jimmerVersion}"
}
plugins {
// Starting from Gradle 7.0, you can use "latest.release" instead of a specific version number to represent the use of the latest version.
id("tech.argonariod.gradle-plugin-jimmer") version "latest.release"
// You can also use the '+' character to indicate matching the latest version number starting from the '+' character.
// Add KSP plugin
id("com.google.devtools.ksp") version "1.7.10+"
...other plugins omitted...
}
jimmer {
// Set the Jimmer dependency version, where you can also use version range expressions such as "latest.release" or "0.+"
version = "${jimmerVersion}"
}
- The first three: Standard configurations
- The last two: Community-provided Gradle plugin for further simplifying configurations
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
andbuild.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 Type | Usage | Notes |
---|---|---|
Projects defining entities | Generate necessary code based on entity definitions, such as Draft, SQL DSL, Fetcher | |
Projects defining DTO files under src/main/dto | Generate DTO classes based on the DTO language code | For Java, unless the current subproject has entity definitions, you need to find any class and annotate it with @EnableDtoGeneration |
Projects using Spring Web annotations | Automatically generate OpenAPI documentation and TypeScript code, writing Java/Kotlin documentation comments into the documentation and client code; support for remote exceptions |
Notes
Since Jimmer is a compile-time framework, and considering that not all users are familiar with apt
and ksp
, it's necessary to mention an important detail.
Apt/Ksp
are standard technologies in the industry, and Java IDEs provide support for them.
-
In most cases, your modifications will include changes to Java or Kotlin code, such as changes in entity types or Web Controller*(Jimmer has its own implementations for OpenAPI and TypeScript generation)*. In this case, you only need to click the IDE's
Run
orDebug
button once, without requiring a full compilation, to trigger all pre-compilation behaviors, the automatically generated source code and resource files will update automatically. -
In rare cases, if you only modify DTO files, meaning there are no Java or Kotlin source code changes within the same project except for the DTO files, you have three options:
- Use the companion DTO plugin
- Perform a full compilation using maven or gradle commands, or the IDE's
Rebuild
button, which can achieve this purpose - Delete the affected project's compilation output directory, then click the IDE's
Run
orDebug
button`
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:
Feature | Java | Kotlin |
---|---|---|
Draft | Use the generated type BookDraft | Use the original entity type Book |
|
| |
SQL DSL | Use the generated type BookTable | Use the original entity type Book |
|
| |
Fetcher | Use the generated types BookFetcher , BookStoreFetcher , and AuthorFetcher | Use the original entity type Book |
|
|
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.$.produce | Immutables.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.
- Java(Maven)
- Java(Gradle)
- Java(Gradle Plugin)
...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...
dependencies {
...omitted other dependencies...
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
annotationProcessor "org.babyfish.jimmer:jimmer-apt:${jimmerVersion}"
}
plugins {
id "tech.argonariod.gradle-plugin-jimmer" version "latest.release"
...省略其它插件...
}
jimmer {
version = ...
}
dependencies {
...omitted other dependencies...
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
}