在我的 Spring Boot Webflux 项目中,我使用 Spring Data R2DBC 进行反应式数据访问。该项目涉及从关系数据库中获取数据,其中一些表的列包含表示嵌套对象的 JSON 数据。
我当前的方法是获取数据,然后使用自定义读取转换器将 JSON 手动解析到我的 POJO 中。然而,当我有多个嵌套对象和不同的结构时,这个过程变得很麻烦。
这是我一直在做的事情的简化示例:
@UtilityClass
public class ConverterHelper {
// ObjectMapper initialization
public static <T> T readJson(Row row, String field, Class<T> clazz) {
// implementation
}
}
@ReadingConverter
public class CustomReadingConverter implements Converter<String, Object> {
// implementation
}
@Configuration
public class R2dbcConfiguration extends AbstractR2dbcConfiguration {
// implementation
}
我的问题是:是否有更通用的方法来处理这个问题,类似于 Spring Data JPA 如何自动映射嵌套对象?我不想每次遇到这种情况都编写自定义转换器。
我知道 JPA 或 Hibernate 可能会更优雅地处理这些场景,但这些不适合我的项目,因为我需要 R2DBC 提供的非阻塞 I/O。在 Spring Data R2DBC 领域是否有解决方案,或者我是否仅限于对这些复杂/嵌套对象使用自定义转换器?
任何建议或见解将不胜感激。
我通过使用 json 字段以另一种方式完成此操作,然后转换为映射步骤中我需要的任何对象。
例如,我可能有一个像这样的实体:
import io.r2dbc.postgresql.codec.Json;
@Table("table_name")
@Data
@Builder
public class MyObject {
@Id
private Integer id;
private Json data;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
像 DTO 类
@Data
@Builder
public class MyObjectDTO {
private Integer id;
private YourNestedObjectType data;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
然后是一个执行类似操作的映射类:
public MyObjectDTO to(MyObject myObj) {
try {
ObjectMapper objectMapper =
YourNestedObjectType somethingNested = objectMapper.readValue(
myObj.getData().asString(),
YourNestedObjectType.class);
return MyObjectDTO.builder()
.id(myObj.getId())
.data(somethingNested)
.createdAt(myObj.getCreatedAt())
.updatedAt(myObj.getUpdatedAt())
.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
理解 YourNestedObjectType 可以是一个包含其他类的包装类,例如
public class YourNestedObjectType {
private String field1;
private List<OtherObject> otherObjects;
}
最初,我尝试通过在 Converter 中自定义泛型类型来创建通用转换器,但没有成功(可能是因为泛型类型擦除,导致 Spring 无法读取类型信息)。 经过一番实验,我发现
GenericConverter
可以满足我们的要求。
这是代码:
@WritingConverter
public static class JsonWritingConverter implements GenericConverter {
private final Set<ConvertiblePair> convertibleTypes;
public JsonWritingConverter(Set<Class<?>> jsonTypes) {
this.convertibleTypes = new HashSet<>();
for (Class<?> targetType : jsonTypes) {
this.convertibleTypes.add(new ConvertiblePair(targetType, Json.class));
}
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.unmodifiableSet(convertibleTypes);
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return Json.of(JSON.toJSONString(source));
}
}
@ReadingConverter
public static class JsonReadingConverter implements GenericConverter {
private final Set<ConvertiblePair> convertibleTypes;
public JsonReadingConverter(Set<Class<?>> jsonTypes) {
this.convertibleTypes = new HashSet<>();
for (Class<?> targetType : jsonTypes) {
this.convertibleTypes.add(new ConvertiblePair(Json.class, targetType));
}
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.unmodifiableSet(convertibleTypes);
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
Json jsonSource = (Json) source;
return JSON.parseObject(jsonSource.asString(), targetType.getType());
}
}
它可以通过重写
Source
方法,通过 Target
显式指定多个 Class<?>
和 getConvertibleTypes
类型组合。这比使用泛型可靠得多。
然后,您可以在 convert
方法中编写之前指定的组合的转换逻辑。
至于注册,和
Converter
差不多。
@Bean
public R2dbcCustomConversions r2dbcCustomConversions(ConnectionFactory connectionFactory) {
List<Object> converters = new ArrayList<>();
Reflections reflections = new Reflections("your.package.to.scan");
Set<Class<?>> jsonTypes = reflections.getTypesAnnotatedWith(StoreJson.class);
converters.add(new JsonConverter.JsonWritingConverter(jsonTypes));
converters.add(new JsonConverter.JsonReadingConverter(jsonTypes));
return R2dbcCustomConversions.of(DialectResolver.getDialect(connectionFactory), converters);
}
我使用
Reflections
扫描自定义注解,这让我可以一次性获取所有映射到自定义Json的类。
而且,由于Converter
和ConvertiblePair
没有继承关系,所以List<Object>
的泛型类型需要是Object。
使用此配置,您只需要像这样注释您的类:
@Data
@Builder
@StoreJson
public class SessionData {
private List<SessionDataItem> itemData;
}
并且它会正常工作。
但是,正如你所看到的,我不知道如何优雅地处理JSON数组的类型,只能通过嵌套对象来实现。 希望我的回答可以帮助到你和其他人。