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

本文探讨了在使用Spring Boot框架与JPA进行数据库操作时遇到的ConverterNotFoundException错误,详细解析了错误原因并提供了三种解决方案:手动转换Tuple对象、使用EntityManager与Map对象转换以及升级JPA版本至2.1利用@SqlResultSetMapping注解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题原因

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();

转载自 Activiti交流社区 youngboy.vip

### Spring Data JPA 中 `org.springframework.data.jpa.repository` 包不存在的解决方案 在开发基于 Spring Data JPA 的应用程序时,如果遇到 `org.springframework.data.jpa.repository` 包不存在的问题,通常是由于项目依赖配置不正确或缺少必要的依赖项。以下是解决问题的详细方法[^1]: #### 1. 检查 Maven 或 Gradle 配置 确保项目的构建工具(如 Maven 或 Gradle)中已正确添加 Spring Data JPA 的依赖项。对于 Maven 用户,需要在 `pom.xml` 文件中添加以下依赖项: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>所需版本号</version> </dependency> ``` 对于 Gradle 用户,需要在 `build.gradle` 文件中添加以下内容: ```gradle implementation 'org.springframework.boot:spring-boot-starter-data-jpa:所需版本号' ``` 上述依赖项会自动引入 `org.springframework.data.jpa.repository` 包及其相关功能[^2]。 #### 2. 确认 Spring Boot 版本 如果使用的是 Spring Boot 项目,请确保所使用的 Spring Boot 版本与 Spring Data JPA 的版本兼容。可以通过查看官方文档确认版本兼容性[^1]。例如,Spring Boot 2.x 默认集成了 Spring Data JPA 的特定版本。 #### 3. 更新项目依赖 在修改了 `pom.xml` 或 `build.gradle` 文件后,执行以下命令以更新项目依赖: - 对于 Maven 项目: ```bash mvn clean install ``` - 对于 Gradle 项目: ```bash gradle clean build ``` #### 4. 检查 IDE 配置 有时,IDE 的缓存可能导致依赖项未正确加载。尝试以下操作以解决问题: - 在 IntelliJ IDEA 中,点击 **File > Invalidate Caches / Restart**。 - 在 Eclipse 中,右键单击项目并选择 **Maven > Update Project**。 #### 5. 验证类路径 确保项目构建完成后,`org.springframework.data.jpa.repository` 包已被正确加载到类路径中。可以通过以下方式验证: - 查看生成的 `target` 或 `build` 目录中的依赖树。 - 使用以下命令检查 Maven 依赖树: ```bash mvn dependency:tree ``` 如果仍未找到该包,可能需要手动检查依赖冲突或排除不必要的依赖项。 --- ### 示例代码:定义一个继承自 `JpaRepository` 的接口 以下是一个简单的示例,展示如何定义一个继承自 `JpaRepository` 的接口: ```java import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository<Product, Long> { // 可以在此添加自定义查询方法 } ``` 其中,`Product` 是实体类,`Long` 是主键类型。 --- ### 注意事项 如果以上步骤仍无法解决问题,可能是由于网络问题导致依赖下载失败。可以尝试以下方法: - 检查本地 Maven 或 Gradle 缓存目录是否包含所需的依赖项。 - 更改 Maven 或 Gradle 的镜像源为国内镜像(如阿里云镜像)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值