
在使用orm框架如hibernate时,数据预加载(eager fetching)是优化性能和避免懒加载异常(lazyinitializationexception)的关键技术之一。当实体之间存在多级关联,并且这些关联被配置为懒加载(fetchtype.lazy)时,如何有效地一次性加载所有所需的数据,特别是子对象中的子集合,就成了一个常见的挑战。本文将聚焦于如何利用criteriaquery实现这种多级嵌套的预加载。
我们首先来看两个关联的实体模型:Funcionario(员工)和Cargo(职位)。Funcionario与Cargo是多对一关系,而Cargo又与Treinamento(培训)是多对多关系。
Funcionario 实体
@Entity
@Table(name = "funcionarios")
public class Funcionario extends Model {
    // ... 其他属性
    @NotFound(action = NotFoundAction.IGNORE)
    @ManyToOne(fetch = FetchType.LAZY, optional = true)
    private Cargo 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; // 与Treinamento的关联,懒加载
}从上述模型可以看出,Funcionario关联的Cargo是懒加载的,而Cargo关联的treinamentosNecessarios集合也是懒加载的。我们的目标是在查询Funcionario时,同时预加载其Cargo对象以及Cargo对象内部的treinamentosNecessarios集合。
当我们需要获取Funcionario及其Cargo,并进一步获取Cargo下的treinamentosNecessarios时,如果直接使用root.fetch("cargo", JoinType.LEFT),只能预加载Cargo对象本身。尝试使用类似root.fetch("cargo.treinamentosNecessarios", JoinType.LEFT)的路径表达式来直接预加载嵌套集合是无效的,因为fetch方法通常只接受当前Root或Fetch对象的直接属性名称。
下面是常见的初始尝试代码,它能预加载Cargo,但无法预加载treinamentosNecessarios:
public Funcionario find(Long id, Session session, boolean closeSession) {
    try {
        CriteriaBuilder cb = session.getCriteriaBuilder();
        CriteriaQuery<Funcionario> criteriaQuery = cb.createQuery(Funcionario.class);
        Root<Funcionario> root = criteriaQuery.from(Funcionario.class);
        // 预加载其他关联...
        root.fetch("avaliacoes", JoinType.LEFT);
        root.fetch("treinamentosRealizados", JoinType.LEFT);
        // 预加载Cargo对象
        root.fetch("cargo", JoinType.LEFT);
        // 尝试预加载嵌套集合(此行会编译错误或不生效)
        // root.fetch("cargo.treinamentosNecessarios", JoinType.LEFT);
        criteriaQuery.select(root);
        criteriaQuery.where(cb.equal(root.get("id"), id));
        Query<Funcionario> query = session.createQuery(criteriaQuery);
        Funcionario singleResult = query.getSingleResult();
        return singleResult;
    } catch (Exception ex) {
        throw ex;
    } finally {
        if (session != null && closeSession) {
            session.close();
        }
    }
}解决这个问题的关键在于利用fetch方法返回的javax.persistence.criteria.Fetch对象。当我们在Root对象上调用fetch方法预加载一个关联对象时,该方法会返回一个Fetch对象,这个Fetch对象代表了当前已经预加载的关联。我们可以在这个返回的Fetch对象上继续调用fetch方法,以预加载其内部的关联集合。
实现步骤:
这样就形成了一个链式的预加载操作,确保了所有必要的关联数据都能在一次查询中被加载。
下面是修改后的find方法,展示了如何正确地进行多级预加载:
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;
import org.hibernate.Session; // 确保Session类型正确导入
// ... (其他必要的导入)
public class FuncionarioDAO { // 假设这是Funcionario的数据访问对象
    public Funcionario find(Long id, Session session, boolean closeSession) {
        try {
            CriteriaBuilder cb = session.getCriteriaBuilder();
            CriteriaQuery<Funcionario> criteriaQuery = cb.createQuery(Funcionario.class);
            Root<Funcionario> root = criteriaQuery.from(Funcionario.class);
            // 预加载其他关联...
            root.fetch("avaliacoes", JoinType.LEFT);
            root.fetch("treinamentosRealizados", JoinType.LEFT);
            // 关键步骤:
            // 1. 预加载Cargo对象,并获取返回的Fetch对象
            Fetch<Funcionario, Cargo> cargoFetch = root.fetch("cargo", JoinType.LEFT);
            // 2. 在cargoFetch对象上继续预加载treinamentosNecessarios集合
            cargoFetch.fetch("treinamentosNecessarios", JoinType.LEFT);
            criteriaQuery.select(root);
            criteriaQuery.where(cb.equal(root.get("id"), id));
            Query<Funcionario> query = session.createQuery(criteriaQuery);
            Funcionario singleResult = query.getSingleResult();
            return singleResult;
        } catch (Exception ex) {
            throw ex;
        } finally {
            if (session != null && closeSession) {
                session.close();
            }
        }
    }
}通过上述代码,当查询ID为id的Funcionario时,其关联的Cargo对象以及Cargo对象内部的treinamentosNecessarios集合都将被一次性加载到内存中,避免了后续访问这些懒加载集合时可能触发的额外查询或懒加载异常。
在Hibernate中使用CriteriaQuery进行多级关联对象的预加载,特别是预加载子对象中的子集合,是一个常见的需求。通过理解fetch方法返回的Fetch对象的链式调用机制,我们可以优雅地解决这一问题。这种方法不仅能有效避免懒加载异常,还能显著减少数据库查询次数,从而提升应用程序的整体性能。在实际开发中,应根据具体的业务场景和性能需求,灵活运用CriteriaQuery的预加载功能。
以上就是使用CriteriaQuery进行多级关联对象的预加载:解决子列表懒加载问题的详细内容,更多请关注php中文网其它相关文章!
                        
                        每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号