
在构建数据访问层时,我们经常需要从数据库中加载一个实体及其关联的实体。当关联实体内部又包含一个集合时,如果希望一次性加载所有相关数据,就需要进行嵌套的预加载(eager fetching)。
考虑以下两个JPA实体模型:
Funcionario (员工) 实体
@Entity
@Table(name = "funcionarios")
public class Funcionario extends Model {
    // ... 其他属性 ...
    @NotFound(action = NotFoundAction.IGNORE)
    @ManyToOne(fetch = FetchType.LAZY, optional = true)
    private Cargo cargo; // 员工所属的职位,默认懒加载
    // ...
}Cargo (职位) 实体
@Entity
@Table(name = "cargos")
public class Cargo extends Model {
    @Column(nullable = false, unique = true, columnDefinition = "TEXT")
    private String cargo = "";
    @ManyToMany(fetch = FetchType.LAZY)
    private Set<Treinamento> treinamentosNecessarios; // 职位所需的培训,默认懒加载
}我们的目标是:当查询Funcionario时,不仅要预加载其关联的Cargo对象,还要进一步预加载Cargo对象内部的treinamentosNecessarios集合。
在使用CriteriaQuery进行预加载时,通常会使用root.fetch()方法。例如,要预加载Funcionario的cargo,可以这样做:
Root<Funcionario> root = criteriaQuery.from(Funcionario.class);
root.fetch("cargo", JoinType.LEFT); // 预加载Cargo然而,如果尝试直接在root上通过点号路径来预加载cargo内部的treinamentosNecessarios,如下所示:
// 错误尝试:无法直接在Root上通过点号路径预加载嵌套集合
// root.fetch("cargo.treinamentosNecessarios", JoinType.LEFT);这种方式是无效的,因为root代表的是Funcionario实体,它不直接拥有treinamentosNecessarios这个属性,treinamentosNecessarios是Cargo实体的属性。CriteriaQuery需要更明确地指定预加载的上下文。
解决这个问题的关键在于,fetch方法会返回一个Fetch对象,这个Fetch对象代表了当前预加载的关联。我们可以在这个Fetch对象上继续调用fetch方法,从而实现嵌套的预加载。
以下是实现嵌套预加载的正确方法:
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Fetch; // 注意引入Fetch类
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Root;
import org.hibernate.query.Query; // 如果使用Hibernate的Query接口
// ... 在你的数据访问方法中 ...
public Funcionario findFuncionarioWithNestedEagerLoading(Long id, Session session) {
    try {
        CriteriaBuilder cb = session.getCriteriaBuilder();
        CriteriaQuery<Funcionario> criteriaQuery = cb.createQuery(Funcionario.class);
        Root<Funcionario> root = criteriaQuery.from(Funcionario.class);
        // 1. 预加载Funcionario的cargo关联
        // root.fetch("cargo", JoinType.LEFT) 会返回一个 Fetch<Funcionario, Cargo> 对象
        Fetch<Funcionario, Cargo> cargoFetch = root.fetch("cargo", JoinType.LEFT);
        // 2. 在cargoFetch对象上继续调用fetch,预加载Cargo的treinamentosNecessarios集合
        // cargoFetch.fetch("treinamentosNecessarios", JoinType.LEFT) 会返回一个 Fetch<Cargo, Treinamento> 对象
        cargoFetch.fetch("treinamentosNecessarios", JoinType.LEFT);
        // (可选) 如果Funcionario还有其他需要预加载的关联,可以继续添加
        // root.fetch("avaliacoes", JoinType.LEFT);
        // root.fetch("treinamentosRealizados", JoinType.LEFT);
        criteriaQuery.select(root);
        criteriaQuery.where(cb.equal(root.get("id"), id)); // 根据ID过滤
        Query<Funcionario> query = session.createQuery(criteriaQuery);
        Funcionario singleResult = query.getSingleResult();
        return singleResult;
    } catch (Exception ex) {
        // 异常处理
        throw new RuntimeException("Error fetching Funcionario with nested eager loading", ex);
    }
}代码解释:
通过这种链式调用fetch的方法,CriteriaQuery能够正确地构建查询,生成包含所有必要联接(JOIN)的SQL语句,从而一次性加载所有相关数据。
通过链式调用Fetch对象上的fetch方法,我们能够灵活且精确地控制JPA CriteriaQuery的预加载行为,实现对嵌套关联集合的有效加载。这种方法不仅避免了N+1查询问题,提高了数据访问效率,也使得数据访问逻辑更加清晰和可维护。在设计数据访问层时,理解并掌握这种嵌套预加载技巧对于构建高性能的企业级应用至关重要。
以上就是使用CriteriaQuery实现嵌套对象集合的预加载的详细内容,更多请关注php中文网其它相关文章!
 
                        
                        每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
 
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号