Java
Usage
First, add the annotation and processor dependencies to your favorite build-tool. See below for Maven, Gradle or Mill.
Mark your domain-primitive with the @LazyValue annotation and the processor will figure out the rest.
Quantity Record
@LazyValue()
public record Quantity(int value) {
public Quantity {
if(value <= 0){
throw new IllegalArgumentException("Quantity must be greater than 0");
}
}
}
ISBN Object with factory method
@LazyValue
public final class Isbn {
private final String value;
private Isbn(String value){
Objects.requireNonNull(value);
this.value = value;
}
public String value(){
return value;
}
// will be used by the annotation processor (factory methods have higher precedence)
public static Isbn parse(String value) throws IllegalArgumentException {
Objects.requireNonNull(value, "ISBN cannot be null");
String cleanValue = value.replaceAll("[-\\s]", "");
if (cleanValue.length() == 10) {
validateIsbn10(cleanValue);
} else if (cleanValue.length() == 13) {
validateIsbn13(cleanValue);
} else {
throw new IllegalArgumentException("Invalid ISBN length. Must be 10 or 13 digits (excluding hyphens)");
}
return new Isbn(value);
}
// ... validation, equals, hashCode, toString
Generated Mapper definition
@Mapper(
unmappedTargetPolicy = ReportingPolicy.ERROR
)
public interface LazyvalMapper {
default int mapQuantityToWrappedType(Quantity type) {
return type.value();
}
default Quantity mapQuantity(int value) {
return new Quantity(value);
}
default String mapIsbnToWrappedType(Isbn type) {
return type == null ? null : type.value();
}
default Isbn mapIsbn(String value) {
return value == null ? null : Isbn.parse(value);
}
}
This Mapper can now be used in other mappers or included in a default config. See Mapstructs docu on shared configurations
@MapperConfig(
uses = LazyvalMapper.class
)
public interface DefaultMapperConfig {
}
Generated JPA AttributeConverter definition
@Converter(
autoApply = true
)
public class QuantityAttributeConverter implements AttributeConverter<Quantity, Integer> { // 1.
public Integer convertToDatabaseColumn(Quantity type) {
return type.value();
}
public Quantity convertToEntityAttribute(Integer dbValue) {
return new Quantity(dbValue);
}
}
@Converter(
autoApply = true
)
public class IsbnAttributeConverter implements AttributeConverter<Isbn, String> {
public String convertToDatabaseColumn(Isbn type) {
return type == null ? null : type.value();
}
public Isbn convertToEntityAttribute(String dbValue) {
return dbValue == null ? null : Isbn.parse(dbValue);
}
}
-
where needed, wrapped primitives are boxed
These converters will be automatically picked up by your persistence provider.
| Lazyval will only generate Classes for which the dependencies are available on the classpath. In case neither Mapstruct, nor JPA is available, a Warning message will be logged in the compilation. |
Setup
Maven
pom.xml
<properties>
<version.lazyval>0.1.0</version.lazyval>
<version.mapstruct>1.6.3</version.mapstruct>
<version.jakarta-ee>11.0.0</version.jakarta-ee>
</properties>
<dependencies>
<dependency>
<groupId>de.qualityminds.lazyval</groupId>
<artifactId>lazyval</artifactId>
<version>${version.lazyval}</version>
<optional>true</optional> <!-- 1. -->
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${version.mapstruct}</version>
</dependency>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>${version.jakarta-ee}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>17</release>
<proc>full</proc>
<annotationProcessorPaths>
<path>
<groupId>de.qualityminds.lazyval</groupId>
<artifactId>lazyval-processor</artifactId>
<version>${version.lazyval}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${version.mapstruct}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amapstruct.unmappedTargetPolicy=ERROR</arg>
<arg>-Alazyval.jpa.generatedPackage=test.boundary.persistence</arg>
<arg>-Alazyval.mapstruct.generatedPackage=test</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
-
mark the dependency as optional to avoid runtime-dependency
Gradle
build.gradle.kts
val versionLazyval = project.findProperty("version.lazyval") as String? ?: "0.1.0"
dependencies {
compileOnly("de.qualityminds.lazyval:lazyval:$versionLazyval")
implementation("org.mapstruct:mapstruct:1.6.3")
compileOnly("jakarta.platform:jakarta.jakartaee-api:11.0.0")
// Annotation processors
annotationProcessor("de.qualityminds.lazyval:lazyval-processor:$versionLazyval")
annotationProcessor("org.mapstruct:mapstruct-processor:1.6.3")
}
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.apply{
add("-Amapstruct.unmappedTargetPolicy=ERROR")
add("-Alazyval.jpa.generatedPackage=test.boundary.persistence")
add("-Alazyval.mapstruct.generatedPackage=test")
}
}
Mill
build.mill
override def mvnDeps: T[Seq[Dep]] = Seq(
mvn"org.mapstruct:mapstruct:${Version.mapstruct}",
)
override def compileMvnDeps: T[Seq[Dep]] = Seq(
mvn"de.qualityminds.lazyval:lazyval:${Version.lazyval}",
mvn"jakarta.persistence:jakarta.persistence-api:${Version.jpa}"
)
override def annotationProcessorsMvnDeps = Seq(
mvn"de.qualityminds.lazyval:lazyval-processor:${Version.lazyval}",
mvn"org.mapstruct:mapstruct-processor:${Version.mapstruct}"
)
def javacOptions = super.javacOptions() ++ Seq(
"-Amapstruct.unmappedTargetPolicy=ERROR",
"-Alazyval.jpa.generatedPackage=test.boundary.persistence",
"-Alazyval.mapstruct.generatedPackage=test",
)