@RequiresSession
  public List<KerberosKeytabPrincipalEntity> findByFilter(KerberosKeytabPrincipalFilter filter) {
    CriteriaBuilder cb = entityManagerProvider.get().getCriteriaBuilder();
    CriteriaQuery<KerberosKeytabPrincipalEntity> cq = cb.createQuery(KerberosKeytabPrincipalEntity.class);
    Root<KerberosKeytabPrincipalEntity> root = cq.from(KerberosKeytabPrincipalEntity.class);
    ArrayList<Predicate> predicates = new ArrayList<>();

    if (CollectionUtils.isNotEmpty(filter.getServiceNames())) {
      Join<KerberosKeytabPrincipalEntity, KerberosKeytabServiceMappingEntity> mappingJoin = root.join("serviceMapping");
      predicates.add(mappingJoin.get("serviceName").in(filter.getServiceNames()));
      if (CollectionUtils.isNotEmpty(filter.getComponentNames())) {
        predicates.add(mappingJoin.get("componentName").in(filter.getComponentNames()));
      }
    }

    if (CollectionUtils.isNotEmpty(filter.getHostNames())) {
      List<Long> hostIds = new ArrayList<>();
      boolean hasNull = false;

      for (String hostname : filter.getHostNames()) {
        HostEntity host = hostDAO.findByName(hostname);

        if (host == null) {
          // host may be null after a delete host operation, if so, add an OR NULL clause
          hasNull = true;
        } else {
          hostIds.add(host.getHostId());
        }
      }

      //split hostIDs into batches and combine them using OR
      if (CollectionUtils.isNotEmpty(hostIds)) {
        List<Predicate> hostPredicates = new ArrayList<>();
        SQLOperations.batch(hostIds, SQLConstants.IN_ARGUMENT_MAX_SIZE, (chunk, currentBatch, totalBatches, totalSize) -> {
          hostPredicates.add(root.get("hostId").in(chunk));
          return 0;
        });

        Predicate hostCombinedPredicate = cb.or(hostPredicates.toArray(new Predicate[hostPredicates.size()]));
        predicates.add(hostCombinedPredicate);
      }

      if (hasNull) {
        predicates.add(root.get("hostId").isNull());
      }
    }

    //Split principals into batches and combine them using OR
    if (CollectionUtils.isNotEmpty(filter.getPrincipals())) {
      ArrayList<Predicate> principalPredicates = new ArrayList<>();
      SQLOperations.batch(filter.getPrincipals(), SQLConstants.IN_ARGUMENT_MAX_SIZE,
        (chunk, currentBatch, totalBatches, totalSize) -> {
          principalPredicates.add(root.get("principalName").in(chunk));
          return 0;
        });

      Predicate principalCombinedPredicate = cb.or(principalPredicates.toArray(new Predicate[0]));
      predicates.add(principalCombinedPredicate);
    }
    cq.where(cb.and(predicates.toArray(new Predicate[0])));

    TypedQuery<KerberosKeytabPrincipalEntity> query = entityManagerProvider.get().createQuery(cq);
    List<KerberosKeytabPrincipalEntity> result = query.getResultList();
    if (result == null) {
      return Collections.emptyList();
    }
    return result;
  }
Ideally for this record PK must be (hostId, keytabPath, principalName), but in some cases hostId can be null.
So surrogate auto-generated PK used, and unique constraint for (hostId, keytabPath, principalName) applied.
This method checks if hostId is null and calls specific method.

@param hostId host id
@param keytabPath keytab path
@param principalName principal name
@return keytab found
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosKeytabPrincipalDAO.java