JPQL使用JpaResultMapper映射DTO报无匹配构造函数问题咨询
你遇到的报错本质是qlrm组件的JpaResultMapper实现逻辑决定的:它做结果映射时,只会根据查询返回的列数量、类型、顺序,去DTO里找参数列表完全匹配的构造函数,通过构造函数实例化对象,不会调用无参构造+setter给属性赋值。所以你查1个String类型的字段,它就找只有1个String参数的构造函数,查2个String字段就找对应2个参数的构造函数,字段组合多了之后,给DTO写一堆重载构造函数确实完全不可维护。
你不需要给DTO写所有字段组合对应的构造函数,根据你的技术栈选下面任意一种方案即可:
方案1:使用JPA原生构造投影,移除QLRM依赖
JPA规范原生支持DTO投影,不需要引入第三方映射工具,直接在JPQL中用new关键字调用DTO构造方法即可,示例代码:public CardDTO test1() { // 注意写DTO的全限定类名,传入的字段顺序、类型要和构造函数一致 String jpql = "SELECT new com.xxx.CardDTO(c.test1) FROM CardEntity c"; TypedQuery<CardDTO> query = entityManager.createQuery(jpql, CardDTO.class); try { return query.getSingleResult(); } catch (NoResultException e) { return null; } }这个方案无额外依赖,稳定性最高,适合查询场景固定、字段组合少的项目,缺点是如果字段组合非常多,还是需要写对应构造函数。
方案2:替换结果映射器,走setter赋值(改动最小,最推荐)
把QLRM的映射器换成支持属性赋值的结果转换器,只要你的DTO保留无参构造、有对应字段的setter方法(你现在用Lombok的@Data+@NoArgsConstructor已经满足要求),查询时给列起和DTO属性名一致的别名,转换器会自动调用setter赋值,不需要写任何重载构造函数,不管查几个字段都能适配。
以Hibernate内置的别名转Bean转换器为例,代码改造成本极低:public CardDTO test1() { // 注意给查询字段加别名,和DTO属性名完全一致 String jpql = "SELECT c.test1 as test1 FROM CardEntity c"; Query query = entityManager.createQuery(jpql); // 替换原有的JpaResultMapper逻辑,用Hibernate内置转换器 try { return (CardDTO) query.unwrap(org.hibernate.query.Query.class) .setResultTransformer(new org.hibernate.transform.AliasToBeanResultTransformer(CardDTO.class)) .getSingleResult(); } catch (NoResultException nre) { return null; } }后续你要查test2、test3或者任意字段组合,只需要修改JPQL里的查询字段、保持别名和DTO属性名一致即可,不需要修改DTO的代码。如果是Hibernate 6+版本,将过时的
setResultTransformer替换为官方提供的TupleTransformer实现即可,逻辑完全一致。方案3:使用接口投影,无需编写DTO实现类
如果不同查询的字段组合差异极大,你甚至不需要写CardDTO这种普通类,只需要定义对应字段getter的接口,JPA会自动生成代理对象映射结果,完全不需要写构造函数、也不需要写setter。比如只查test1时定义接口:public interface CardSimpleView { String getTest1(); }查询时直接把返回类型改成对应接口即可,适合字段组合多、DTO不需要承载业务逻辑的场景。
避坑提示:不管用哪种映射方案,只要是走属性/setter赋值,一定要给查询的字段加和DTO/接口属性名一致的别名,否则会出现字段映射为空的问题。
内容的提问来源于stack exchange,提问作者choding

