diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1937-reduce-revinclude-db-roundtrips.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1937-reduce-revinclude-db-roundtrips.yaml new file mode 100644 index 00000000000..fdecae9c2d2 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1937-reduce-revinclude-db-roundtrips.yaml @@ -0,0 +1,6 @@ +--- +type: perf +issue: 1937 +title: Due to an inefficient SQL statement when performing searches with large numbers of _revincludes where the resources + have tags, a large number of database roundtrips were produced when searching. This has been streamlined, greatly improving + the response times for some searches. diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTagDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTagDao.java index bb81b65ce0a..e565db8c193 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTagDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTagDao.java @@ -32,7 +32,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTag; public interface IResourceTagDao extends JpaRepository { @Query("" + "SELECT t FROM ResourceTag t " + - "INNER JOIN TagDefinition td ON (td.myId = t.myTagId) " + + "INNER JOIN FETCH t.myTag td " + "WHERE t.myResourceId in (:pids)") Collection findByResourceIds(@Param("pids") Collection pids); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java index d5569612515..e17b4d4fed4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java @@ -4,11 +4,13 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.SqlQuery; import ca.uhn.fhir.jpa.util.TestUtil; +import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.ReferenceParam; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CareTeam; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.IdType; @@ -538,6 +540,53 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { } + @Test + public void testSearchOnReverseInclude() { + Patient patient = new Patient(); + patient.getMeta().addTag("http://system", "value1", "display"); + patient.setId("P1"); + patient.getNameFirstRep().setFamily("FAM1"); + myPatientDao.update(patient); + + patient = new Patient(); + patient.setId("P2"); + patient.getMeta().addTag("http://system", "value1", "display"); + patient.getNameFirstRep().setFamily("FAM2"); + myPatientDao.update(patient); + + for (int i = 0; i < 3; i++) { + CareTeam ct = new CareTeam(); + ct.setId("CT1-" + i); + ct.getMeta().addTag("http://system", "value11", "display"); + ct.getSubject().setReference("Patient/P1"); + myCareTeamDao.update(ct); + + ct = new CareTeam(); + ct.setId("CT2-" + i); + ct.getMeta().addTag("http://system", "value22", "display"); + ct.getSubject().setReference("Patient/P2"); + myCareTeamDao.update(ct); + } + + SearchParameterMap map = SearchParameterMap + .newSynchronous() + .addRevInclude(CareTeam.INCLUDE_SUBJECT) + .setSort(new SortSpec(Patient.SP_NAME)); + + myCaptureQueriesListener.clear(); + IBundleProvider outcome = myPatientDao.search(map); + assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder( + "Patient/P1", "CareTeam/CT1-0", "CareTeam/CT1-1","CareTeam/CT1-2", + "Patient/P2", "CareTeam/CT2-0", "CareTeam/CT2-1","CareTeam/CT2-2" + )); + + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + } + @Test public void testTransactionWithMultipleReferences() { Bundle input = new Bundle(); diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java index d255c5d0005..7a7e338280a 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTag.java @@ -42,7 +42,7 @@ public class ResourceTag extends BaseTag { @Column(name = "PID") private Long myId; - @ManyToOne(cascade = {}) + @ManyToOne(cascade = {}, fetch = FetchType.LAZY) @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_RESTAG_RESOURCE")) private ResourceTable myResource; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java index 282bee12598..b099d4dc6c5 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java @@ -159,8 +159,9 @@ public class SearchParameterMap implements Serializable { } } - public void addRevInclude(Include theInclude) { + public SearchParameterMap addRevInclude(Include theInclude) { getRevIncludes().add(theInclude); + return this; } private void addUrlIncludeParams(StringBuilder b, String paramName, Set theList) { @@ -268,8 +269,9 @@ public class SearchParameterMap implements Serializable { return mySort; } - public void setSort(SortSpec theSort) { + public SearchParameterMap setSort(SortSpec theSort) { mySort = theSort; + return this; } /**