
本教程详细介绍了在jpa和hibernate中,如何通过关联实体字段组合查询条件。针对多对一关系,我们将演示如何使用jpql和criteria api高效地实现基于多个关联表uuid的实体检索,避免常见错误,确保查询逻辑的准确性和可维护性。
在企业级应用开发中,数据模型往往包含复杂的关联关系。当我们需要根据多个关联实体(如多对一关系中的外键实体)的特定属性来筛选主实体时,如何高效且正确地构建查询语句是一个常见的挑战。本文将以一个具体的 Queue 实体为例,演示如何基于其关联的 Location 和 QueueRoom 实体的 UUID 进行组合查询。
问题场景分析
假设我们有一个 Queue 实体,它与 Location 和 QueueRoom 实体存在多对一(ManyToOne)关联。Queue 实体模型简化如下:
import javax.persistence.*;
@Entity
@Table(name = "queue")
public class Queue {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "queue_id")
private Integer queueId;
@ManyToOne
@JoinColumn(name = "location_id", nullable = false)
private Location location;
@ManyToOne
@JoinColumn(name = "queue_room_id", nullable = true)
public QueueRoom queueRoom;
// Getters and Setters
public Integer getQueueId() { return queueId; }
public void setQueueId(Integer queueId) { this.queueId = queueId; }
public Location getLocation() { return location; }
public void setLocation(Location location) { this.location = location; }
public QueueRoom getQueueRoom() { return queueRoom; }
public void setQueueRoom(QueueRoom queueRoom) { this.queueRoom = queueRoom; }
}
@Entity
@Table(name = "location")
public class Location {
@Id
private String uuid; // Assuming uuid is the primary key or a unique identifier
// ... other fields
public String getUuid() { return uuid; }
public void setUuid(String uuid) { this.uuid = uuid; }
}
@Entity
@Table(name = "queue_room")
public class QueueRoom {
@Id
private String uuid; // Assuming uuid is the primary key or a unique identifier
// ... other fields
public String getUuid() { return uuid; }
public void setUuid(String uuid) { this.uuid = uuid; }
}我们的目标是查询所有满足特定 locationUuid 和 queueRoomUuid 的 Queue 记录。初学者在尝试使用旧版 Hibernate Criteria API 时,可能会遇到以下类似的代码结构:
// 示例:旧版 Hibernate Criteria API 的常见尝试,但并非最佳实践 // 假设 getCurrentSession() 返回一个 Hibernate Session // 且 includeVoidedObjects 是一个用于添加通用限制的方法 // 此代码片段仅为说明问题,不推荐在新项目中使用 public ListgetAllQueuesByLocationAndQueueRoom(String locationUuid, String queueRoomUuid) { // import org.hibernate.Criteria; // import org.hibernate.criterion.Restrictions; // import org.hibernate.Session; // Session currentSession = getCurrentSession(); // 获取当前Session // Criteria criteria = currentSession.createCriteria(Queue.class, "q"); // // 假设 includeVoidedObjects 是一个用于添加通用限制的方法 // // includeVoidedObjects(criteria, false); // // 为 location 关联创建 Criteria,并添加限制 // Criteria locationCriteria = criteria.createCriteria("location", "ql"); // locationCriteria.add(Restrictions.eq("ql.uuid", locationUuid)); // // 为 queueRoom 关联创建 Criteria,并添加限制 // Criteria queueRoomCriteria = criteria.createCriteria("queueRoom", "qr"); // queueRoomCriteria.add(Restrictions.eq("qr.uuid", queueRoomUuid)); // // 返回结果,这里可能只考虑了 queueRoomCriteria 的限制,或者行为不明确 // return (List ) queueRoomCriteria.list(); return new ArrayList<>(); // 占位符,避免编译错误 }
上述代码的问题在于,criteria.createCriteria("location", "ql") 和 criteria.createCriteria("queueRoom", "qr") 虽然都从根 criteria 创建,但它们返回的是针对各自关联实体的新 Criteria 实例。直接调用 queueRoomCriteria.list() 可能会导致 locationCriteria 上添加的限制未能有效应用到最终的查询结果中,或者查询逻辑不够清晰。正确的做法是将所有条件逻辑地组合起来,并应用于根查询。
解决方案一:使用 JPA JPQL (Java Persistence Query Language)
JPQL 是 JPA 提供的一种面向对象的查询语言,它允许我们直接在实体模型上进行查询,语法类似于 SQL 但操作的是实体和它们的属性。对于关联字段的组合查询,JPQL 提供了一种非常直观和简洁的方式。
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.List;
public class QueueRepository {
private final EntityManager entityManager; // 假设通过依赖注入获取
public QueueRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
/**
* 使用 JPQL 根据 Location UUID 和 QueueRoom UUID 查询 Queue 列表
* @param locationUuid Location 的 UUID
* @param queueRoomUuid QueueRoom 的 UUID
* @return 符合条件的 Queue 列表
*/
public List getAllQueuesByLocationAndQueueRoomJPQL(String locationUuid, String queueRoomUuid) {
String jpql = "SELECT q FROM Queue q " +
"WHERE q.location.uuid = :location_uuid " +
"AND q.queueRoom.uuid = :room_uuid";
TypedQuery query = entityManager.createQuery(jpql, Queue.class);
query.setParameter("location_uuid", locationUuid);
query.setParameter("room_uuid", queueRoomUuid);
return query.getResultList();
}
} 代码解析:
- SELECT q FROM Queue q: 声明我们要查询 Queue 实体,并为其指定别名 q。
- WHERE q.location.uuid = :location_uuid: 通过点号操作符 (.) 轻松地遍历 Queue 实体到其关联的 Location 实体,并访问 Location 的 uuid 属性。
- AND q.queueRoom.uuid = :room_uuid: 使用 AND 关键字将两个条件逻辑地组合起来。同样,通过点号操作符访问 QueueRoom 的 uuid 属性。
- :location_uuid 和 :room_uuid: 这是命名参数,用于安全地传递查询值,防止 SQL 注入。
JPQL 的优势在于其简洁性和可读性,特别适合于静态、结构相对固定的查询。
解决方案二:使用 JPA Criteria API
JPA Criteria API 提供了一种类型安全、编程化的方式来构建查询。它非常适合于动态查询,即查询条件可能在运行时根据业务逻辑变化的场景。
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
public class QueueRepository {
private final EntityManager entityManager; // 假设通过依赖注入获取
public QueueRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
/**
* 使用 JPA Criteria API 根据 Location UUID 和 QueueRoom UUID 查询 Queue 列表
* @param locationUuid Location 的 UUID
* @param queueRoomUuid QueueRoom 的 UUID
* @return 符合条件的 Queue 列表
*/
public List getAllQueuesByLocationAndQueueRoomCriteria(String locationUuid, String queueRoomUuid) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery criteria = builder.createQuery(Queue.class);
Root queueRoot = criteria.from(Queue.class);
// 方法一:将所有条件通过 builder.and() 组合起来
criteria.where(
builder.and(
builder.equal(queueRoot.get("location").get("uuid"), locationUuid),
builder.equal(queueRoot.get("queueRoom").get("uuid"), queueRoomUuid)
)
);
// 方法二:动态构建 Predicate 列表,然后组合
// List predicates = new ArrayList<>();
// predicates.add(builder.equal(queueRoot.get("location").get("uuid"), locationUuid));
// predicates.add(builder.equal(queueRoot.get("queueRoom").get("uuid"), queueRoomUuid));
// criteria.where(builder.and(predicates.toArray(new Predicate[0])));
return entityManager.createQuery(criteria).getResultList();
}
}代码解析:
- CriteriaBuilder builder = entityManager.getCriteriaBuilder();: 获取 CriteriaBuilder 实例,它是构建 Criteria 查询的工厂。
- CriteriaQuery
criteria = builder.createQuery(Queue.class);: 创建一个 CriteriaQuery 对象,指定查询结果的类型为 Queue。
- `Root
queueRoot =










