如何为ProxyProjectionFactory添加自定义转换器支持接口式投影?
问题描述
升级至Spring Boot 3.2及对应的Hibernate 6.2后,数据库枚举值的列类型默认从integer变为smallint。我们将数据库列改为smallint后,Hibernate会将其映射为short类型,这导致包含枚举的接口式投影出现转换异常:
示例投影接口:
public interface MyProjection { MyEnum getEnum(); }
抛出的错误:
java.lang.UnsupportedOperationException: Cannot project java.lang.Short to com.bbraun.nemo.enums.MyEnum; Target type is not an interface and no matching Converter found at org.springframework.data.projection.ProjectingMethodInterceptor.potentiallyConvertResult(ProjectingMethodInterceptor.java:110) at org.springframework.data.projection.ProjectingMethodInterceptor.invoke(ProjectingMethodInterceptor.java:80) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.data.projection.ProxyProjectionFactory$TargetAwareMethodInterceptor.invoke(ProxyProjectionFactory.java:243) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:249)
已尝试的无效方案
编写了Short转枚举的自定义转换器工厂:
@Component final class ShortToEnumConverterFactory implements ConverterFactory<Short, Enum> { @Override public <T extends Enum> Converter<Short, T> getConverter(Class<T> targetType) { return new ShortToEnum(targetType); } private static class ShortToEnum<T extends Enum> implements Converter<Short, T> { private final Class<T> enumType; public ShortToEnum(Class<T> enumType) { this.enumType = enumType; } @Override public T convert(Short source) { return this.enumType.getEnumConstants()[source]; } } }
并通过两种方式注册:
- 在配置类中添加到
FormatterRegistry:
@Configuration ... @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(new ShortToEnumConverterFactory()); } ...
- 添加到
DefaultConversionService共享实例:
DefaultConversionService sharedInstance = (DefaultConversionService)DefaultConversionService.getSharedInstance(); sharedInstance.addConverterFactory(new ShortToEnumConverterFactory());
以上操作均无效,原因是ProxyProjectionFactory内部使用了自身创建的静态DefaultConversionService实例,而非共享实例,导致自定义转换器无法被加载。
解决方案
提供几种可行的解决思路:
方案1:自定义ProxyProjectionFactory并注入转换器
创建自定义的ProxyProjectionFactory,通过反射将自定义转换器添加到它的内部静态转换服务,再注册为Spring Bean:
@Configuration public class ProjectionConfig { @Bean public ProjectionFactory projectionFactory() { ProxyProjectionFactory factory = new ProxyProjectionFactory(); try { Field conversionServiceField = ProxyProjectionFactory.class.getDeclaredField("CONVERSION_SERVICE"); conversionServiceField.setAccessible(true); GenericConversionService conversionService = (GenericConversionService) conversionServiceField.get(null); conversionService.addConverterFactory(new ShortToEnumConverterFactory()); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException("Failed to add converter to ProxyProjectionFactory's conversion service", e); } return factory; } }
注意:该方案依赖反射访问私有静态字段,若Spring Data Commons后续修改字段定义可能失效。
方案2:强制Hibernate将枚举映射为Integer
在枚举字段上显式指定Hibernate类型为Integer,即使数据库列是smallint,也会以Integer类型映射,避免Short到枚举的转换问题:
@Entity public class MyEntity { @Enumerated(EnumType.ORDINAL) @Column(columnDefinition = "smallint") @Type(type = "org.hibernate.type.IntegerType") private MyEnum myEnum; // 其他字段和方法 }
方案3:改用类式投影
替换接口式投影为类式投影,Spring Data会直接通过构造函数或Setter注入值,无需依赖投影转换服务:
public class MyProjection { private final MyEnum myEnum; public MyProjection(MyEnum myEnum) { this.myEnum = myEnum; } public MyEnum getEnum() { return myEnum; } }
方案4:通过BeanPostProcessor扩展转换服务
实现BeanPostProcessor,在ProxyProjectionFactory初始化后,将自定义转换器添加到它的内部转换服务:
@Component public class ProjectionConversionServicePostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof ProxyProjectionFactory) { try { Field conversionServiceField = ProxyProjectionFactory.class.getDeclaredField("CONVERSION_SERVICE"); conversionServiceField.setAccessible(true); GenericConversionService conversionService = (GenericConversionService) conversionServiceField.get(null); conversionService.addConverterFactory(new ShortToEnumConverterFactory()); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException("Failed to customize ProxyProjectionFactory conversion service", e); } } return bean; } }
内容的提问来源于stack exchange,提问作者CodeWizard1985
相关产品推荐
相关产品推荐

