解决JPA Native 查询不能使用投影(Projection)的问题 org.springframework.core.convert.ConverterNotFoundException:

问题原因
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();
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值