跳到主要内容

Scala Provider

基本概念

在前面的文档中,我们介绍过如何映射枚举,以及如何使用@Serialized映射JSON

但是,有的时候,以上两种方法都无法满足我们的要求,这时我们可以使用ScalarProvider

提示

ScalarProvider是Jimmer关于自定义数据类型最底层的SPI。

之前我们探讨过的Enum映射JSON映射,其实就是Jimmer对ScalarProvider的内置实现。

可以为SqlClient注册多个ScalarProvider,每个ScalarProvider告诉Jimmer如何处理一种自定义数据类型。

ScalarProvider分为两种:

  • 全局级

    定义Java/Kotlin类型和数据库类型的映射规则,全局统一。任何实体定义中包含该类型的属性都会被全局ScalarProvider统一处理。

    备注

    全局级ScalarProvider只能处理非集合类型,比如:类、接口、枚举。不能处理类型为:ArrayCollectionMap等集合类型。

  • 属性级

    对某个特定的实体属性,定义Java/Kotlin类型和数据库类型的映射规则。

    备注
    • 属性级ScalarProvider可以处理任何非Jimmer内置的类型。包括集合类型,比如:ArrayCollectionMap

    • 如果被映射的属性类型是集合类型,该属性需要被@org.babyfish.jimmer.Scalar修饰。

提示

JSON映射一文阐述了@Serialized注解,既可以修饰属性返回的类型也可以修饰实体属性, 就是因为更底层的ScalarProvider有两种。

ScalarProvider是Jimmer提供的一个SPI接口,其定义如下:

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) {
...省略代码...
}

protected ScalarProvider() {
...省略代码...
}

public abstract T toScalar(S sqlValue);

public abstract S toSql(T scalarValue);

public Collection<ImmutableProp> getHandledProps() {
return null;
}
}
  • ❶ 范型参数T: Java/Kotlin中数据类型;

  • ❷ 范型参数S: 数据库中数据类型;

  • ❸ 明确指定TS所代表类型的构造函数;

    这个构造函数通常用于定义通用性和可复用性较强的ScalarProvider

  • ❹ 无需明确指定TS所代表类型的构造函数;

    要求派生类明确指定范型参数TS,让Jimmer可以自动分析出TS所代表类型。否则,会异常。

    这个构造函数通常用于定义特定类型所对应的ScalarProvider,不要求通用性和可复用性。

  • ❺ 方法toScalar: 把数据库中读取到的非null数据转换为Java数据;

  • ❻ 方法toSql: 把Java的非null数据转换为数据库可接受的数据;

  • ❼ 如果要定义属性级ScalarProvider,有一种选择 (也有其他选择) 是在派生类中覆盖方法getHandledProps

全局级ScalarProvider

例如当前数据库不支持UUID类型,可以如此处理:

定义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();
}
}

注册Scalar Provider

有两种方法可以让Jimmer注册ScalarProvider

  • 使用Spring Boot Starter

    ScalarProvider的各派生类被Spring托管即可。有两种选择。

    • 修改上面的UUIDScalarProvider类,用spring的@Component修饰

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

      ...省略代码...
      }
    • 用Spring的@Bean方法,向Spring注册UUIDScalaProvider对象

      @Bean
      public UUIDScalarProvider uuidScalarProvider() {
      return new UUIDScalarProvider();
      }
  • 使用底层API

    @Bean
    public JSqlClient sqlClient() {
    return JSqlClient
    .newBuilder()
    .addScalarProvider(new UUIDScalarProvider())
    ...省略其他配置...
    .build();
    }

属性级ScalarProvider

属性级ScalarProvider相较于全局级ScalarProvider唯一区别是,它只适用于特定属性,而非所有属性。

所以,属性级ScalarProvider中最重要的数据转换方法toScalartoSql的用户代码实现方法完全一样,只是注册方式不同而已。

因此,这里我们假设有一个用户自定义类型Location,其对应的ScalarProvider实现类为LocationScalarProvider,无需给出具体实现

非Spring注册方式

@Bean
public JSqlClient sqlClient() {
return JSqlClient
.newBuilder()
.setScalarProvider(
FlightProps.SOURCE_LOCATION
new LocationScalarProvider()
)
.setScalarProvider(
FlightProps.TARGET_LOCATION
new LocationScalarProvider()
)
...省略其他配置...
.build();
}

即,除Flight.sourceLocationFlight.targetLocation属性外,LocationScalarProvider不影响其他任何类型为Location的属性

Spring注册方式

上面的方式很直观,但是是手动注册的,Spring自动这册的方式可以用于注册属性级ScalarProvider吗?

当然。只需要覆盖ScalarProvider的方法getHandledProps,就可以用注册全局级ScalarProvider的方法注册属性级ScalarProvider。例如:

LocationScalarProvider.java
@Component
public class LocationScalarProvider extends AbstractScalarProvider<Location, String> {

@Override
public Collection<ImmutableProp> getHandledProps() {
return Arrays.asList(
FlightProps.SOURCE_LOCATION,
FlightProps.TARGET_LOCATION
);
}

...省略其他代码...
}