in genie-web/src/main/java/com/netflix/genie/web/data/services/impl/jpa/JpaPersistenceServiceImpl.java [280:397]
public Page<Application> findApplications(
@Nullable final String name,
@Nullable final String user,
@Nullable final Set<ApplicationStatus> statuses,
@Nullable final Set<String> tags,
@Nullable final String type,
final Pageable page
) {
/*
* NOTE: This is implemented this way for a reason:
* 1. To solve the JPA N+1 problem: https://vladmihalcea.com/n-plus-1-query-problem/
* 2. To address this: https://vladmihalcea.com/fix-hibernate-hhh000104-entity-fetch-pagination-warning-message/
* This reduces the number of queries from potentially 100's to 3
*/
log.debug(
"[findApplications] Called with name = {}, user = {}, statuses = {}, tags = {}, type = {}",
name,
user,
statuses,
tags,
type
);
final Set<String> statusStrings = statuses != null
? statuses.stream().map(Enum::name).collect(Collectors.toSet())
: null;
// TODO: Still more optimization that can be done here to not load these entities
// Figure out how to use just strings in the predicate
final Set<TagEntity> tagEntities = tags == null
? null
: this.tagRepository.findByTagIn(tags);
if (tagEntities != null && tagEntities.size() != tags.size()) {
// short circuit for no results as at least one of the expected tags doesn't exist
return new PageImpl<>(new ArrayList<>(0));
}
final CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
final CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
final Root<ApplicationEntity> countQueryRoot = countQuery.from(ApplicationEntity.class);
final Subquery<Long> countIdSubQuery = countQuery.subquery(Long.class);
final Root<ApplicationEntity> countIdSubQueryRoot = countIdSubQuery.from(ApplicationEntity.class);
countIdSubQuery.select(countIdSubQueryRoot.get(ApplicationEntity_.id));
countIdSubQuery.where(
ApplicationPredicates.find(
countIdSubQueryRoot,
countIdSubQuery,
criteriaBuilder,
name,
user,
statusStrings,
tagEntities,
type
)
);
countQuery.select(criteriaBuilder.count(countQueryRoot));
countQuery.where(countQueryRoot.get(ApplicationEntity_.id).in(countIdSubQuery));
final Long totalCount = this.entityManager.createQuery(countQuery).getSingleResult();
if (totalCount == null || totalCount == 0) {
// short circuit for no results
return new PageImpl<>(new ArrayList<>(0));
}
final CriteriaQuery<Long> idQuery = criteriaBuilder.createQuery(Long.class);
final Root<ApplicationEntity> idQueryRoot = idQuery.from(ApplicationEntity.class);
idQuery.select(idQueryRoot.get(ApplicationEntity_.id));
idQuery.where(
// NOTE: The problem with trying to reuse the predicate above even though they seem the same is they have
// different query objects. If there is a join added by the predicate function it won't be on the
// right object as these criteria queries are basically builders
ApplicationPredicates.find(
idQueryRoot,
idQuery,
criteriaBuilder,
name,
user,
statusStrings,
tagEntities,
type
)
);
final Sort sort = page.getSort();
final List<Order> orders = new ArrayList<>();
sort.iterator().forEachRemaining(
order -> {
if (order.isAscending()) {
orders.add(criteriaBuilder.asc(idQueryRoot.get(order.getProperty())));
} else {
orders.add(criteriaBuilder.desc(idQueryRoot.get(order.getProperty())));
}
}
);
idQuery.orderBy(orders);
final List<Long> applicationIds = this.entityManager
.createQuery(idQuery)
.setFirstResult(((Long) page.getOffset()).intValue())
.setMaxResults(page.getPageSize())
.getResultList();
final CriteriaQuery<ApplicationEntity> contentQuery = criteriaBuilder.createQuery(ApplicationEntity.class);
final Root<ApplicationEntity> contentQueryRoot = contentQuery.from(ApplicationEntity.class);
contentQuery.select(contentQueryRoot);
contentQuery.where(contentQueryRoot.get(ApplicationEntity_.id).in(applicationIds));
// Need to make the same order by or results won't be accurate
contentQuery.orderBy(orders);
final List<Application> applications = this.entityManager
.createQuery(contentQuery)
.setHint(LOAD_GRAPH_HINT, this.entityManager.getEntityGraph(ApplicationEntity.DTO_ENTITY_GRAPH))
.getResultStream()
.map(EntityV4DtoConverters::toV4ApplicationDto)
.collect(Collectors.toList());
return new PageImpl<>(applications, page, totalCount);
}