This is an automated email from the ASF dual-hosted git repository.

mweiler pushed a commit to branch main
in repository 
https://gitbox.apache.org/repos/asf/incubator-kie-kogito-runtimes.git


The following commit(s) were added to refs/heads/main by this push:
     new 4f6d50a148 [incubator-kie-issues##2168] User Task: Reduce the number 
of interactions with the database (#4127)
4f6d50a148 is described below

commit 4f6d50a14804af9561e1427432902cfb968fc23b
Author: Abhiram Gundala <[email protected]>
AuthorDate: Wed Nov 12 09:19:50 2025 -0500

    [incubator-kie-issues##2168] User Task: Reduce the number of interactions 
with the database (#4127)
    
    * batch update
    
    * Added Tests
---
 .../handler/UserTaskKogitoWorkItemHandler.java     | 68 ++++++++++---------
 jbpm/jbpm-usertask/pom.xml                         |  5 ++
 .../usertask/impl/DefaultUserTaskInstance.java     | 33 +++++++--
 .../usertask/impl/DefaultUserTaskInstanceTest.java | 78 ++++++++++++++++++++++
 4 files changed, 144 insertions(+), 40 deletions(-)

diff --git 
a/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java
 
b/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java
index 80a6ad977e..ffb2a33a34 100644
--- 
a/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java
+++ 
b/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java
@@ -83,39 +83,41 @@ public class UserTaskKogitoWorkItemHandler extends 
DefaultKogitoWorkItemHandler
 
         userTask.instances().create(instance);
 
-        instance.setTaskName(ofNullable((String) 
workItem.getParameter(TASK_NAME)).orElse((String) 
workItem.getParameter(NODE_NAME)));
-        instance.setTaskDescription((String) 
workItem.getParameter(DESCRIPTION));
-        instance.setTaskPriority(priority != null ? priority.toString() : 
null);
-        instance.setSlaDueDate(workItem.getNodeInstance().getSlaDueDate());
-
-        Map<String, Object> metadata = new HashMap<>();
-        metadata.put("ProcessId", 
workItem.getProcessInstance().getProcessId());
-        metadata.put("ProcessType", 
workItem.getProcessInstance().getProcess().getType());
-        metadata.put("ProcessVersion", 
workItem.getProcessInstance().getProcessVersion());
-        metadata.put("ProcessInstanceId", 
workItem.getProcessInstance().getId());
-        metadata.put("ProcessInstanceState", 
workItem.getProcessInstance().getState());
-        metadata.put("RootProcessId", 
workItem.getProcessInstance().getRootProcessId());
-        metadata.put("RootProcessInstanceId", 
workItem.getProcessInstance().getRootProcessInstanceId());
-        metadata.put("ParentProcessInstanceId", 
workItem.getProcessInstance().getParentProcessInstanceId());
-        metadata.put("NodeInstanceId", workItem.getNodeInstance().getId());
-
-        instance.setMetadata(metadata);
-
-        instance.fireInitialStateChange();
-        workItem.getParameters().entrySet().stream().filter(e -> 
!HumanTaskNode.TASK_PARAMETERS.contains(e.getKey())).forEach(e -> 
instance.setInput(e.getKey(), e.getValue()));
-
-        
ofNullable(workItem.getParameters().get(ACTOR_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setPotentialUsers);
-        
ofNullable(workItem.getParameters().get(GROUP_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setPotentialGroups);
-        
ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setAdminUsers);
-        
ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_GROUP_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setAdminGroups);
-        
ofNullable(workItem.getParameters().get(EXCLUDED_OWNER_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setExcludedUsers);
-
-        
ofNullable(workItem.getParameters().get(NOT_STARTED_NOTIFY)).map(String.class::cast).map(DeadlineHelper::parseDeadlines).ifPresent(instance::setNotStartedDeadlines);
-        
ofNullable(workItem.getParameters().get(NOT_STARTED_REASSIGN)).map(String.class::cast).map(DeadlineHelper::parseReassignments).ifPresent(instance::setNotStartedReassignments);
-        
ofNullable(workItem.getParameters().get(NOT_COMPLETED_NOTIFY)).map(String.class::cast).map(DeadlineHelper::parseDeadlines).ifPresent(instance::setNotCompletedDeadlines);
-        
ofNullable(workItem.getParameters().get(NOT_COMPLETED_REASSIGN)).map(String.class::cast).map(DeadlineHelper::parseReassignments).ifPresent(instance::setNotCompletedReassignments);
-
-        instance.initialize(emptyMap(), 
IdentityProviders.of(WORKFLOW_ENGINE_USER));
+        instance.batchUpdate(task -> {
+            task.setTaskName(ofNullable((String) 
workItem.getParameter(TASK_NAME)).orElse((String) 
workItem.getParameter(NODE_NAME)));
+            task.setTaskDescription((String) 
workItem.getParameter(DESCRIPTION));
+            task.setTaskPriority(priority != null ? priority.toString() : 
null);
+            task.setSlaDueDate(workItem.getNodeInstance().getSlaDueDate());
+
+            Map<String, Object> metadata = new HashMap<>();
+            metadata.put("ProcessId", 
workItem.getProcessInstance().getProcessId());
+            metadata.put("ProcessType", 
workItem.getProcessInstance().getProcess().getType());
+            metadata.put("ProcessVersion", 
workItem.getProcessInstance().getProcessVersion());
+            metadata.put("ProcessInstanceId", 
workItem.getProcessInstance().getId());
+            metadata.put("ProcessInstanceState", 
workItem.getProcessInstance().getState());
+            metadata.put("RootProcessId", 
workItem.getProcessInstance().getRootProcessId());
+            metadata.put("RootProcessInstanceId", 
workItem.getProcessInstance().getRootProcessInstanceId());
+            metadata.put("ParentProcessInstanceId", 
workItem.getProcessInstance().getParentProcessInstanceId());
+            metadata.put("NodeInstanceId", workItem.getNodeInstance().getId());
+
+            task.setMetadata(metadata);
+
+            task.fireInitialStateChange();
+            workItem.getParameters().entrySet().stream().filter(e -> 
!HumanTaskNode.TASK_PARAMETERS.contains(e.getKey())).forEach(e -> 
task.setInput(e.getKey(), e.getValue()));
+
+            
ofNullable(workItem.getParameters().get(ACTOR_ID)).map(String.class::cast).map(this::toSet).ifPresent(task::setPotentialUsers);
+            
ofNullable(workItem.getParameters().get(GROUP_ID)).map(String.class::cast).map(this::toSet).ifPresent(task::setPotentialGroups);
+            
ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_ID)).map(String.class::cast).map(this::toSet).ifPresent(task::setAdminUsers);
+            
ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_GROUP_ID)).map(String.class::cast).map(this::toSet).ifPresent(task::setAdminGroups);
+            
ofNullable(workItem.getParameters().get(EXCLUDED_OWNER_ID)).map(String.class::cast).map(this::toSet).ifPresent(task::setExcludedUsers);
+
+            
ofNullable(workItem.getParameters().get(NOT_STARTED_NOTIFY)).map(String.class::cast).map(DeadlineHelper::parseDeadlines).ifPresent(task::setNotStartedDeadlines);
+            
ofNullable(workItem.getParameters().get(NOT_STARTED_REASSIGN)).map(String.class::cast).map(DeadlineHelper::parseReassignments).ifPresent(task::setNotStartedReassignments);
+            
ofNullable(workItem.getParameters().get(NOT_COMPLETED_NOTIFY)).map(String.class::cast).map(DeadlineHelper::parseDeadlines).ifPresent(task::setNotCompletedDeadlines);
+            
ofNullable(workItem.getParameters().get(NOT_COMPLETED_REASSIGN)).map(String.class::cast).map(DeadlineHelper::parseReassignments).ifPresent(task::setNotCompletedReassignments);
+
+            task.initialize(emptyMap(), 
IdentityProviders.of(WORKFLOW_ENGINE_USER));
+        });
 
         if (workItem instanceof InternalKogitoWorkItem ikw) {
             ikw.setExternalReferenceId(instance.getId());
diff --git a/jbpm/jbpm-usertask/pom.xml b/jbpm/jbpm-usertask/pom.xml
index 9e1072a02f..dcaf179947 100644
--- a/jbpm/jbpm-usertask/pom.xml
+++ b/jbpm/jbpm-usertask/pom.xml
@@ -69,6 +69,11 @@ under the License.
             <artifactId>assertj-core</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <dependencyManagement>
diff --git 
a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java
 
b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java
index 8ba6039156..6a3eea5e27 100644
--- 
a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java
+++ 
b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java
@@ -119,6 +119,9 @@ public class DefaultUserTaskInstance implements 
UserTaskInstance {
 
     private Map<String, Reassignment> notCompletedReassignmentsTimers;
 
+    // preventing unnecessary database updates during batch operations
+    private boolean batchOperation = false;
+
     public DefaultUserTaskInstance() {
         this.inputs = new HashMap<>();
         this.outputs = new HashMap<>();
@@ -262,23 +265,40 @@ public class DefaultUserTaskInstance implements 
UserTaskInstance {
 
     @Override
     public void transition(String transitionId, Map<String, Object> data, 
IdentityProvider identity) {
-        Optional<UserTaskTransitionToken> next = 
Optional.of(this.userTaskLifeCycle.newTransitionToken(transitionId, this, 
data));
-        while (next.isPresent()) {
-            UserTaskTransitionToken transition = next.get();
-            next = this.userTaskLifeCycle.transition(this, transition, 
identity);
-            this.status = transition.target();
-            this.userTaskEventSupport.fireOneUserTaskStateChange(this, 
transition.source(), transition.target());
+        batchUpdate(instance -> {
+            Optional<UserTaskTransitionToken> next = 
Optional.of(this.userTaskLifeCycle.newTransitionToken(transitionId, instance, 
data));
+            while (next.isPresent()) {
+                UserTaskTransitionToken transition = next.get();
+                next = this.userTaskLifeCycle.transition(instance, transition, 
identity);
+                instance.status = transition.target();
+                
instance.userTaskEventSupport.fireOneUserTaskStateChange(instance, 
transition.source(), transition.target());
+            }
+        });
+    }
+
+    public void batchUpdate(Consumer<DefaultUserTaskInstance> batch) {
+        this.batchOperation = true;
+        try {
+            batch.accept(this);
+        } finally {
+            this.batchOperation = false;
         }
         this.updatePersistenceOrRemove();
     }
 
     private void updatePersistence() {
+        if (this.batchOperation) {
+            return;
+        }
         if (this.instances != null) {
             this.instances.update(this);
         }
     }
 
     private void updatePersistenceOrRemove() {
+        if (this.batchOperation) {
+            return;
+        }
         if (this.status.isTerminate()) {
             this.instances.remove(this);
         } else {
@@ -792,5 +812,4 @@ public class DefaultUserTaskInstance implements 
UserTaskInstance {
         return "DefaultUserTaskInstance [id=" + id + ", status=" + status + ", 
actualOwner=" + actualOwner + ", taskName=" + taskName + ", taskDescription=" + 
taskDescription + ", taskPriority="
                 + taskPriority + ", slaDueDate=" + slaDueDate + "]";
     }
-
 }
diff --git 
a/jbpm/jbpm-usertask/src/test/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstanceTest.java
 
b/jbpm/jbpm-usertask/src/test/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstanceTest.java
new file mode 100644
index 0000000000..89a695533b
--- /dev/null
+++ 
b/jbpm/jbpm-usertask/src/test/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstanceTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.usertask.impl;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.usertask.UserTaskInstances;
+import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle;
+import org.kie.kogito.usertask.lifecycle.UserTaskState;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class DefaultUserTaskInstanceTest {
+
+    private UserTaskInstances instances;
+    private DefaultUserTaskInstance userTaskInstance;
+
+    @BeforeEach
+    public void setup() {
+        instances = mock(UserTaskInstances.class);
+        userTaskInstance = new DefaultUserTaskInstance();
+        userTaskInstance.setInstances(instances);
+        userTaskInstance.setUserTaskLifeCycle(mock(UserTaskLifeCycle.class));
+    }
+
+    @Test
+    public void testBatchUpdateCallsUpdateOnce() {
+        userTaskInstance.batchUpdate(task -> {
+            task.setTaskName("Test Task");
+            task.setTaskDescription("Test Description");
+            task.setTaskPriority("High");
+            task.setActualOwner("testUser");
+        });
+
+        verify(instances, times(1)).update(userTaskInstance);
+    }
+
+    @Test
+    public void testNonBatchUpdateCallsUpdateMultipleTimes() {
+        userTaskInstance.setTaskName("Test Task");
+        userTaskInstance.setTaskDescription("Test Description");
+        userTaskInstance.setTaskPriority("High");
+        userTaskInstance.setActualOwner("testUser");
+
+        verify(instances, times(4)).update(userTaskInstance);
+    }
+
+    @Test
+    public void testBatchUpdateCallsRemoveWhenTerminated() {
+        userTaskInstance.setStatus(UserTaskState.of("Completed", 
UserTaskState.TerminationType.COMPLETED));
+
+        userTaskInstance.batchUpdate(task -> {
+            task.setTaskName("Completed Task");
+        });
+
+        verify(instances, times(1)).remove(userTaskInstance);
+        verify(instances, never()).update(userTaskInstance);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to