问题原因
org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type
[org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [com.example.IdsOnly]
JPA版本 1.11
报错原因是没有目标转换器
转换器使用的是 Spring-core 包中的DefaultConversionService JPA相关代码构建代码位于ResultProcess类中和内部类ProjectingConverter
下面是JPA中的代码
@RequiredArgsConstructor
private static class ProjectingConverter implements Converter<Object, Object> {
...
@Override
public Object convert(Object source) {
Class<?> targetType = type.getReturnedType();
if (targetType.isInterface()) {
return factory.createProjection(targetType, getProjectionTarget(source));
}
//从这里开始,调用DefaultConversionService的convert方法
return conversionService.convert(source, targetType);
}
...
}
大神可以研究下DefaultConversionService为啥 native 查询没有转换器
解决方法
方案一
repository 返回 Tuple 对象,自己写代码手动转换为指定对象
上代码
repository层使用native查询
@Query(value = "select id,nt from position",nativeQuery = true)
List<Tuple> testFind();
//目标实体
@Data
public class PositionTest {
private String id;
private String nt;
}
工具类(依赖Spring中的BeanUtils)
class NativeResultProcessUtils {
/**
* tuple转实体对象
* @param source tuple对象
* @param targetClass 目标实体class
* @param <T> 目标实体类型
* @return 目标实体
*/
public static <T> T processResult(Tuple source,Class<T> targetClass) {
Object instantiate = BeanUtils.instantiate(targetClass);
convertTupleToBean(source,instantiate,null);
return (T) instantiate;
}
/**
*
* tuple转实体对象
* @param source tuple对象
* @param targetClass 目标实体class
* @param <T> 目标实体类型
* @param ignoreProperties 要忽略的属性
* @return 目标实体
*/
public static <T> T processResult(Tuple source,Class<T> targetClass,String... ignoreProperties) {
Object instantiate = BeanUtils.instantiate(targetClass);
convertTupleToBean(source,instantiate,ignoreProperties);
return (T) instantiate;
}
/**
* 把tuple中属性名相同的值复制到实体中
* @param source tuple对象
* @param target 目标对象实例
*/
public static void convertTupleToBean(Tuple source,Object target){
convertTupleToBean(source,target,null);
}
/**
* 把tuple中属性名相同的值复制到实体中
* @param source tuple对象
* @param target 目标对象实例
* @param ignoreProperties 要忽略的属性
*/
public static void convertTupleToBean(Tuple source,Object target, String... ignoreProperties){
//目标class
Class<?> actualEditable = target.getClass();
//获取目标类的属性信息
PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);
//忽略列表
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
//遍历属性节点信息
for (PropertyDescriptor targetPd : targetPds) {
//获取set方法
Method writeMethod = targetPd.getWriteMethod();
//判断字段是否可以set
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
//获取source节点对应的属性
String propertyName = targetPd.getName();
Object value = source.get(propertyName);
if(value!=null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], value.getClass())) {
try {
//判断target属性是否private
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
//写入target
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
使用方法
List<Tuple> positionTests = inspectPositionRepository.testFind();
positionTests.stream().map(tuple->{
//使用工具类将tuple转为对象
PositionTest positionTest = NativeResultProcessUtils.processResult(tuple, PositionTest.class);
return positionTest;
}).forEach(System.out::println);
方案二
使用EntityManager 获得hibernate Query对象,使用hibernate Query返回Map对象,然后手动将Map转为实体类
上代码
Query nativeQuery = entityManager.createNativeQuery("select id,nt from position");
//不同hibernate版本写法不一样
NativeQueryImplementor nativeQueryImplementor = nativeQuery
.unwrap(NativeQueryImpl.class)
.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
List<Map> resultList = nativeQueryImplementor.getResultList();
//手动转换为实体对象
... //此处省略转换过程
方案三
升级JPA版本到2.1
JPA 2.1 版本增加了对@SqlResultSetMapping的支持,所以native查询也能像其他查询一样正常使用
@SqlResultSetMapping(
name = "testFind", // 如果@Query 不指定name 会默认使用方法名
classes = {
@ConstructorResult(
targetClass = domain.io.PositionTest.class
columns = {
@ColumnResult( name = "id", type = String.class),
@ColumnResult( name = "nt", type = String.class)
}
)
}
)
@Data
public class PositionTest {
private String id;
private String nt;
}
repository层写法
@Query(value = "select id,nt from position",nativeQuery = true)
List<PositionTest> testFind();