如何自定义JdbcClient所使用的ConversionService?
JdbcClient自定义类型转换失败:如何配置其ConversionService
问题场景
使用JdbcClient读取数据库记录时,需要将字符串列转换为自定义PersonName类型,已实现StringToPersonNameConverter转换器并尝试注册到Spring容器,但始终触发org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [com.example.demo.DemoApplication$PersonName]异常。希望直接通过配置ConversionService解决,避免使用RowMapper。
环境:Java 17 + Spring Boot 3.3.0
原测试代码:
package com.example.demo; import java.util.List; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.format.FormatterRegistry; import org.springframework.jdbc.core.simple.JdbcClient; import org.springframework.stereotype.Component; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import jakarta.annotation.PostConstruct; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { System.out.println("*** DemoApplication.main"); SpringApplication.run(new Class[]{ MyWebConfiguration.class, MyOtherConfiguration.class, DemoApplication.class }, args); } private final JdbcClient jdbcClient; public DemoApplication(JdbcClient jdbcClient) { System.out.println("*** DemoApplication.DemoApplication"); this.jdbcClient = jdbcClient; } @PostConstruct public void init() { final List<Person> list = jdbcClient.sql("select 'John;Doe' as \"name\" from dual") .query(Person.class) .list(); System.out.println("list = " + list); } @Configuration public static class MyWebConfiguration implements WebMvcConfigurer { private final StringToPersonNameConverter stringToPersonNameConverter; public MyWebConfiguration(StringToPersonNameConverter stringToPersonNameConverter) { System.out.println("*** MyWebConfiguration.MyWebConfiguration"); this.stringToPersonNameConverter = stringToPersonNameConverter; } @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(stringToPersonNameConverter); } } @Configuration public static class MyOtherConfiguration { private final ConfigurableEnvironment environment; private final ConverterRegistry converterRegistry; private final GenericConversionService conversionService; private final StringToPersonNameConverter stringToPersonNameConverter; public MyOtherConfiguration( ConfigurableEnvironment environment, ConverterRegistry converterRegistry, GenericConversionService conversionService, StringToPersonNameConverter stringToPersonNameConverter) { System.out.println("*** MyOtherConfiguration.MyOtherConfiguration"); this.environment = environment; this.converterRegistry = converterRegistry; this.conversionService = conversionService; this.stringToPersonNameConverter = stringToPersonNameConverter; } @PostConstruct public void addCustomConverters() { environment.getConversionService().addConverter(stringToPersonNameConverter); converterRegistry.addConverter(stringToPersonNameConverter); conversionService.addConverter(stringToPersonNameConverter); } } @Component @ConfigurationPropertiesBinding public static class StringToPersonNameConverter implements Converter<String, PersonName> { public StringToPersonNameConverter() { System.out.println("*** StringToPersonNameConverter.StringToPersonNameConverter"); } @Override public PersonName convert(String source) { if (source.contains(";")) { String[] parts = source.split(";", 2); return new PersonName(parts[0], parts[1]); } return new PersonName(source, "?"); } } public record Person(PersonName name) { } public record PersonName(String firstName, String lastName) { } }
问题原因
JdbcClient默认使用内部独立的GenericConversionService实例,不会自动复用Spring容器中的全局ConversionService。之前将转换器注册到WebMvc或环境的ConversionService中,对JdbcClient完全无效。
解决方案
通过自定义JdbcClient Bean,在构建JdbcClient时手动注入包含自定义转换器的ConversionService。
修改后的代码
package com.example.demo; import java.util.List; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.jdbc.core.simple.JdbcClient; import org.springframework.stereotype.Component; import jakarta.annotation.PostConstruct; import javax.sql.DataSource; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } private final JdbcClient jdbcClient; public DemoApplication(JdbcClient jdbcClient) { this.jdbcClient = jdbcClient; } @PostConstruct public void init() { final List<Person> list = jdbcClient.sql("select 'John;Doe' as \"name\" from dual") .query(Person.class) .list(); System.out.println("list = " + list); } // 自定义JdbcClient配置 @Configuration static class JdbcClientConfig { @Bean JdbcClient jdbcClient(DataSource dataSource, StringToPersonNameConverter converter) { // 创建包含自定义转换器的ConversionService GenericConversionService conversionService = new GenericConversionService(); conversionService.addConverter(converter); return JdbcClient.builder(dataSource) .conversionService(conversionService) // 替换默认的ConversionService .build(); } } @Component public static class StringToPersonNameConverter implements Converter<String, PersonName> { @Override public PersonName convert(String source) { if (source.contains(";")) { String[] parts = source.split(";", 2); return new PersonName(parts[0], parts[1]); } return new PersonName(source, "?"); } } public record Person(PersonName name) { } public record PersonName(String firstName, String lastName) { } }
关键要点
- 移除冗余配置:删除原代码中
MyWebConfiguration和MyOtherConfiguration的转换器注册逻辑,这些配置对JdbcClient不起作用。 - 自定义JdbcClient构建:通过
JdbcClient.builder(dataSource)获取构建器,调用conversionService()方法传入包含自定义转换器的实例,覆盖默认的内部ConversionService。 - 转换器简化:
StringToPersonNameConverter只需保留@Component注解,作为Spring组件被注入即可,无需额外注册到其他ConversionService。
内容的提问来源于stack exchange,提问作者Kristof Neirynck
相关产品推荐
相关产品推荐

