@Override
    public ProjectMap getProjectsByIdList(List<Integer> projIdList) {
      if (projIdList.isEmpty()) {
        return ProjectMap.empty();
      }

      List<StoredProject> projs =
          autoCommit(
              (handle, dao) ->
                  handle
                      .createQuery(
                          "select * from projects"
                              + " where site_id = :siteId"
                              + " and id "
                              + inLargeIdListExpression(projIdList))
                      .bind("siteId", siteId)
                      .map(new StoredProjectMapper(cfm))
                      .list());

      ImmutableMap.Builder<Integer, StoredProject> builder = ImmutableMap.builder();
      for (StoredProject proj : projs) {
        builder.put(proj.getId(), proj);
      }
      return new ProjectMap(builder.build());
    }
    @Override
    public <T> T deleteProject(int projId, ProjectObsoleteAction<T> func)
        throws ResourceNotFoundException {
      return transaction(
          (handle, dao) -> {
            StoredProject proj =
                requiredResource(
                    dao.getProjectByIdWithLockForDelete(siteId, projId), "project id=%d", projId);

            T res = func.call(new DatabaseProjectControlStore(handle, siteId), proj);

            dao.deleteProject(proj.getId());

            return res;
          },
          ResourceNotFoundException.class);
    }