Scalar Provider
Basic Concepts
In previous documents, we have introduced how to map enums, and how to use @Serialized
to map JSON.
However, sometimes neither of these methods can meet our requirements. In this case, we can use ScalarProvider
.
ScalarProvider
is the most low-level SPI of Jimmer for custom data types.
The Enum Mapping and JSON Mapping we discussed before are actually Jimmer's built-in implementations of ScalarProvider
.
Multiple ScalarProvider
can be registered for SqlClient. Each ScalarProvider
tells Jimmer how to handle a custom data type.
There are two types of ScalarProvider
:
-
Global
Define the mapping rules between Java/Kotlin types and database types globally and uniformly. Any entity definition that contains properties of this type will be uniformly handled by the global
ScalarProvider
.noteGlobal
ScalarProvider
can only handle non-collection types, such as: classes, interfaces, enums, etc. It cannot handle collection types like:Array
,Collection
,Map
, etc. -
Property level
For a specific entity property, define the mapping rules between Java/Kotlin types and database types.
note-
Property-level
ScalarProvider
can handle any non-built-in types of Jimmer, including collection types such as:Array
,Collection
,Map
. -
If the mapped property type is a collection type, the property needs to be annotated with
@org.babyfish.jimmer.Scalar
.
-
The @Serialized
annotation discussed in JSON Mapping can annotate both the return type of properties and entity properties.
This is because the more low-level ScalarProvider
has two types.
ScalarProvider
is an SPI interface provided by Jimmer, which is defined as follows:
package org.babyfish.jimmer.sql.runtime;
import java.util.function.Consumer;
public abstract class ScalarProvider<T ❶, S ❷> {
protected ScalarProvider(Class<T> scalarType, Class<S> sqlType) { ❸
...Omitted code...
}
protected ScalarProvider() { ❹
...Omitted code...
}
public abstract T toScalar(S sqlValue); ❺
public abstract S toSql(T scalarValue); ❻
public Collection<ImmutableProp> getHandledProps() { ❼
return null;
}
}
-
❶ Generic parameter
T
: Data type in Java/Kotlin; -
❷ Generic parameter
S
: Data type in the database; -
❸ Constructor that explicitly specifies the types represented by
T
andS
;This constructor is usually used to define
ScalarProvider
with higher generality and reusability. -
❹ Constructor that does not need to explicitly specify the types represented by
T
andS
;It requires derived classes to explicitly specify generic parameters
T
andS
so that Jimmer can automatically analyze the types represented byT
andS
. Otherwise, an exception will occur.This constructor is usually used to define
ScalarProvider
corresponding to specific types without requiring generality and reusability. -
❺ Method
toScalar
: Convert non-null data read from the database to Java data; -
❻ Method
toSql
: Convert non-null Java data to data acceptable to the database; -
❼ If you want to define a property-level
ScalarProvider
, one option (there are other options) is to override thegetHandledProps
method in the derived class;
Global ScalarProvider
For example, the current database does not support the UUID type, which can be handled as follows:
Define ScalarProvider
- Java
- Kotlin
public class UUIDScalarProvider extends AbstractScalarProvider<UUID, String> {
@Override
public UUID toScalar(String sqlValue) {
return UUID.fromString(sqlValue);
}
@Override
public String toSql(UUID scalarValue) {
return scalarValue.toString();
}
}
class UUIDScalarProvider : ScalarProvider<UUID, String> {
override fun toScalar(sqlValue: String): UUID =
UUID.fromString(sqlValue)
override fun toSql(scalarValue: UUID): String =
scalarValue.toString()
}
Register Scalar Provider
There are two ways for Jimmer to register ScalarProvider
:
-
Use Spring Boot Starter
Just let the derived classes of
ScalarProvider
be managed by Spring. There are two options:-
Modify the above
UUIDScalarProvider
class and annotate it with spring's@Component
- Java
- Kotlin
UUIDScalarProvider.java@Component
public class UUIDScalarProvider extends AbstractScalarProvider<UUID, String> {
...Omitted code...
}UUIDScalarProvider.kt@Component
class UUIDScalarProvider : ScalarProvider<UUID, String> {
...Omitted code...
} -
Use Spring's
@Bean
method to register the UUIDScalaProvider object to Spring- Java
- Kotlin
@Bean
public UUIDScalarProvider uuidScalarProvider() {
return new UUIDScalarProvider();
}@Bean
fun uuidScalarProvider(): UUIDScalarProvider =
UnitTestIdGenerator()
-
-
Use underlying API
- Java
- Kotlin
@Bean
public JSqlClient sqlClient() {
return JSqlClient
.newBuilder()
.addScalarProvider(new UUIDScalarProvider())
...Omit other configurations...
.build();
}@Bean
fun sqlClient() : KSqlClient =
newKSqlClient {
addScalarProvider(UUIDScalarProvider())
...Omit other configurations...
}
Property level ScalarProvider
The only difference between property-level ScalarProvider
and global ScalarProvider
is that it only applies to specific properties, not all properties.
So the most important data conversion methods toScalar
and toSql
in property-level ScalarProvider
have exactly the same user code implementation, only the registration method is different.
Therefore, here we assume there is a user-defined type Location
and its corresponding ScalarProvider
implementation class is LocationScalarProvider
. Specific implementation is omitted.
Non-Spring registration
- Java
- Kotlin
@Bean
public JSqlClient sqlClient() {
return JSqlClient
.newBuilder()
.setScalarProvider(
FlightProps.SOURCE_LOCATION
new LocationScalarProvider()
)
.setScalarProvider(
FlightProps.TARGET_LOCATION
new LocationScalarProvider()
)
...Omit other configurations...
.build();
}
@Bean
fun sqlClient() : KSqlClient =
newKSqlClient {
setScalarProvider(
Flight::sourceLocation
LocationScalarProvider()
)
setScalarProvider(
Flight::targetLocation
LocationScalarProvider()
)
...Omit other configurations...
}
That is, except for the Flight.sourceLocation
and Flight.targetLocation
properties, LocationScalarProvider
does not affect any other properties of type Location
.
Spring registration
The above method is intuitive, but it is manually registered. Can Spring's automatic registration method be used to register property-level ScalarProvider
?
Of course. Just override the getHandledProps
method of ScalarProvider
, and you can use the method of registering global ScalarProvider
to register property-level ScalarProvider
. For example:
- Java
- Kotlin
@Component
public class LocationScalarProvider extends AbstractScalarProvider<Location, String> {
@Override
public Collection<ImmutableProp> getHandledProps() {
return Arrays.asList(
FlightProps.SOURCE_LOCATION,
FlightProps.TARGET_LOCATION
);
}
...Omit other code...
}
@Component
class LocationScalarProvider : ScalarProvider<Location, String> {
override fun getHandledProps(): Collection<ImmutableProp> =
listOf(
Flight::sourceLocation.toImmutableProp(),
Flight::targetLocation.toImmutableProp()
)
...Omit other code...
}