Skip to main content

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.

tip

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.

    note

    Global 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.

tip

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:

ScalarProvider
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 and S;

    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 and S;

    It requires derived classes to explicitly specify generic parameters T and S so that Jimmer can automatically analyze the types represented by T and S. 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 the getHandledProps 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

UUIDScalarProvider.java
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();
}
}

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

      UUIDScalarProvider.java
      @Component 
      public class UUIDScalarProvider extends AbstractScalarProvider<UUID, String> {

      ...Omitted code...
      }
    • Use Spring's @Bean method to register the UUIDScalaProvider object to Spring

      @Bean
      public UUIDScalarProvider uuidScalarProvider() {
      return new UUIDScalarProvider();
      }
  • Use underlying API

    @Bean
    public JSqlClient sqlClient() {
    return JSqlClient
    .newBuilder()
    .addScalarProvider(new UUIDScalarProvider())
    ...Omit other configurations...
    .build();
    }

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

@Bean
public JSqlClient sqlClient() {
return JSqlClient
.newBuilder()
.setScalarProvider(
FlightProps.SOURCE_LOCATION
new LocationScalarProvider()
)
.setScalarProvider(
FlightProps.TARGET_LOCATION
new LocationScalarProvider()
)
...Omit other configurations...
.build();
}

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:

LocationScalarProvider.java
@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...
}