Overriding Default Mapping with Custom Converters

Custom Conversions

Spring Converter 实现的以下示例将 String 转换为自定义 Email 值对象:

@ReadingConverter
public class EmailReadConverter implements Converter<String, Email> {

  public Email convert(String source) {
    return Email.valueOf(source);
  }
}

如果您编写一个其源类型和目标类型都是本机类型的 Converter,我们将无法确定是否应该将其视为读转换器还是写转换器。将转换器实例同时注册为两个实例可能会导致意外结果。例如,Converter<String, Long> 是有歧义的,尽管在编写时尝试将所有 String 实例转换为 Long 实例可能没有意义。为了强制基础设施仅为单向注册一个转换器,我们提供 @ReadingConverter@WritingConverter 注释在转换器实现中使用。 由于不会从类路径或容器扫描中拾取转换器实例,因此必须显式注册转换器,以避免对转换服务进行不必要的注册以及由此注册产生的副作用。转换器使用 CustomConversions 注册,CustomConversions 是一个中心设施,允许基于源类型和目标类型注册和查询已注册的转换器。 CustomConversions 附带一组预定义的转换器注册:

  • JSR-310 转换器,用于在 java.timejava.util.DateString 类型之间进行转换。

本地时间类型的默认转换器(例如 LocalDateTimejava.util.Date)依赖于系统默认时区设置在这些类型之间进行转换。你可以通过注册自己的转换器来覆盖默认转换器。

Converter Disambiguation

总体而言,我们检查 Converter 实现从哪些源类型和目标类型进行转换以及转换到哪些源类型和目标类型。根据其中一个是否是底层数据访问 API 本机可以处理的类型,我们将转换器实例注册为读转换器或写转换器。以下示例显示了写转换器和读转换器(请注意,Converter 上的限定词的顺序存在差异):

// Write converter as only the target type is one that can be handled natively
class MyConverter implements Converter<Person, String> { … }

// Read converter as only the source type is one that can be handled natively
class MyConverter implements Converter<String, Person> { … }

Overriding Default Mapping with Custom Converters

要对映射过程进行更细粒度的控制,你可以向 CassandraConverter 实现(例如 MappingCassandraConverter),注册 Spring Converters

MappingCassandraConverter 首先检查是否任何 Spring Converters 都能处理特定类,然后才尝试映射对象本身。要“劫持”MappingCassandraConverter 的正常映射策略(可能是为了提高性能或其他自定义映射需要),你需要创建一个 Spring Converter 界面实现,并使用 MappingCassandraConverter 注册它。

Saving by Using a Registered Spring Converter

你可以将转换和保存合并到一个过程中,基本上使用转换器来进行保存操作。

以下示例使用 Converter 将一个 Person 对象使用 Jackson 2 转换为 java.lang.String

/*
 * Copyright 2020-2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.cassandra.example;

import java.io.IOException;

import org.springframework.core.convert.converter.Converter;

import com.fasterxml.jackson.databind.ObjectMapper;
// tag::class[]
class PersonWriteConverter implements Converter<Person, String> {

	public String convert(Person source) {

		try {
			return new ObjectMapper().writeValueAsString(source);
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
	}
}
// end::class[]

Reading by Using a Spring Converter

类似于你可以合并保存和转换的方式,你还可以合并读取和转换。

以下示例使用 Converter,该 Converterjava.lang.String 转换为带有 Jackson 2 的 Person 对象:

/*
 * Copyright 2020-2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.cassandra.example;

import java.io.IOException;

import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;

import com.fasterxml.jackson.databind.ObjectMapper;

// tag::class[]
class PersonReadConverter implements Converter<String, Person> {

	public Person convert(String source) {

		if (StringUtils.hasText(source)) {
			try {
				return new ObjectMapper().readValue(source, Person.class);
			} catch (IOException e) {
				throw new IllegalStateException(e);
			}
		}

		return null;
	}
}
// end::class[]

Registering Spring Converters with CassandraConverter

Spring Data for Apache Cassandra Java 配置提供了一种方便的方法来注册 Spring Converter 实例:MappingCassandraConverter。以下配置代码段显示了如何手动注册转换器以及配置 CustomConversions

/*
 * Copyright 2020-2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.cassandra.example;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.core.convert.CassandraCustomConversions;

// tag::class[]
@Configuration
public class ConverterConfiguration extends AbstractCassandraConfiguration {

	@Override
	public CassandraCustomConversions customConversions() {

		return CassandraCustomConversions.create(config -> {
			config.registerConverter(new PersonReadConverter()));
			config.registerConverter(new PersonWriteConverter()));
		});
	}

	// other methods omitted...

	// end::class[]
	@Override
	protected String getKeyspaceName() {
		return null;
	}
	// tag::class[]
}
// end::class[]