SQL 左外连接查询到 JPA 条件的转换

Posted

技术标签:

【中文标题】SQL 左外连接查询到 JPA 条件的转换【英文标题】:Conversion of SQL Left Outer Join Query to JPA Criteria 【发布时间】:2017-02-14 12:35:19 【问题描述】:

我尝试将以下本机 SQL 查询转换为 JPA 条件:

select et.* from t_empl_tx et, t_dept d 
where et.assigned_dept = d.dept (+)
  and et.employee_id = :employee_id
  and (et.start_date >= d.dept_est_date and
       et.start_date <= d.dept_close_date or
       et.start_date is null or
       d.dept is null)

(请注意,在这种情况下,(+) 大致相当于左外连接。是的,我知道它表示 OPTIONAL 表等)。

这是我对代码的尝试:

EntityManager entityManager = getEntityManager();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<EmployeeTransaction> criteriaQuery = criteriaBuilder.createQuery(EmployeeTransaction.class);
Root<EmployeeTransaction> root = criteriaQuery.from(EmployeeTransaction.class);

    // this line bombs!
Join<EmployeeTransaction, Department> join = 
    root.join(EmployeeTransaction_.assignedDepartment).join(Department_.id).join(DepartmentCompositeId_.department, JoinType.LEFT);

List<Predicate> predicates = new ArrayList<>();

predicates.add(criteriaBuilder.equal(root.get(EmployeeTransaction_.id).get(EmployeeTransactionCompositeId_.employeeId), employeeId));
predicates.add(criteriaBuilder.or(
    criteriaBuilder.and(
    criteriaBuilder.greaterThanOrEqualTo(root.<Date>get(EmployeeTransaction_.requestedStartDate), join.get(Department_.id).<Date>get(DepartmentCompositeId_.departmentCreationDate)),
    criteriaBuilder.lessThanOrEqualTo(root.<Date>get(EmployeeTransaction_.requestedStartDate), join.<Date>get(Department_.departmentCloseDate))
    ),
    criteriaBuilder.isNull(root.get(EmployeeTransaction_.requestedStartDate)),
    criteriaBuilder.isNull(join.get(Department_.id).get(DepartmentCompositeId_.departmentCreationDate))
));

criteriaQuery.select(root).where(predicates.toArray(new Predicate[]));

TypedQuery<EmployeeTransaction> query = entityManager.createQuery(criteriaQuery);
List<EmployeeTransaction> result = query.getResultList();

这个问题似乎是我试图将一个字符串列 assigedDepartment 加入到复合 ID 的单个字段中。这在 SQL 中是完全合法的,但在代码中却不是那么容易。

一种选择是转换为多个子查询,这似乎完全杀死了左外连接的点。

谁能指出我做错了什么?

杰森

【问题讨论】:

没有时间写答案,而是“从 entity1 中选择 entity1 离开加入 entity1.field2 where ...”。如果您希望左连接被急切地获取,您需要“左连接获取”。 JoinType 属性只有三个可能的值:INNER、LEFT 和 RIGHT。似乎没有左连接提取的选项。另外,join() 行也有问题。即使 join() 返回一个 SingularAttribute,我似乎也无法像这样链接命令。 在 JPA Criteria 查询中,您正在引用 SQL 查询中不存在的表 DepartmentCompositeId。请通过添加每个实体定义的相关部分来完成信息 您在外部左联接和内部联接中加入(链接)t_empl_txt_dept 表? SQL 是否在数据库客户端中工作?我的意思是纯 SQL,没有 JPA 等。也许我遗漏了一些东西,但我看到了你的标准 or d.dept is null- 它不会取消你的左连接吗? 【参考方案1】:

您应该发布您的实体,以便答案更具体。

不过,我会试一试。

如果我是对的,你可以重写查询:

select et.* 
from t_empl_tx et
    left join t_dept d on et.assigned_dept = d.dept
where 
    et.employee_id = :employee_id
    and (
        et.start_date >= d.dept_est_date 
        and et.start_date <= d.dept_close_date
        or et.start_date is null 
        or d.dept is null)

因此,很快,您必须将 JoinType.LEFT 移动到 assignedDepartment 加入:

EntityManager entityManager = getEntityManager();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<EmployeeTransaction> criteriaQuery = criteriaBuilder.createQuery(EmployeeTransaction.class);

Root<EmployeeTransaction> root = criteriaQuery.from(EmployeeTransaction.class);
Join<EmployeeTransaction, Department> department =  root.join(EmployeeTransaction_.assignedDepartment, JoinType.LEFT);
Path<Date> employeeTransactionRequestedStartDate = root.get(EmployeeTransaction_.requestedStartDate);
Path<DepartmentCompositeId> departmentId = department.get(Department_.id);
Path<Date> departmentCreationDate = departmentId.get(DepartmentCompositeId_.departmentCreationDate)
Path<Date> departmentCloseDate = departmentId.get(DepartmentCompositeId_.departmentCloseDate)

criteriaQuery.select(root).where(
    criteriaBuilder.equal(root.get(EmployeeTransaction_.id).get(EmployeeTransactionCompositeId_.employeeId), employeeId),
    criteriaBuilder.or(
        criteriaBuilder.and(
            criteriaBuilder.greaterThanOrEqualTo(employeeTransactionRequestedStartDate, departmentCreationDate)),
            criteriaBuilder.lessThanOrEqualTo(employeeTransactionRequestedStartDate, departmentCloseDate)
        ),
        criteriaBuilder.isNull(employeeTransactionRequestedStartDate),
        criteriaBuilder.isNull(departmentCreationDate)
    )
);

TypedQuery<EmployeeTransaction> query = entityManager.createQuery(criteriaQuery);
List<EmployeeTransaction> result = query.getResultList();

【讨论】:

以上是关于SQL 左外连接查询到 JPA 条件的转换的主要内容,如果未能解决你的问题,请参考以下文章

mysql中的连接查询

MySql的回顾五:多表查询下(内联/左外/右外/自连接/交叉)-1999语法

MySQLDQL之连接查询

SQL多表联查总结

5.6 进阶6:多表连接查询

sql99 连接查询