From f601b212ad27e783b61abd8342c1d046fd594816 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 20 Oct 2018 14:47:37 -0400 Subject: [PATCH] Performance enhancements to the JPA server and better retry logic in $trigger-subscription --- .../ca/uhn/fhir/jpa/config/BaseConfig.java | 22 +++++ .../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 91 +++++++++--------- .../SubscriptionTriggeringProvider.java | 94 ++++++++++++++++--- .../SubscriptionRestHookInterceptor.java | 2 - .../ca/uhn/fhir/jpa/config/TestR4Config.java | 3 +- .../dao/r4/FhirResourceDaoR4CreateTest.java | 21 +++++ src/changes/changes.xml | 11 +++ 7 files changed, 184 insertions(+), 60 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index fee87d32661..3aa4f830363 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -104,8 +104,30 @@ public abstract class BaseConfig implements SchedulingConfigurer { retVal.put(AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD); } + /* + * Set some performance options + */ + + if (!retVal.containsKey(AvailableSettings.STATEMENT_BATCH_SIZE)) { + retVal.put(AvailableSettings.STATEMENT_BATCH_SIZE, "30"); + } + + if (!retVal.containsKey(AvailableSettings.ORDER_INSERTS)) { + retVal.put(AvailableSettings.ORDER_INSERTS, "true"); + } + + if (!retVal.containsKey(AvailableSettings.ORDER_UPDATES)) { + retVal.put(AvailableSettings.ORDER_UPDATES, "true"); + } + + if (!retVal.containsKey(AvailableSettings.BATCH_VERSIONED_DATA)) { + retVal.put(AvailableSettings.BATCH_VERSIONED_DATA, "true"); + } + return retVal; } + + }; configureEntityManagerFactory(retVal, fhirContext()); return retVal; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index 74ef803e4ed..42d9af1f44b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -64,7 +64,6 @@ import org.hibernate.ScrollableResults; import org.hibernate.query.Query; import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; import org.hibernate.query.criteria.internal.predicate.BooleanStaticAssertionPredicate; -import org.hl7.fhir.dstu3.model.BaseResource; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -88,7 +87,7 @@ import static org.apache.commons.lang3.StringUtils.*; @SuppressWarnings("JpaQlInspection") public class SearchBuilder implements ISearchBuilder { - private static final List EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList()); + private static final List EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>()); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchBuilder.class); private static Long NO_MORE = -1L; private static HandlerTypeEnum ourLastHandlerMechanismForUnitTest; @@ -96,7 +95,7 @@ public class SearchBuilder implements ISearchBuilder { private static String ourLastHandlerThreadForUnitTest; private static boolean ourTrackHandlersForUnitTest; protected IResourceTagDao myResourceTagDao; - protected IResourceSearchViewDao myResourceSearchViewDao; + private IResourceSearchViewDao myResourceSearchViewDao; private List myAlsoIncludePids; private CriteriaBuilder myBuilder; private BaseHapiFhirDao myCallingDao; @@ -122,11 +121,11 @@ public class SearchBuilder implements ISearchBuilder { /** * Constructor */ - public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, - IFulltextSearchSvc theFulltextSearchSvc, BaseHapiFhirDao theDao, - IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, - IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry, - IResourceTagDao theResourceTagDao, IResourceSearchViewDao theResourceViewDao) { + SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, + IFulltextSearchSvc theFulltextSearchSvc, BaseHapiFhirDao theDao, + IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, + IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry, + IResourceTagDao theResourceTagDao, IResourceSearchViewDao theResourceViewDao) { myContext = theFhirContext; myEntityManager = theEntityManager; myFulltextSearchSvc = theFulltextSearchSvc; @@ -175,8 +174,7 @@ public class SearchBuilder implements ISearchBuilder { List codePredicates = new ArrayList<>(); for (IQueryParameterType nextOr : theList) { - IQueryParameterType params = nextOr; - Predicate p = createPredicateDate(params, theResourceName, theParamName, myBuilder, join); + Predicate p = createPredicateDate(nextOr, theResourceName, theParamName, myBuilder, join); codePredicates.add(p); } @@ -216,6 +214,7 @@ public class SearchBuilder implements ISearchBuilder { throw new InvalidRequestException("Invalid resource type: " + targetResourceType); } + assert parameterName != null; String paramName = parameterName.replaceAll("\\..*", ""); RuntimeSearchParam owningParameterDef = myCallingDao.getSearchParamByName(targetResourceDefinition, paramName); if (owningParameterDef == null) { @@ -244,7 +243,7 @@ public class SearchBuilder implements ISearchBuilder { private void addPredicateLanguage(List> theList) { for (List nextList : theList) { - Set values = new HashSet(); + Set values = new HashSet<>(); for (IQueryParameterType next : nextList) { if (next instanceof StringParam) { String nextValue = ((StringParam) next).getValue(); @@ -265,7 +264,6 @@ public class SearchBuilder implements ISearchBuilder { myPredicates.add(predicate); } - return; } private void addPredicateNumber(String theResourceName, String theParamName, List theList) { @@ -279,10 +277,9 @@ public class SearchBuilder implements ISearchBuilder { List codePredicates = new ArrayList<>(); for (IQueryParameterType nextOr : theList) { - IQueryParameterType params = nextOr; - if (params instanceof NumberParam) { - NumberParam param = (NumberParam) params; + if (nextOr instanceof NumberParam) { + NumberParam param = (NumberParam) nextOr; BigDecimal value = param.getValue(); if (value == null) { @@ -293,12 +290,12 @@ public class SearchBuilder implements ISearchBuilder { ParamPrefixEnum prefix = ObjectUtils.defaultIfNull(param.getPrefix(), ParamPrefixEnum.EQUAL); String invalidMessageName = "invalidNumberPrefix"; - Predicate predicateNumeric = createPredicateNumeric(theResourceName, theParamName, join, myBuilder, params, prefix, value, fromObj, invalidMessageName); + Predicate predicateNumeric = createPredicateNumeric(theResourceName, theParamName, join, myBuilder, nextOr, prefix, value, fromObj, invalidMessageName); Predicate predicateOuter = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, predicateNumeric); codePredicates.add(predicateOuter); } else { - throw new IllegalArgumentException("Invalid token type: " + params.getClass()); + throw new IllegalArgumentException("Invalid token type: " + nextOr.getClass()); } } @@ -392,7 +389,7 @@ public class SearchBuilder implements ISearchBuilder { final List> resourceTypes; String resourceId; - if (!ref.getValue().matches("[a-zA-Z]+\\/.*")) { + if (!ref.getValue().matches("[a-zA-Z]+/.*")) { RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); resourceTypes = new ArrayList<>(); @@ -904,6 +901,7 @@ public class SearchBuilder implements ISearchBuilder { case NUMBER: case REFERENCE: case URI: + case SPECIAL: break; } @@ -1590,6 +1588,7 @@ public class SearchBuilder implements ISearchBuilder { sortAttrName = new String[]{"myValue"}; joinType = JoinEnum.QUANTITY; break; + case SPECIAL: case COMPOSITE: case HAS: default: @@ -2022,6 +2021,7 @@ public class SearchBuilder implements ISearchBuilder { } break; case HAS: + case SPECIAL: // should not happen break; } @@ -2076,6 +2076,7 @@ public class SearchBuilder implements ISearchBuilder { case REFERENCE: qp = new ReferenceParam(); break; + case SPECIAL: case URI: case HAS: default: @@ -2114,7 +2115,7 @@ public class SearchBuilder implements ISearchBuilder { private Long myNext; private int myPageSize = myCallingDao.getConfig().getEverythingIncludesFetchPageSize(); - public IncludesIterator(Set thePidSet) { + IncludesIterator(Set thePidSet) { myCurrentPids = new ArrayList<>(thePidSet); myCurrentIterator = EMPTY_LONG_LIST.iterator(); myCurrentOffset = 0; @@ -2128,22 +2129,20 @@ public class SearchBuilder implements ISearchBuilder { break; } - if (!myCurrentIterator.hasNext()) { - int start = myCurrentOffset; - int end = myCurrentOffset + myPageSize; - if (end > myCurrentPids.size()) { - end = myCurrentPids.size(); - } - if (end - start <= 0) { - myNext = NO_MORE; - break; - } - myCurrentOffset = end; - Collection pidsToScan = myCurrentPids.subList(start, end); - Set includes = Collections.singleton(new Include("*", true)); - Set newPids = loadIncludes(myCallingDao, myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated()); - myCurrentIterator = newPids.iterator(); + int start = myCurrentOffset; + int end = myCurrentOffset + myPageSize; + if (end > myCurrentPids.size()) { + end = myCurrentPids.size(); } + if (end - start <= 0) { + myNext = NO_MORE; + break; + } + myCurrentOffset = end; + Collection pidsToScan = myCurrentPids.subList(start, end); + Set includes = Collections.singleton(new Include("*", true)); + Set newPids = loadIncludes(myCallingDao, myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated()); + myCurrentIterator = newPids.iterator(); } } @@ -2151,7 +2150,7 @@ public class SearchBuilder implements ISearchBuilder { @Override public boolean hasNext() { fetchNext(); - return myNext != NO_MORE; + return !NO_MORE.equals(myNext); } @Override @@ -2268,7 +2267,7 @@ public class SearchBuilder implements ISearchBuilder { myFirst = false; } - if (myNext == NO_MORE) { + if (NO_MORE.equals(myNext)) { ourLog.debug("Query found {} matches in {}ms for query {}", myPidSet.size(), myStopwatch.getMillis(), mySearchUuid); } @@ -2279,7 +2278,7 @@ public class SearchBuilder implements ISearchBuilder { if (myNext == null) { fetchNext(); } - return myNext != NO_MORE; + return !NO_MORE.equals(myNext); } @Override @@ -2287,7 +2286,7 @@ public class SearchBuilder implements ISearchBuilder { fetchNext(); Long retVal = myNext; myNext = null; - Validate.isTrue(retVal != NO_MORE, "No more elements"); + Validate.isTrue(!NO_MORE.equals(retVal), "No more elements"); return retVal; } @@ -2301,7 +2300,7 @@ public class SearchBuilder implements ISearchBuilder { private final Set myUniqueQueryStrings; private Iterator myWrap = null; - public UniqueIndexIterator(Set theUniqueQueryStrings) { + UniqueIndexIterator(Set theUniqueQueryStrings) { myUniqueQueryStrings = theUniqueQueryStrings; } @@ -2343,7 +2342,7 @@ public class SearchBuilder implements ISearchBuilder { private boolean myCountLoaded; private Long myCount; - public CountQueryIterator(TypedQuery theQuery) { + CountQueryIterator(TypedQuery theQuery) { myQuery = theQuery; } @@ -2374,7 +2373,7 @@ public class SearchBuilder implements ISearchBuilder { private final JoinEnum myJoinType; private final String myParamName; - public JoinKey(String theParamName, JoinEnum theJoinType) { + JoinKey(String theParamName, JoinEnum theJoinType) { super(); myParamName = theParamName; myJoinType = theJoinType; @@ -2382,6 +2381,9 @@ public class SearchBuilder implements ISearchBuilder { @Override public boolean equals(Object theObj) { + if (!(theObj instanceof JoinKey)) { + return false; + } JoinKey obj = (JoinKey) theObj; return new EqualsBuilder() .append(myParamName, obj.myParamName) @@ -2482,8 +2484,7 @@ public class SearchBuilder implements ISearchBuilder { cq.where(SearchBuilder.toArray(lastUpdatedPredicates)); TypedQuery query = theEntityManager.createQuery(cq); - List resultList = query.getResultList(); - return resultList; + return query.getResultList(); } @VisibleForTesting @@ -2504,8 +2505,8 @@ public class SearchBuilder implements ISearchBuilder { ourTrackHandlersForUnitTest = true; } - static Predicate[] toArray(List thePredicates) { - return thePredicates.toArray(new Predicate[thePredicates.size()]); + private static Predicate[] toArray(List thePredicates) { + return thePredicates.toArray(new Predicate[0]); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java index a232e1aadf2..5f8a18d5bbf 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java @@ -46,7 +46,9 @@ import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.ValidateUtil; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.commons.lang3.time.DateUtils; +import org.apache.commons.lang3.tuple.Pair; import org.hl7.fhir.instance.model.IdType; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -62,7 +64,7 @@ import org.springframework.scheduling.annotation.Scheduled; import javax.annotation.PostConstruct; import java.util.*; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -84,6 +86,7 @@ public class SubscriptionTriggeringProvider implements IResourceProvider, Applic @Autowired private ISearchCoordinatorSvc mySearchCoordinatorSvc; private ApplicationContext myAppCtx; + private ExecutorService myExecutorService; /** * Sets the maximum number of resources that will be submitted in a single pass @@ -105,6 +108,37 @@ public class SubscriptionTriggeringProvider implements IResourceProvider, Applic Collection values1 = myAppCtx.getBeansOfType(BaseSubscriptionInterceptor.class).values(); Collection> values = (Collection>) values1; mySubscriptionInterceptorList.addAll(values); + + + LinkedBlockingQueue executorQueue = new LinkedBlockingQueue<>(1000); + BasicThreadFactory threadFactory = new BasicThreadFactory.Builder() + .namingPattern("SubscriptionTriggering-%d") + .daemon(false) + .priority(Thread.NORM_PRIORITY) + .build(); + RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() { + @Override + public void rejectedExecution(Runnable theRunnable, ThreadPoolExecutor theExecutor) { + ourLog.info("Note: Subscription triggering queue is full ({} elements), waiting for a slot to become available!", executorQueue.size()); + StopWatch sw = new StopWatch(); + try { + executorQueue.put(theRunnable); + } catch (InterruptedException theE) { + throw new RejectedExecutionException("Task " + theRunnable.toString() + + " rejected from " + theE.toString()); + } + ourLog.info("Slot become available after {}ms", sw.getMillis()); + } + }; + myExecutorService = new ThreadPoolExecutor( + 0, + 10, + 0L, + TimeUnit.MILLISECONDS, + executorQueue, + threadFactory, + rejectedExecutionHandler); + } @Operation(name = JpaConstants.OPERATION_TRIGGER_SUBSCRIPTION) @@ -227,10 +261,17 @@ public class SubscriptionTriggeringProvider implements IResourceProvider, Applic // Submit individual resources int totalSubmitted = 0; + List>> futures = new ArrayList<>(); while (theJobDetails.getRemainingResourceIds().size() > 0 && totalSubmitted < myMaxSubmitPerPass) { totalSubmitted++; String nextResourceId = theJobDetails.getRemainingResourceIds().remove(0); - submitResource(theJobDetails.getSubscriptionId(), nextResourceId); + Future future = submitResource(theJobDetails.getSubscriptionId(), nextResourceId); + futures.add(Pair.of(nextResourceId, future)); + } + + // Make sure these all succeeded in submitting + if (validateFuturesAndReturnTrueIfWeShouldAbort(futures)) { + return; } // If we don't have an active search started, and one needs to be.. start it @@ -267,14 +308,22 @@ public class SubscriptionTriggeringProvider implements IResourceProvider, Applic List resourceIds = mySearchCoordinatorSvc.getResources(theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex); ourLog.info("Triggering job[{}] delivering {} resources", theJobDetails.getJobId(), theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex); + int highestIndexSubmitted = theJobDetails.getCurrentSearchLastUploadedIndex(); + for (Long next : resourceIds) { IBaseResource nextResource = resourceDao.readByPid(next); - submitResource(theJobDetails.getSubscriptionId(), nextResource); + Future future = submitResource(theJobDetails.getSubscriptionId(), nextResource); + futures.add(Pair.of(nextResource.getIdElement().getIdPart(), future)); totalSubmitted++; - theJobDetails.setCurrentSearchLastUploadedIndex(theJobDetails.getCurrentSearchLastUploadedIndex()+1); + highestIndexSubmitted++; } - int expectedCount = toIndex - fromIndex; + if (validateFuturesAndReturnTrueIfWeShouldAbort(futures)) { + return; + } + + theJobDetails.setCurrentSearchLastUploadedIndex(highestIndexSubmitted); + if (resourceIds.size() == 0 || (theJobDetails.getCurrentSearchCount() != null && toIndex >= theJobDetails.getCurrentSearchCount())) { ourLog.info("Triggering job[{}] search {} has completed ", theJobDetails.getJobId(), theJobDetails.getCurrentSearchUuid()); theJobDetails.setCurrentSearchResourceType(null); @@ -287,15 +336,34 @@ public class SubscriptionTriggeringProvider implements IResourceProvider, Applic ourLog.info("Subscription trigger job[{}] triggered {} resources in {}ms ({} res / second)", theJobDetails.getJobId(), totalSubmitted, sw.getMillis(), sw.getThroughput(totalSubmitted, TimeUnit.SECONDS)); } - private void submitResource(String theSubscriptionId, String theResourceIdToTrigger) { + private boolean validateFuturesAndReturnTrueIfWeShouldAbort(List>> theIdToFutures) { + + for (Pair> next : theIdToFutures) { + String nextDeliveredId = next.getKey(); + try { + Future nextFuture = next.getValue(); + nextFuture.get(); + ourLog.info("Finished redelivering {}", nextDeliveredId); + } catch (Exception e) { + ourLog.error("Failure triggering resource " + nextDeliveredId, e); + return true; + } + } + + // Clear the list since it will potentially get reused + theIdToFutures.clear(); + return false; + } + + private Future submitResource(String theSubscriptionId, String theResourceIdToTrigger) { org.hl7.fhir.r4.model.IdType resourceId = new org.hl7.fhir.r4.model.IdType(theResourceIdToTrigger); IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceId.getResourceType()); IBaseResource resourceToTrigger = dao.read(resourceId); - submitResource(theSubscriptionId, resourceToTrigger); + return submitResource(theSubscriptionId, resourceToTrigger); } - private void submitResource(String theSubscriptionId, IBaseResource theResourceToTrigger) { + private Future submitResource(String theSubscriptionId, IBaseResource theResourceToTrigger) { ourLog.info("Submitting resource {} to subscription {}", theResourceToTrigger.getIdElement().toUnqualifiedVersionless().getValue(), theSubscriptionId); @@ -305,9 +373,13 @@ public class SubscriptionTriggeringProvider implements IResourceProvider, Applic msg.setSubscriptionId(new IdType(theSubscriptionId).toUnqualifiedVersionless().getValue()); msg.setNewPayload(myFhirContext, theResourceToTrigger); - for (BaseSubscriptionInterceptor next : mySubscriptionInterceptorList) { - next.submitResourceModified(msg); - } + return myExecutorService.submit(()->{ + for (BaseSubscriptionInterceptor next : mySubscriptionInterceptorList) { + next.submitResourceModified(msg); + } + return null; + }); + } public void cancelAll() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java index 17f597d548b..551f9893725 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java @@ -34,8 +34,6 @@ public class SubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor @Override protected Optional createDeliveryHandler(CanonicalSubscription theSubscription) { SubscriptionDeliveringRestHookSubscriber value = new SubscriptionDeliveringRestHookSubscriber(getSubscriptionDao(), getChannelType(), this); - // FIXME: remove - ourLog.info("** Creating delivery subscriber " + value + " for " + theSubscription.getIdElementString()); return Optional.of(value); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index 89b114a0724..d6a0342cacf 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -105,7 +105,7 @@ public class TestR4Config extends BaseJavaConfigR4 { DataSource dataSource = ProxyDataSourceBuilder .create(retVal) -// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") + .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") .logSlowQueryBySlf4j(10, TimeUnit.SECONDS) .countQuery(new ThreadQueryCountHolder()) .build(); @@ -125,7 +125,6 @@ public class TestR4Config extends BaseJavaConfigR4 { private Properties jpaProperties() { Properties extraProperties = new Properties(); - extraProperties.put("hibernate.jdbc.batch_size", "1"); extraProperties.put("hibernate.format_sql", "false"); extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.hbm2ddl.auto", "update"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java index fba604cdefc..1a2b7155c1a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.util.TestUtil; +import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.IdType; @@ -109,6 +110,26 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test { assertThat(output.getEntry().get(1).getResponse().getLocation(), matchesPattern("Patient/[a-z0-9]{8}-.*")); + } + + @Test + public void testWritesPerformMinimalSqlStatements() { + Patient p = new Patient(); + p.addIdentifier().setSystem("sys1").setValue("val1"); + p.addIdentifier().setSystem("sys2").setValue("val2"); + + ourLog.info("** About to perform write"); + new ThreadQueryCountHolder().getOrCreateQueryCount("").setInsert(0); + new ThreadQueryCountHolder().getOrCreateQueryCount("").setUpdate(0); + + myPatientDao.create(p); + + ourLog.info("** Done performing write"); + + ourLog.info("Inserts: {}", new ThreadQueryCountHolder().getOrCreateQueryCount("").getInsert()); + ourLog.info("Updates: {}", new ThreadQueryCountHolder().getOrCreateQueryCount("").getUpdate()); + + } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index a40474af6da..9a8f49cd6e1 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -105,6 +105,17 @@ batch operation. As of this version, when authorizing a transaction operation (via the transaction() rule), both batch and transaction will be allowed. + + The JPA server now automatically supplies several appropriate hibernate performance + settings as long as the JPA EntityManagerFactory was created using HAPI FHIR's + built-in method for creating it. +
]]> + Existing JPA projects should consider using + super.entityManagerFactory()]]> + as shown in + the example project]]> + if they are not already. +