I recently stumbled upon this weird behaviour which I can't quite explain.
If I do
public Long getAllUsersCount() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> query = cb.createQuery(Long.class);
Root<Userr> root = query.from(Userr.class); // exp = 3; act = 3 exp = 2; act = 2
//Root<Userr> root2 = query.from(Userr.class); // exp = 3; act = 9 = 3^2 exp = 2; act = 6
//Root<Userr> root3 = query.from(Userr.class); // exp = 3; act = 27 = 3^3 exp = 2; act = 18
query.select(cb.count(root));
//query.where(cb.equal(root.get("name"), "josh"));
return entityManager.createQuery(query).getSingleResult();
}
I correctly get 3 (EXPected = ACTual).
When I uncomment root2
I get 9 which is 3 squared.
Furthermore, when I also uncomment root3
I get 27 which is 3 cubed.
Now let's comment root1
and root2
again but uncomment the where
.
With only one root
I correctly get 2 (EXPected = ACTual).
When I uncomment root2
I get 6.
And when I also uncomment root3
I get 18.
This is the data I'm working with
INSERT INTO userr (id, name, email) VALUES
(1, 'josh', 'josh@a.com'),
(2, 'josh', 'josh@b.com'),
(3, 'coby', 'coby@a.com');
It looks like the count increases as I increase the number of query.from
.
I know I should use only one root, but I just want to understand why this happens. Do you have any idea?
Also it only happens on the count. A normal select always returns the correct items, no matter how many times I do query.from
.
Both @turo comment and @rob-spoor answer helped me understand what's happening. But I wanted to elaborate a bit more to also describe what happens when we add a where clause.
Doing query.from doesn't overwrite the previouse query.from but it adds a new table to the from using the cross join. For example, starting from 1 root to n roots, this is the equivalent sql query:
SELECT COUNT(*) FROM userr;
SELECT COUNT(*) FROM userr, userr;
SELECT COUNT(*) FROM userr, ..., userr;
In my example I had a table with 3 rows.
SELECT COUNT(*) FROM userr;
= 3SELECT COUNT(*) FROM userr, userr;
= number of rows of the table resulting from the cross join between userr and userr = 3x3 = 9SELECT COUNT(*) FROM userr, userr, userr;
= 3x3x3 = 27Nice! But what happens when i add the where clause? In this case the where applies only to the first table. Let's see it in practice:
SELECT COUNT(*) FROM userr WHERE name = 'josh';
= 2SELECT COUNT(*) FROM userr, userr WHERE name = 'josh';
= take only users which name is 'josh' from the first userr table (there's two joshes), then do the cross join with the second userr table which has all three users = 2x3 = 6SELECT COUNT(*) FROM userr, userr, userr WHERE name = 'josh';
= 2x3x3 = 18But what happens when we do just a simple select and not the count?
Apparently JPA implicitly applies a DISTINCT on the primary key. That's why, no matter how many roots we have, the result is always the same as if we only had one root.