
本文旨在解决在使用 JPA Criteria 查询时,如何正确获取 `ManyToOne` 关联实体中的数据。通过 `Join` 操作,我们可以访问关联实体中的属性,并将其应用于查询条件中,避免常见的 “Unable to locate Attribute” 错误。同时,建议使用元模型(Metamodel)进行类型安全的查询。
在使用 hibernate JPA 进行数据查询时,经常会遇到需要访问关联实体属性的场景,特别是当实体之间存在 ManyToOne 关系时。直接在 Criteria 查询中使用关联实体的属性,会导致 Unable to locate Attribute 错误。本文将详细介绍如何通过 Join 操作正确地访问 ManyToOne 关联实体中的数据,并提供示例代码和注意事项。
问题分析
假设我们有两个实体 GroupEntity 和 UserEntity,其中 GroupEntity 通过 ManyToOne 关系关联到 UserEntity。GroupEntity 包含 name 和 status 属性,而 UserEntity 包含 userCode 属性。如果我们需要查询 userCode 匹配特定值,或者 name 匹配其他特定值的 GroupEntity 列表,直接在 GroupEntity 的 Root 上使用 userCode 属性会抛出异常,因为 userCode 实际上是 UserEntity 的属性。
解决方案:使用 Join 操作
要解决这个问题,我们需要使用 Join 操作将 GroupEntity 和 UserEntity 连接起来。通过 Join 操作,我们可以获得 UserEntity 的引用,从而访问其属性。
以下是修改后的 JPA Criteria 查询代码示例:
public List<GroupEntity> getData(final String userCode, final String currentGroupName, final String newGroupName) { final EntityManager entityManager = entityManagerProvider.get(); final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); final CriteriaQuery<GroupEntity> cq = cb.createQuery(GroupEntity.class); final Root<GroupEntity> root = cq.from(GroupEntity.class); // 使用 Join 操作连接 GroupEntity 和 UserEntity Join<GroupEntity, UserEntity> user = root.join("userEntity"); cq.select(root).where(cb.or( cb.equal(user.get("userCode"), userCode), // 使用 user.get("userCode") 访问 UserEntity 的 userCode 属性 cb.equal(root.get("name"), currentGroupName), cb.equal(root.get("name"), newGroupName))); return entityManager.createQuery(cq).getResultList(); }
代码解释:
- Join<GroupEntity, UserEntity> user = root.join(“userEntity”);:这行代码创建了一个 Join 对象,将 GroupEntity 的 root 和 UserEntity 连接起来。”userEntity” 是 GroupEntity 中 @ManyToOne 注解指定的属性名称。
- cb.equal(user.get(“userCode”), userCode):使用 user.get(“userCode”) 可以访问 UserEntity 的 userCode 属性,并将其用于查询条件。
示例实体类
为了更好地理解上述代码,以下是 GroupEntity 和 UserEntity 的示例代码:
@Entity @Table(name = "groups") public class GroupEntity extends BaseEntity { @Id @SequenceGenerator(name = "beta_group_seq", sequenceName = "group_seq", allocationSize = 1) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "group_seq") @Column(name = "id") private Long id; @Column(name = "name", length = 1400) private String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private UserEntity userEntity; @Column(name = "status") private String status; // Getters and setters } @Entity @Table(name = "users") public class UserEntity extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @Column(name = "user_code", nullable = false, unique = true) private String userCode; // Getters and setters } @Getter @MappedSuperclass public abstract class BaseEntity { @Column(name = "CREATED_BY", length = 8, updatable = false) private String createdBy; @Temporal(TemporalType.TIMESTAMP) @Column(name = "CREATION_DATE", updatable = false) private Date creationDate; @Column(name = "LAST_UPDATED_BY", length = 8, updatable = false) private String lastUpdatedBy; @Temporal(TemporalType.TIMESTAMP) @Column(name = "LAST_UPDATED_DATE", updatable = false) private Date lastUpdatedDate; }
使用元模型(Metamodel)进行类型安全查询
虽然使用字符串指定属性名称可以解决问题,但存在类型安全问题。如果属性名称发生变化,编译器无法检测到错误。为了解决这个问题,可以使用元模型(Metamodel)进行类型安全的查询。
首先,需要配置 maven 插件来生成元模型类。在 pom.xml 文件中添加以下插件:
<plugin> <groupId>org.hibernate.orm.tooling</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>${hibernate.version}</version> <executions> <execution> <goals> <goal>generate</goal> </goals> </execution> </executions> <configuration> <outputDirectory>src/main/Java</outputDirectory> </configuration> </plugin>
然后,重新编译项目,Hibernate 将会自动生成元模型类,例如 GroupEntity_ 和 UserEntity_。
修改后的 JPA Criteria 查询代码如下:
public List<GroupEntity> getData(final String userCode, final String currentGroupName, final String newGroupName) { final EntityManager entityManager = entityManagerProvider.get(); final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); final CriteriaQuery<GroupEntity> cq = cb.createQuery(GroupEntity.class); final Root<GroupEntity> root = cq.from(GroupEntity.class); // 使用 Join 操作连接 GroupEntity 和 UserEntity Join<GroupEntity, UserEntity> user = root.join(GroupEntity_.userEntity); cq.select(root).where(cb.or( cb.equal(user.get(UserEntity_.userCode), userCode), // 使用 UserEntity_.userCode 访问 UserEntity 的 userCode 属性 cb.equal(root.get(GroupEntity_.name), currentGroupName), cb.equal(root.get(GroupEntity_.name), newGroupName))); return entityManager.createQuery(cq).getResultList(); }
代码解释:
- Join<GroupEntity, UserEntity> user = root.join(GroupEntity_.userEntity);:使用 GroupEntity_.userEntity 代替字符串 “userEntity”,可以获得类型安全的 Join 对象。
- cb.equal(user.get(UserEntity_.userCode), userCode):使用 UserEntity_.userCode 代替字符串 “userCode”,可以访问 UserEntity 的 userCode 属性,并进行类型安全的比较。
注意事项
- 确保 @ManyToOne 注解中的 fetch 属性设置为 FetchType.LAZY,以避免不必要的性能开销。
- 在使用 Join 操作时,确保连接的属性名称与实体类中的属性名称一致。
- 推荐使用元模型(Metamodel)进行类型安全的查询,以提高代码的可维护性和可靠性。
总结
通过本文,我们学习了如何使用 JPA Criteria 查询获取 ManyToOne 关联实体中的数据。通过 Join 操作,我们可以访问关联实体中的属性,并将其应用于查询条件中。同时,建议使用元模型(Metamodel)进行类型安全的查询。掌握这些技巧可以帮助我们编写更高效、更可靠的 JPA 查询代码。


