使用类DTO时JPA CriteriaQuery MultiSelect左连接失败的解决办法
问题背景
根据JPA CriteriaQuery API文档说明:当CriteriaQuery为自定义类X的查询(通过传入X类创建查询对象)时,multiselect的参数会传入X的构造器,每行数据返回一个X实例。
代码场景
原本使用INNER JOIN的代码可正常运行,能获取ProductWrapper列表:
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<ProductWrapper> query = cb.createQuery(ProductWrapper.class); Root<TableA> root = query.from(TableA.class); Join<TableA, TableB> tableBJoin = root.join("tabBList", JoinType.INNER); query.multiselect(root.get("col11"), tableBJoin.get("col21"));
将连接类型改为LEFT JOIN后:
Join<TableA, TableB> tableBJoin = root.join("tabBList", JoinType.LEFT);
出现实例化失败错误。
错误信息
2022-08-04 03:51:41.013 ERROR 31772 --- [nio-8080-exec-2]
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for
servlet [dispatcherServlet] in context with path [] threw exception
[Request processing failed; nested exception is
org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.QueryException: could not instantiate class
[com.exp.reports.dtos.ProductWrapper] from tuple; nested exception is
java.lang.IllegalArgumentException: org.hibernate.QueryException:
could not instantiate class [com.exp.reports.dtos.ProductWrapper from
tuple] with root cause
已尝试操作
推测左连接导致TableB的col21字段为空,给ProductWrapper添加了单参数构造器:
public ProductWrapper(String col11) {...};
但问题未解决。可切换为Tuple返回类型(左连接下正常工作),但更倾向于使用DTO类方式,希望找到无需切换到Tuple的解决方案。当前环境为Spring Boot 2.7.0 + spring-boot-starter-data-jpa:2.7.0。
解决方案
方案1:保留双参数构造器,允许接收null值
multiselect会始终传递你指定的所有参数(即使为null),不会自动匹配单参数构造器。需确保ProductWrapper存在双参数构造器,且第二个参数支持接收null:
public ProductWrapper(String col11, String col21) { this.col11 = col11; this.col21 = col21; // 允许col21为null }
左连接时,col21为null时会直接调用该构造器完成实例化。
方案2:使用构造函数表达式显式指定构造器
通过cb.construct()方法强制指定要使用的构造器,避免JPA自动推断出错:
query.select(cb.construct(ProductWrapper.class, root.get("col11"), tableBJoin.get("col21")));
此方式会明确调用双参数构造器,即使参数为null也能正常实例化DTO。
方案3:添加@ConstructorProperties注解辅助匹配(可选)
若构造器参数顺序与DTO字段顺序不一致,可在构造器上添加@ConstructorProperties注解明确参数映射:
import java.beans.ConstructorProperties; @ConstructorProperties({"col11", "col21"}) public ProductWrapper(String col11, String col21) { this.col11 = col11; this.col21 = col21; }
帮助JPA更准确地匹配查询返回字段与构造器参数。
内容的提问来源于stack exchange,提问作者samshers

