diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/hibernate/HibernateIdentifiableObjectStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/hibernate/HibernateIdentifiableObjectStore.java index 936812e8c0c6..14abd7a1d48d 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/hibernate/HibernateIdentifiableObjectStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/hibernate/HibernateIdentifiableObjectStore.java @@ -33,6 +33,7 @@ import static org.hisp.dhis.query.JpaQueryUtils.generateHqlQueryForSharingCheck; import jakarta.persistence.EntityManager; +import jakarta.persistence.FlushModeType; import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; @@ -919,7 +920,14 @@ public List findByCreatedBy(@Nonnull UserDetails user) { } /** - * Look up objects which have property createdBy or lastUpdatedBy linked to given {@link User} + * Look up objects which have property createdBy or lastUpdatedBy linked to given {@link User}. + * + *

Runs with {@link FlushModeType#COMMIT}, so the JPA auto-flush (a full-session dirty-check) + * is skipped before this query. This is safe for the current caller (the read-only deletion veto + * phase, which runs before any deletion handler writes), and avoids a per-query flush that + * dominates the cost when a large object graph is in the session. If a future caller reads within + * a transaction and must observe earlier un-flushed changes, change this method to accept a + * flush-mode parameter rather than relaxing the mode for everyone. * * @param user the {@link User} for filtering * @return TRUE of objects found. FALSE otherwise. @@ -939,7 +947,12 @@ public boolean existsByUser(@Nonnull User user, final Set checkPropertie return false; } query.where(builder.or(predicates.toArray(new Predicate[0]))); - return !entityManager.createQuery(query).setMaxResults(1).getResultList().isEmpty(); + return !entityManager + .createQuery(query) + .setFlushMode(FlushModeType.COMMIT) + .setMaxResults(1) + .getResultList() + .isEmpty(); } /** diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dataelement/DataElementStoreTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dataelement/DataElementStoreTest.java index 5b0d9075db57..4261b6ed6e02 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dataelement/DataElementStoreTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dataelement/DataElementStoreTest.java @@ -347,6 +347,7 @@ void testExistsByUser() { DataElement dataElementA = createDataElement('A'); dataElementA.setCreatedBy(userA); dataElementStore.save(dataElementA); + entityManager.flush(); assertTrue(dataElementStore.existsByUser(userA, Set.of("createdBy"))); assertFalse(dataElementStore.existsByUser(userB, Set.of("createdBy"))); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/user/UserServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/user/UserServiceTest.java index f0fdd64f3b1b..c589654ec99f 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/user/UserServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/user/UserServiceTest.java @@ -194,6 +194,7 @@ void testDeleteCreatedByUser() { DataElement dataElement = createDataElement('A'); dataElement.setCreatedBy(userA); idObjectManager.save(dataElement); + entityManager.flush(); assertThrows(DeleteNotAllowedException.class, () -> userService.deleteUser(userA)); } @@ -208,6 +209,7 @@ void testDeleteLastUpdatedByUser() { dataElement.setDescription("Updated"); idObjectManager.update(dataElement); + entityManager.flush(); assertThrows(DeleteNotAllowedException.class, () -> userService.deleteUser(userA)); }