Hibernate 6 criteria subquery

Hibernate 6 subquery number of relations.

org.springframework.dao.InvalidDataAccessResourceUsageException: Error interpreting query [SqmRoot not yet resolved to TableGroup]

What is the correct way of querying the number of related items with Criteria API?

I want to get back the below DTO, I have a OneToMany relation, I am interested about the number of related items.

I could not figure it out with AI, it might be a bug.

Example code: DTO:

package com.example.demo.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Dto {

    private Long id;

    private Long count;

}

Entities:

package com.example.demo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import lombok.Data;

import java.util.Set;

import static jakarta.persistence.GenerationType.IDENTITY;

@Data
@Entity
public class One {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "one")
    private Set<Many> many;

}

package com.example.demo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Data;

import static jakarta.persistence.FetchType.LAZY;
import static jakarta.persistence.GenerationType.IDENTITY;

@Data
@Entity
public class Many {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    @ManyToOne(fetch = LAZY, optional = false)
    private One one;
}

Repository:

package com.example.demo.repository;

import com.example.demo.dto.Dto;
import com.example.demo.entity.Many;
import com.example.demo.entity.One;
import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
@RequiredArgsConstructor
public class OneRepositoryCustom {

    private final EntityManager entityManager;

    public List<Dto> query() {

        CriteriaBuilder cb = entityManager.getCriteriaBuilder();

        CriteriaQuery<Dto> cq = cb.createQuery(Dto.class);
        Root<One> root = cq.from(One.class);

        Subquery<Long> countMany = cq.subquery(Long.class);
        Root<One> correlatedRoot = countMany.correlate(root);
        Root<Many> manyRoot = countMany.from(Many.class);
        countMany.select(cb.count(manyRoot));

        countMany.where(cb.equal(manyRoot.get("one"), correlatedRoot));

        cq.select(cb.construct(Dto.class, root.get("id"), countMany.getSelection()));
        return entityManager.createQuery(cq).getResultList();

    }

}

POM.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.0.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>25</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

This is why you still need to understand what you’re doing and can’t just blindly trust the AI..
You don’t need a subquery to calculate the count, you can just group by the id.

    CriteriaBuilder cb = entityManager.getCriteriaBuilder();

    CriteriaQuery<Dto> cq = cb.createQuery(Dto.class);
    Root<One> root = cq.from(One.class);
    SetJoin<Many> manyJoin = root.joinSet("many");

    cq.groupBy(root.get("id"));
    cq.select(cb.construct(Dto.class, root.get("id"), cb.count(cb.literal(1))));
    return entityManager.createQuery(cq).getResultList();

Yes, thanks, I also got this answer, but it not works with 2 relation, I want a subquery to work please, it was working in previous version.

Then you have to be more specific.

    CriteriaBuilder cb = entityManager.getCriteriaBuilder();

    CriteriaQuery<Dto> cq = cb.createQuery(Dto.class);
    Root<One> root = cq.from(One.class);

    Subquery<Long> countMany = cq.subquery(Long.class);
    Root<One> correlatedRoot = countMany.correlate(root);
    SetJoin<Root, Many> manyJoin = countMany.joinSet("many");
    countMany.select(cb.count(manyJoin));
    cq.select(cb.construct(Dto.class, root.get("id"), countMany));

I do not see the joinSet method of the Subquery on Jakarta Persistence 3.2.
What version are you using?
I do not want my query to contain a join.

I’m sorry, that’s a typo. It should have been

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

CriteriaQuery<Dto> cq = cb.createQuery(Dto.class);
Root<One> root = cq.from(One.class);

Subquery<Long> countMany = cq.subquery(Long.class);
Root<One> correlatedRoot = countMany.correlate(root);
SetJoin<Root, Many> manyJoin = correlatedRoot.joinSet("many");
countMany.select(cb.count(manyJoin));
cq.select(cb.construct(Dto.class, root.get("id"), countMany));

Works fine, without a join in querry, many thanks.