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
ScalarProvidercan 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
ScalarProvidercan 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
TandS;This constructor is usually used to define
ScalarProviderwith higher generality and reusability. -
❹ Constructor that does not need to explicitly specify the types represented by
TandS;It requires derived classes to explicitly specify generic parameters
TandSso that Jimmer can automatically analyze the types represented byTandS. Otherwise, an exception will occur.This constructor is usually used to define
ScalarProvidercorresponding 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 thegetHandledPropsmethod 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
ScalarProviderbe managed by Spring. There are two options:-
Modify the above
UUIDScalarProviderclass 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
@Beanmethod 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...
}