JPA原生查询结果转StudentEntity抛出ClassCastException求因
问题:JPA原生查询结果强转实体类抛出ClassCastException
问题场景
服务端接收客户端请求后,尝试将MySQL原生查询返回的结果转换为StudentEntity实体类,核心代码片段如下:
else if(operation == GET_STUDENT_SUBJECTS_DATA) { System.out.println("I'm getting students subjects data!"); StudentSubjectRequestDto studentGradesRequestData = (StudentSubjectRequestDto) inStream.readObject(); Query queryForGettingStudent = databaseJpaHandler.getEntityManager().createNativeQuery("SELECT * FROM Students S WHERE S.Email = :email ") .setParameter("email", studentGradesRequestData.getEmail()); List<StudentEntity> listOfStudentsMatched = (List<StudentEntity>)(List<?>) queryForGettingStudent.getResultList(); if(!listOfStudentsMatched.isEmpty()) { StudentEntity student = listOfStudentsMatched.get(0); // 后续查询逻辑... } }
执行StudentEntity student = listOfStudentsMatched.get(0);时抛出以下异常:
Exception in thread "Thread-2" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class org.academic.server.entities.StudentEntity ([Ljava.lang.Object; is in module java.base of loader 'bootstrap'; org.academic.server.entities.StudentEntity is in unnamed module of loader 'app')
对应的StudentEntity实体类已正确标注@Entity和@Table注解,映射数据库Students表。
异常原因
核心原因是JPA原生SQL查询的默认返回类型并非实体类实例:
- 使用
EntityManager.createNativeQuery(String sql)重载方法时,JPA不会自动将结果映射为实体类,而是返回List<Object[]>——查询结果的每一行都会被拆分为一个Object数组,数组元素对应SQL查询的每一列值,顺序与SELECT语句中的列顺序一致。 - 你代码中的
(List<StudentEntity>)(List<?>)只是编译期的强制类型转换,并没有改变运行时的实际元素类型,所以当你尝试把数组Object[]强转为StudentEntity时,必然触发ClassCastException。
解决办法
1. 让原生查询直接返回实体类
使用createNativeQuery的重载方法,指定返回的实体类类型,JPA会自动完成结果映射:
// 重载方法第二个参数指定实体类 Query queryForGettingStudent = databaseJpaHandler.getEntityManager() .createNativeQuery("SELECT * FROM Students S WHERE S.Email = :email ", StudentEntity.class) .setParameter("email", studentGradesRequestData.getEmail()); // 无需强制类型转换,直接获取实体类列表 List<StudentEntity> listOfStudentsMatched = queryForGettingStudent.getResultList();
2. 处理非实体类的DTO映射(针对第二个查询)
第二个查询需要返回StudentSubjectResponseDto(非JPA实体类),无法直接用实体类映射,可通过以下方式处理:
方式一:手动遍历数组映射
List<Object[]> resultRows = queryForGettingStudentSubjects.getResultList(); List<StudentSubjectResponseDto> responseList = new ArrayList<>(); for(Object[] row : resultRows) { StudentSubjectResponseDto dto = new StudentSubjectResponseDto(); dto.setShortcut((String) row[0]); // 对应UD.Shortcut dto.setSubjectName((String) row[1]); // 对应Subj.Name dto.setProfessorName((String) row[2]); // 对应P.FirstName + ' ' + P.LastName dto.setGrade((Integer) row[3]); // 对应SS.Grade dto.setEcts((Integer) row[4]); // 对应Subj.ECTS dto.setSpecification((String) row[5]); // 对应Subj.Specification responseList.add(dto); } outStream.writeObject(responseList);
方式二:使用@SqlResultSetMapping定义映射规则
在实体类上添加@SqlResultSetMapping,将查询列映射到DTO的字段,再通过createNativeQuery指定映射名称。
内容的提问来源于stack exchange,提问作者Hubertius
相关产品推荐
相关产品推荐

