package org.jbpm.services.task.query; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.ListJoin; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.Selection; import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.PluralAttribute; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.SessionImpl; import org.hibernate.persister.collection.CollectionPersister; import org.jbpm.services.task.HumanTaskServiceFactory; import org.jbpm.services.task.HumanTaskServicesBaseTest; import org.jbpm.services.task.impl.factories.TaskFactory; import org.jbpm.services.task.impl.model.OrganizationalEntityImpl; import org.jbpm.services.task.impl.model.OrganizationalEntityImpl_; import org.jbpm.services.task.impl.model.PeopleAssignmentsImpl; import org.jbpm.services.task.impl.model.PeopleAssignmentsImpl_; import org.jbpm.services.task.impl.model.TaskDataImpl; import org.jbpm.services.task.impl.model.TaskDataImpl_; import org.jbpm.services.task.impl.model.TaskImpl; import org.jbpm.services.task.impl.model.TaskImpl_; import org.jbpm.services.task.impl.model.UserImpl_; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.kie.api.task.model.Task; import org.kie.api.task.model.TaskSummary; import org.kie.internal.task.api.InternalTaskService; import org.kie.internal.task.api.model.InternalTaskData; import bitronix.tm.resource.jdbc.PoolingDataSource; /** * * Verifying that the new JPA Criteria API approach is more performant (primarilly because of better use of joins!) * */ @Ignore public class DistincVsJoinPerformanceTest extends HumanTaskServicesBaseTest { private PoolingDataSource pds; private EntityManagerFactory emf; private static final String stakeHolder = "vampire"; @Before public void setup() { pds = setupPoolingDataSource(); emf = Persistence.createEntityManagerFactory( "org.jbpm.services.task" ); this.taskService = (InternalTaskService) HumanTaskServiceFactory.newTaskServiceConfigurator() .entityManagerFactory(emf) .getTaskService(); } @After public void clean() { super.tearDown(); if (emf != null) { emf.close(); } if (pds != null) { pds.close(); } } @Test public void performanceTest() { long workItemId = 59; long procInstId = 12111; String busAdmin = "Wintermute"; String potOwner = "Maelcum"; String deploymentId = "Dixie Flatline"; String name = "Complete Mission"; int total = 100; for( int i = 0; i < total; ++i ) { // Add two more tasks, in order to have a quorum ++workItemId; ++procInstId; busAdmin = "Wintermute"; potOwner = "Maelcum"; deploymentId = "Dixie Flatline"; name = "Complete Mission"; addTask(workItemId, procInstId, busAdmin, potOwner, name, deploymentId); // Add two more tasks, in order to have a quorum ++workItemId; ++procInstId; busAdmin = "Neuromancer"; potOwner = "Hideo"; deploymentId = "Linda Lee"; name = "Resurrect"; addTask(workItemId, procInstId, busAdmin, potOwner, name, deploymentId); } List<String> groupIds = new ArrayList<String>(); groupIds.add("Ninja"); String userId = "Hideo"; EntityManager em = emf.createEntityManager(); CriteriaBuilder builder = em.getCriteriaBuilder(); long joinTotal = 0; long selectTotal = 0; for( int i = 0; i < 500; ++i ) { // Add two more tasks, in order to have a quorum ++workItemId; ++procInstId; busAdmin = "Neuromancer"; potOwner = i % 2 == 0 ? "Ninja" : "Hideo"; deploymentId = "Linda Lee"; name = "Resurrect"; addTask(workItemId, procInstId, busAdmin, potOwner, name, deploymentId); ++total; long selectDur, joinDur; if( i % 2 == 0 ) { selectDur = doSelectQuery(em, userId, groupIds, total); joinDur = doJoinQuery(em, userId, groupIds, total); } else { joinDur = doJoinQuery(em, userId, groupIds, total); selectDur = doSelectQuery(em, userId, groupIds, total); } System.out.println( "API: " + joinDur + " JPQL: " + selectDur); joinTotal += joinDur; selectTotal += selectDur; } joinTotal /= 20; selectTotal /= 20; assertTrue( "Join [" + joinTotal + "ms] took longer than Select [" + selectTotal + "ms]!", joinTotal < selectTotal ); } private long doJoinQuery(EntityManager em, String userId, List<String> groupIds, int total) { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery<TaskImpl> joinQuery = builder.createQuery(TaskImpl.class); Root<TaskImpl> taskRoot = joinQuery.from(TaskImpl.class); Join<TaskImpl, TaskDataImpl> join = taskRoot.join(TaskImpl_.taskData); Selection select = getTaskSummarySelect(builder, taskRoot); joinQuery.select(select); Join<TaskImpl, PeopleAssignmentsImpl> peopleAssign = taskRoot.join(TaskImpl_.peopleAssignments); ListJoin<PeopleAssignmentsImpl,OrganizationalEntityImpl> busAdmins = peopleAssign.join(PeopleAssignmentsImpl_.businessAdministrators, JoinType.LEFT); ListJoin<PeopleAssignmentsImpl,OrganizationalEntityImpl> potOwners = peopleAssign.join(PeopleAssignmentsImpl_.potentialOwners, JoinType.LEFT); ListJoin<PeopleAssignmentsImpl,OrganizationalEntityImpl> stakeHols = peopleAssign.join(PeopleAssignmentsImpl_.taskStakeholders, JoinType.LEFT); List<Predicate> predicates = new ArrayList<Predicate>(); predicates.add( builder.equal(taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.actualOwner).get(UserImpl_.id), userId) ); predicates.add( builder.equal(taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.createdBy).get(UserImpl_.id), userId) ); predicates.add( builder.or( builder.equal( busAdmins.get(OrganizationalEntityImpl_.id), userId ), busAdmins.get(OrganizationalEntityImpl_.id).in(groupIds) ) ); predicates.add( builder.or( builder.equal( potOwners.get(OrganizationalEntityImpl_.id), userId ), potOwners.get(OrganizationalEntityImpl_.id).in(groupIds) ) ); predicates.add( builder.or( builder.equal( stakeHols.get(OrganizationalEntityImpl_.id), userId ), stakeHols.get(OrganizationalEntityImpl_.id).in(groupIds) ) ); if( ! predicates.isEmpty() ) { joinQuery.where(builder.or(predicates.toArray(new Predicate[predicates.size()]))); } return timeQueryExecution(em, joinQuery, null, total); } private long doSelectQuery(EntityManager em, String userId, List<String> groupIds, int total) { String selectFrom = "SELECT distinct new org.jbpm.services.task.query.TaskSummaryImpl(\n" + " t.id,\n" + " t.name,\n" + " t.description,\n" + " t.taskData.status,\n" + " t.priority,\n" + " t.taskData.actualOwner.id,\n" + " t.taskData.createdBy.id,\n" + " t.taskData.createdOn,\n" + " t.taskData.activationTime,\n" + " t.taskData.expirationTime,\n" + " t.taskData.processId,\n" + " t.taskData.processInstanceId,\n" + " t.taskData.parentId,\n" + " t.taskData.deploymentId,\n" + " t.taskData.skipable )\n" + "FROM TaskImpl t,\n" + " OrganizationalEntityImpl stakeHolders,\n" + " OrganizationalEntityImpl potentialOwners,\n" + " OrganizationalEntityImpl businessAdministrators\n" + "WHERE "; StringBuffer queryStr = new StringBuffer(selectFrom); String userIdParam = "U"; String groupIdsParam = "G"; queryStr .append("t.taskData.createdBy.id = :").append(userIdParam).append("\n OR ") .append("( stakeHolders.id in :").append(groupIdsParam).append(" and\n") .append(" stakeHolders in elements ( t.peopleAssignments.taskStakeholders ) )").append("\n OR " ) .append("( potentialOwners.id in :").append(groupIdsParam).append(" and\n") .append(" potentialOwners in elements ( t.peopleAssignments.potentialOwners ) )").append("\n OR " ) .append("t.taskData.actualOwner.id = :").append(userIdParam).append("\n OR ") .append("( businessAdministrators.id in :").append(groupIdsParam).append(" and\n") .append(" businessAdministrators in elements ( t.peopleAssignments.businessAdministrators ) )") .append(" )\n"); Query realQuery = em.createQuery(queryStr.toString()); realQuery.setParameter(userIdParam, userId); realQuery.setParameter(groupIdsParam, groupIds); return timeQueryExecution(em, null, realQuery, total); } private String [] createOriginalAndExpectedKeys(Attribute embeddedAttr, PluralAttribute listAttr) { String originalKey = embeddedAttr.getDeclaringType().getJavaType().getName() + "." + embeddedAttr.getName() + "." + listAttr.getName(); String copyKey = listAttr.getDeclaringType().getJavaType().getName() + "." + listAttr.getName(); String [] keys = { originalKey, copyKey }; return keys; } private void copyCollectionPersisterKeys(Attribute embeddedAttr, PluralAttribute listAttr, EntityManager em) { String [] keys = createOriginalAndExpectedKeys(embeddedAttr, listAttr); try { SessionImpl session = (SessionImpl) em.getDelegate(); SessionFactoryImplementor sessionFactory = session.getSessionFactory(); CollectionPersister persister = sessionFactory.getCollectionPersister(keys[0]); sessionFactory.getCollectionPersisters().put(keys[1], persister); } catch (Exception e) { throw new RuntimeException(e); } } private Selection<TaskSummaryImpl> getTaskSummarySelect(CriteriaBuilder builder, Root<TaskImpl> taskRoot) { Selection<TaskSummaryImpl> select = builder.construct(TaskSummaryImpl.class, taskRoot.get(TaskImpl_.id), taskRoot.get(TaskImpl_.name), taskRoot.get(TaskImpl_.subject), taskRoot.get(TaskImpl_.description), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.status), taskRoot.get(TaskImpl_.priority), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.skipable), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.actualOwner).get(UserImpl_.id), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.createdBy).get(UserImpl_.id), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.createdOn), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.activationTime), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.expirationTime), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.processId), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.processSessionId), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.processInstanceId), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.deploymentId), taskRoot.get(TaskImpl_.subTaskStrategy), taskRoot.get(TaskImpl_.taskData).get(TaskDataImpl_.parentId) ); return select; } private long timeQueryExecution(EntityManager em, CriteriaQuery query, Query realQuery, int total) { if( realQuery == null ) { realQuery = em.createQuery(query); realQuery.setMaxResults(2000); } long start = System.nanoTime(); List<TaskSummary> results = realQuery.getResultList(); long end = System.nanoTime(); assertEquals( "query results", total, results.size() ); return (end - start)/1000000; } private TaskImpl addTask( long workItemId, long procInstId, String busAdmin, String potOwner, String name, String deploymentId) { String str = "(with (new Task()) { priority = 55, taskData = (with( new TaskData()) { } ), "; String potOwnerType = potOwner.equals("Hideo") ? "User" : "Group"; str += "peopleAssignments = (with ( new PeopleAssignments() ) { " + "taskStakeholders = [new User('" + stakeHolder + "')]," + "businessAdministrators = [new User('" + busAdmin + "')]," + "potentialOwners = [new " + potOwnerType + "('" + potOwner + "')]" + " }),"; str += "name = '" + name + "' })"; Task taskImpl = TaskFactory.evalTask(new StringReader(str)); ((InternalTaskData) taskImpl.getTaskData()).setWorkItemId(workItemId); ((InternalTaskData) taskImpl.getTaskData()).setProcessInstanceId(procInstId); ((InternalTaskData) taskImpl.getTaskData()).setDeploymentId(deploymentId); taskService.addTask(taskImpl, new HashMap<String, Object>()); assertNotNull( "Null task id", taskImpl.getId()); return (TaskImpl) taskImpl; } }