多次调用query.from()返回错误计数的原因探究
你遇到的现象本质是多次调用from()方法会生成同一张表的交叉连接(笛卡尔积),而count函数统计的是交叉连接后的总记录数,普通查询则因为只选取单个Root的字段,最终返回的是原表的记录(而非笛卡尔积结果)。
1. 多次from()的SQL逻辑
每次调用query.from(Userr.class),JPA的Criteria API都会在SQL中添加一个对userr表的关联,默认是交叉连接(CROSS JOIN)——也就是将表与自身进行全量组合。
1个Root时,生成的SQL类似:
SELECT COUNT(u1.id) FROM userr u1结果就是原表的行数3。
2个Root时,生成的SQL类似:
SELECT COUNT(u1.id) FROM userr u1 CROSS JOIN userr u2交叉连接后总记录数是
3*3=9,count自然返回9。3个Root时,SQL为
userr u1 CROSS JOIN userr u2 CROSS JOIN userr u3,总记录数3*3*3=27,对应count结果27。
2. 带where条件的情况
当添加where cb.equal(root.get("name"), "josh")后,这个条件只作用于第一个Root(u1),会先筛选出u1中name为josh的2条记录,再和其他Root做交叉连接:
- 2个Root时,SQL类似:
结果是SELECT COUNT(u1.id) FROM userr u1 CROSS JOIN userr u2 WHERE u1.name = 'josh'2*3=6,和你测试的一致。 - 3个Root时,结果是
2*3*3=18,也符合你的数据。
3. 普通查询为什么正确?
你提到的“普通查询”应该是指select(root)而非count的情况,此时JPA会只返回第一个Root对应的实体数据,在映射结果时只提取该Root的字段,自动忽略交叉连接产生的冗余组合,因此最终还是原表的3条记录,看起来结果正常。但实际上生成的SQL依然是交叉连接,只是结果映射时做了处理。
总结
Criteria API中,from()方法用于定义查询的根实体,多次调用会增加额外的交叉连接。统计count时会计算所有交叉连接后的记录数,而普通实体查询因为只映射单个Root的数据,所以结果表现正常。正确的做法始终是只定义一个Root来查询单表数据。
内容的提问来源于stack exchange,提问作者zcb

