From 04e3b07b08876053ab34ac7d6a95fc4448b0504c Mon Sep 17 00:00:00 2001 From: BartChris Date: Thu, 6 Feb 2025 13:56:05 +0100 Subject: [PATCH] 3_8_Speed up template usage query by using Scalar projections --- .../data/database/persistence/BaseDAO.java | 16 ++++++++++++++ .../kitodo/production/forms/TemplateForm.java | 15 ++++++------- .../services/data/TemplateService.java | 21 +++++++++++++++++++ .../data/base/SearchDatabaseService.java | 11 ++++++++++ .../services/data/TemplateServiceIT.java | 14 +++++++++++++ 5 files changed, 70 insertions(+), 7 deletions(-) diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/BaseDAO.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/BaseDAO.java index b4fa57c38d6..f74a41cfb8c 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/BaseDAO.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/BaseDAO.java @@ -259,6 +259,22 @@ public Long count(String query) throws DAOException { } } + /** + * Executes an HQL query that returns scalar projections (e.g., specific fields or aggregate results) + * instead of full entity objects. + * + * @param hql the HQL query string + * @param parameters query parameters + * @return list of scalar projection results + */ + public List getProjectionByQuery(String hql, Map parameters) { + try (Session session = HibernateUtil.getSession()) { + Query query = session.createQuery(hql, Object[].class); + addParameters(query, parameters); + return query.getResultList(); + } + } + /** * Removes the object from the database with with specified class type and * {@code id}. diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java b/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java index e852d31f89b..5f0a811b842 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/TemplateForm.java @@ -17,10 +17,11 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; import javax.annotation.PostConstruct; -import javax.enterprise.context.SessionScoped; +import javax.faces.view.ViewScoped; import javax.inject.Named; import org.apache.commons.lang3.StringUtils; @@ -46,7 +47,7 @@ import org.kitodo.production.workflow.model.Converter; @Named("TemplateForm") -@SessionScoped +@ViewScoped public class TemplateForm extends TemplateBaseForm { private static final Logger logger = LogManager.getLogger(TemplateForm.class); @@ -60,6 +61,7 @@ public class TemplateForm extends TemplateBaseForm { private List templateFilters; private List selectedTemplateFilters; private static final String DEACTIVATED_TEMPLATES_FILTER = "deactivatedTemplates"; + private Map templateUsageMap; /** * Constructor. @@ -458,12 +460,11 @@ private void prepareTasks() throws DAOException, IOException, WorkflowException, * @return whether template is used by any processes or not */ public boolean isTemplateUsed(int templateId) { - try { - return !ServiceManager.getProcessService().findByTemplate(templateId).isEmpty(); - } catch (DataException e) { - Helper.setErrorMessage(e); - return false; + if (Objects.isNull(templateUsageMap)) { + templateUsageMap = ServiceManager.getTemplateService().getTemplateUsageMap(); } + Boolean isUsed = templateUsageMap.get(templateId); + return Boolean.TRUE.equals(isUsed); } /** diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/TemplateService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/TemplateService.java index 0df3e44f003..8148067e9a2 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/TemplateService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/TemplateService.java @@ -86,6 +86,27 @@ public static TemplateService getInstance() { return localReference; } + /** + * Retrieves a map indicating the usage status of templates. + * The method executes an HQL query to determine whether each template is used + * (i.e., has associated processes). + * + * @return a map where the key is the template ID and the value is a boolean + * indicating whether the template is used + */ + public Map getTemplateUsageMap() { + String hql = "SELECT t.id AS templateId, " + + " CASE WHEN EXISTS (SELECT 1 FROM Process p WHERE p.template.id = t.id) " + + " THEN true ELSE false END AS isUsed " + + " FROM Template t"; + List results = getProjectionByQuery(hql); + return results.stream() + .collect(Collectors.toMap( + row -> (Integer) row[0], // templateId + row -> (Boolean) row[1] // isUsed + )); + } + @Override public Long countDatabaseRows() throws DAOException { return countDatabaseRows("SELECT COUNT(*) FROM Template"); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/base/SearchDatabaseService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/base/SearchDatabaseService.java index d91970c3580..384e5571611 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/base/SearchDatabaseService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/base/SearchDatabaseService.java @@ -197,6 +197,17 @@ public List getAll(int offset, int size) throws DAOException { return dao.getAll(offset, size); } + /** + * Executes an HQL query that returns scalar projections (e.g., specific fields or aggregate results) + * instead of full entity objects. + * + * @param hql the HQL query string + * @return list of scalar projection results + */ + protected List getProjectionByQuery(String hql) { + return dao.getProjectionByQuery(hql, null); + } + /** * Evict given bean object. * diff --git a/Kitodo/src/test/java/org/kitodo/production/services/data/TemplateServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/data/TemplateServiceIT.java index 57f7f415b89..afa4da6cad0 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/data/TemplateServiceIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/data/TemplateServiceIT.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -102,4 +103,17 @@ public void shouldHasCompleteTasks() throws Exception { condition = templateService.hasCompleteTasks(templateDTO.getTasks()); assertFalse(condition, "Process DTO has complete tasks!"); } + + @Test + public void shouldCorrectlyDetermineTemplateUsage() throws Exception { + Map templateUsageMap = templateService.getTemplateUsageMap(); + Map expectedMap = Map.of( + 1, true, + 2, false, + 3, false, + 4, false + ); + // Assert that the generated map matches the expected map + assertEquals(expectedMap, templateUsageMap, "The template usage map does not match expected results."); + } }