Search code examples
javahibernatecriteriacriteria-api

Hibernate criteria implementation for this entity model (subquery, self-join)


Given the following entity one-to-many model:

One Repository can be linked to many AuditRecords.

Many AuditRecords can all link to the same Repository

@Entity
class AuditRecordEntity {
  private AuditRepositoryEntity auditRepository;  

  @ManyToOne(cascade = CascadeType.ALL)
  @JoinColumn(name = AUDIT_REPOSITORY_DB_COLUMN_NAME, nullable = false, updatable = false)
  public AuditRepositoryEntity getAuditRepository() {
    return auditRepository;
  }
  ...
}

@Entity
class AuditRepositoryEntity {
  private List<AuditRecordEntity> auditRecords = new ArrayList<AuditRecordEntity>();

  @OneToMany(mappedBy = "auditRepository")
  public List<AuditRecordEntity> getAuditRecords() {
    return auditRecords;
  }
  ...
}

Minor correction, in ERD diagram below, for 'repositoryId', read 'auditRepository'

I am trying to get the Criteria API implementation to:

Get the latest (by accessTime) AuditRecord for each distinct Repository? I.e. a list of AuditRecords, one for each Repository, where the AuditRecord is the last AuditRecord for that Repository (in the case where a Repository has many AuditRecords).

I have the HQL query to do this:

select auditRecord from AuditRecordEntity auditRecord where auditRecord.accessTime = 
(select max(auditRecord2.accessTime) from AuditRecordEntity auditRecord2 where  
auditRecord2.auditRepository = auditRecord.auditRepository)

But need to use the Criteria APi instead:

CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Object> query = builder.createQuery();
Root<AuditRecordEntity> root = query.from(AuditRecordEntity.class);
// what next?

Solution

  • I have got this to work(around) by using the output from the HQL query as input to the criteria API:

    final List<UUID> auditRecordIds = execute("select auditRecord from AuditRecordEntity auditRecord where auditRecord.accessTime = 
    (select max(auditRecord2.accessTime) from AuditRecordEntity auditRecord2 where  
    auditRecord2.auditRepository = auditRecord.auditRepository)")
    
    Root<AuditRecordEntity> root = criteriaQuery.from(AuditRecordEntity.class);
    criteriaQuery.select(root);
    
    List<Predicate> predicates = new ArrayList<Predicate>();
    predicates.add(root.get("id").in(auditRecordIds.toArray()));
    entitySearchCriteria.addPredicates(predicates);
    ...