Skip to content

Commit 88c750f

Browse files
committed
db query and index, as well as java code performance improvements
- New "not found" cache for metadata resources by Url and by Url and Version queries. - Added "current" column to resource tables with default value "true", after insert trigger sets current to false for old versions. New after insert triggers for resource tables that are not part of read_accesss mechanism: Questionnaire, StructureDefinition (snapshot) and Task. Update queries run on db schema migration to set current to false for existing older resource versions. Updated current... and dependent views. - Added a few new db indexes: ...id_current_index to speedup finding the current resource via the current... views, ...full_id and ...ref indexes to speedup queries with OrganizationAffiliation to Organization and Endpoint relations. - Refactored SQL queries using concat(...) to || (concat operator) to enable index usage. - A few other refactored queries to make better use of new current indexes.
1 parent c3d52fd commit 88c750f

76 files changed

Lines changed: 1152 additions & 162 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dsf-fhir/dsf-fhir-server/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@
189189
<groupId>org.postgresql</groupId>
190190
<artifactId>postgresql</artifactId>
191191
</dependency>
192+
<dependency>
193+
<groupId>com.github.ben-manes.caffeine</groupId>
194+
<artifactId>caffeine</artifactId>
195+
</dependency>
192196

193197
<dependency>
194198
<groupId>com.sun.mail</groupId>

dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/ReadByUrlDao.java

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,71 @@
1616
package dev.dsf.fhir.dao;
1717

1818
import java.sql.Connection;
19+
import java.sql.ResultSet;
1920
import java.sql.SQLException;
2021
import java.util.Optional;
22+
import java.util.function.Supplier;
2123

22-
import org.hl7.fhir.r4.model.DomainResource;
24+
import javax.sql.DataSource;
2325

24-
public interface ReadByUrlDao<R extends DomainResource>
26+
import org.hl7.fhir.r4.model.MetadataResource;
27+
28+
import dev.dsf.fhir.function.BiFunctionWithSqlException;
29+
30+
public interface ReadByUrlDao<R extends MetadataResource>
2531
{
32+
@FunctionalInterface
33+
public interface ReadByUrlDaoFactory<R extends MetadataResource>
34+
{
35+
ReadByUrlDao<R> create(Supplier<DataSource> dataSourceSupplier,
36+
BiFunctionWithSqlException<ResultSet, Integer, R> resourceExtractor, String resourceTable,
37+
String resourceColumn);
38+
}
39+
40+
/**
41+
* @param urlAndVersion
42+
* not <code>null</code>, url|version
43+
* @return {@link Optional#empty()} if param <code>urlAndVersion</code> is null or {@link String#isBlank()}
44+
* @throws SQLException
45+
* if database access errors occur
46+
*/
2647
Optional<R> readByUrlAndVersion(String urlAndVersion) throws SQLException;
2748

49+
/**
50+
* @param url
51+
* not <code>null</code>
52+
* @param version
53+
* may be <code>null</code>
54+
* @return {@link Optional#empty()} if param <code>url</code> is null or {@link String#isBlank()}
55+
* @throws SQLException
56+
* if database access errors occur
57+
*/
2858
Optional<R> readByUrlAndVersion(String url, String version) throws SQLException;
2959

60+
/**
61+
* @param connection
62+
* not <code>null</code>
63+
* @param urlAndVersion
64+
* not <code>null</code>, url|version
65+
* @return {@link Optional#empty()} if param <code>urlAndVersion</code> is null or {@link String#isBlank()}
66+
* @throws SQLException
67+
* if database access errors occur
68+
*/
3069
Optional<R> readByUrlAndVersionWithTransaction(Connection connection, String urlAndVersion) throws SQLException;
3170

71+
/**
72+
* @param connection
73+
* not <code>null</code>
74+
* @param url
75+
* not <code>null</code>
76+
* @param version
77+
* may be <code>null</code>
78+
* @return {@link Optional#empty()} if param <code>url</code> is null or {@link String#isBlank()}
79+
* @throws SQLException
80+
* if database access errors occur
81+
*/
3282
Optional<R> readByUrlAndVersionWithTransaction(Connection connection, String url, String version)
3383
throws SQLException;
84+
85+
void onResourceCreated(R resource);
3486
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2018-2025 Heilbronn University of Applied Sciences
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.dsf.fhir.dao.cache;
17+
18+
import java.sql.Connection;
19+
import java.sql.SQLException;
20+
import java.util.Objects;
21+
import java.util.Optional;
22+
import java.util.concurrent.TimeUnit;
23+
24+
import org.hl7.fhir.r4.model.MetadataResource;
25+
26+
import com.github.benmanes.caffeine.cache.Cache;
27+
import com.github.benmanes.caffeine.cache.Caffeine;
28+
29+
import dev.dsf.fhir.dao.ReadByUrlDao;
30+
31+
public class ReadByUrlDaoNotFoundCache<R extends MetadataResource> implements ReadByUrlDao<R>
32+
{
33+
private final static record UrlAndVersion(String url, String version)
34+
{
35+
}
36+
37+
private final Cache<UrlAndVersion, Boolean> notFoundCache = Caffeine.newBuilder()
38+
.expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).build();
39+
40+
private final ReadByUrlDao<R> delegate;
41+
42+
public ReadByUrlDaoNotFoundCache(ReadByUrlDao<R> delegate)
43+
{
44+
this.delegate = delegate;
45+
}
46+
47+
@Override
48+
public Optional<R> readByUrlAndVersion(String urlAndVersion) throws SQLException
49+
{
50+
if (urlAndVersion == null || urlAndVersion.isBlank())
51+
return Optional.empty();
52+
53+
String[] split = urlAndVersion.split("[|]");
54+
if (split.length < 1 || split.length > 2)
55+
return Optional.empty();
56+
57+
return readByUrlAndVersion(split[0], split.length == 2 ? split[1] : null);
58+
}
59+
60+
@Override
61+
public Optional<R> readByUrlAndVersion(String url, String version) throws SQLException
62+
{
63+
if (Boolean.TRUE.equals(notFoundCache.getIfPresent(new UrlAndVersion(url, version))))
64+
return Optional.empty();
65+
66+
Optional<R> r = delegate.readByUrlAndVersion(url, version);
67+
68+
if (r.isEmpty())
69+
notFoundCache.put(new UrlAndVersion(url, version), Boolean.TRUE);
70+
71+
return r;
72+
}
73+
74+
@Override
75+
public Optional<R> readByUrlAndVersionWithTransaction(Connection connection, String urlAndVersion)
76+
throws SQLException
77+
{
78+
Objects.requireNonNull(connection, "connection");
79+
if (urlAndVersion == null || urlAndVersion.isBlank())
80+
return Optional.empty();
81+
82+
String[] split = urlAndVersion.split("[|]");
83+
if (split.length < 1 || split.length > 2)
84+
return Optional.empty();
85+
86+
return readByUrlAndVersionWithTransaction(connection, split[0], split.length == 2 ? split[1] : null);
87+
}
88+
89+
@Override
90+
public Optional<R> readByUrlAndVersionWithTransaction(Connection connection, String url, String version)
91+
throws SQLException
92+
{
93+
if (Boolean.TRUE.equals(notFoundCache.getIfPresent(new UrlAndVersion(url, version))))
94+
return Optional.empty();
95+
96+
Optional<R> r = delegate.readByUrlAndVersionWithTransaction(connection, url, version);
97+
98+
if (r.isEmpty())
99+
notFoundCache.put(new UrlAndVersion(url, version), Boolean.TRUE);
100+
101+
return r;
102+
}
103+
104+
@Override
105+
public void onResourceCreated(R resource)
106+
{
107+
notFoundCache.invalidate(new UrlAndVersion(resource.getUrl(), null));
108+
notFoundCache.invalidate(new UrlAndVersion(resource.getUrl(), resource.getVersion()));
109+
}
110+
}

dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/AbstractResourceDaoJdbc.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -363,11 +363,17 @@ private R create(LargeObjectManager largeObjectManager, Connection connection, R
363363
statement.execute();
364364
}
365365

366+
onResourceCreated(resource);
367+
366368
return resource;
367369
}
368370

369371
protected abstract R copy(R resource);
370372

373+
protected void onResourceCreated(R resource)
374+
{
375+
}
376+
371377
protected R getResource(ResultSet result, int index) throws SQLException
372378
{
373379
String json = result.getString(index);
@@ -582,14 +588,14 @@ public boolean existsNotDeletedWithTransaction(Connection connection, String idS
582588

583589
if (versionString == null || versionString.isBlank())
584590
{
585-
try (PreparedStatement statement = connection.prepareStatement("SELECT deleted IS NOT NULL FROM "
586-
+ resourceTable + " WHERE " + resourceIdColumn + " = ? ORDER BY version DESC LIMIT 1"))
591+
try (PreparedStatement statement = connection.prepareStatement(
592+
"SELECT deleted IS NULL FROM " + resourceTable + " WHERE " + resourceIdColumn + " = ? AND current"))
587593
{
588594
statement.setObject(1, preparedStatementFactory.uuidToPgObject(uuid));
589595

590596
try (ResultSet result = statement.executeQuery())
591597
{
592-
return result.next() && !result.getBoolean(1);
598+
return result.next() && result.getBoolean(1);
593599
}
594600
}
595601
}
@@ -599,15 +605,15 @@ public boolean existsNotDeletedWithTransaction(Connection connection, String idS
599605
if (version == null || version < FIRST_VERSION)
600606
return false;
601607

602-
try (PreparedStatement statement = connection.prepareStatement("SELECT deleted IS NOT NULL FROM "
608+
try (PreparedStatement statement = connection.prepareStatement("SELECT deleted IS NULL FROM "
603609
+ resourceTable + " WHERE " + resourceIdColumn + " = ? AND version = ?"))
604610
{
605611
statement.setObject(1, preparedStatementFactory.uuidToPgObject(uuid));
606612
statement.setLong(2, version);
607613

608614
try (ResultSet result = statement.executeQuery())
609615
{
610-
return result.next() && !result.getBoolean(1);
616+
return result.next() && result.getBoolean(1);
611617
}
612618
}
613619
}
@@ -781,7 +787,7 @@ protected final Optional<LatestVersion> getLatestVersionIfExists(UUID uuid, Conn
781787
return Optional.empty();
782788

783789
try (PreparedStatement statement = connection.prepareStatement("SELECT version, deleted IS NOT NULL FROM "
784-
+ resourceTable + " WHERE " + resourceIdColumn + " = ? ORDER BY version DESC LIMIT 1"))
790+
+ resourceTable + " WHERE " + resourceIdColumn + " = ? AND current"))
785791
{
786792
statement.setObject(1, preparedStatementFactory.uuidToPgObject(uuid));
787793

dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/AbstractStructureDefinitionDaoJdbc.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import ca.uhn.fhir.context.FhirContext;
3434
import dev.dsf.common.auth.conf.Identity;
35+
import dev.dsf.fhir.dao.ReadByUrlDao;
3536
import dev.dsf.fhir.dao.StructureDefinitionDao;
3637
import dev.dsf.fhir.search.SearchQueryIdentityFilter;
3738
import dev.dsf.fhir.search.SearchQueryParameter;
@@ -58,12 +59,13 @@ private static <R extends Resource> SearchQueryParameterFactory<R> factory(Strin
5859
return factory(parameterName, () -> supplier.apply(resourceColumn));
5960
}
6061

61-
private final ReadByUrlDaoJdbc<StructureDefinition> readByUrl;
62+
private final ReadByUrlDao<StructureDefinition> readByUrl;
6263
private final String readByBaseDefinition;
6364

6465
protected AbstractStructureDefinitionDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource,
6566
FhirContext fhirContext, String resourceTable, String resourceColumn, String resourceIdColumn,
66-
Function<Identity, SearchQueryIdentityFilter> userFilter)
67+
Function<Identity, SearchQueryIdentityFilter> userFilter,
68+
ReadByUrlDaoFactory<StructureDefinition> readByUrlDaoFactory)
6769
{
6870
super(dataSource, permanentDeleteDataSource, fhirContext, StructureDefinition.class, resourceTable,
6971
resourceColumn, resourceIdColumn, userFilter,
@@ -80,12 +82,18 @@ protected AbstractStructureDefinitionDaoJdbc(DataSource dataSource, DataSource p
8082
StructureDefinitionVersion::new, StructureDefinitionVersion.getNameModifiers())),
8183
List.of());
8284

83-
readByUrl = new ReadByUrlDaoJdbc<>(this::getDataSource, this::getResource, resourceTable, resourceColumn);
85+
readByUrl = readByUrlDaoFactory.create(this::getDataSource, this::getResource, resourceTable, resourceColumn);
8486

8587
readByBaseDefinition = "SELECT " + resourceColumn + " FROM current_" + resourceTable + " WHERE "
8688
+ resourceColumn + "->>'baseDefinition' = ?";
8789
}
8890

91+
@Override
92+
public void onResourceCreated(StructureDefinition resource)
93+
{
94+
readByUrl.onResourceCreated(resource);
95+
}
96+
8997
@Override
9098
public Optional<StructureDefinition> readByUrlAndVersion(String urlAndVersion) throws SQLException
9199
{

dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/ActivityDefinitionDaoJdbc.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import ca.uhn.fhir.context.FhirContext;
3434
import dev.dsf.fhir.dao.ActivityDefinitionDao;
35+
import dev.dsf.fhir.dao.ReadByUrlDao;
3536
import dev.dsf.fhir.search.filter.ActivityDefinitionIdentityFilter;
3637
import dev.dsf.fhir.search.parameters.ActivityDefinitionDate;
3738
import dev.dsf.fhir.search.parameters.ActivityDefinitionIdentifier;
@@ -45,10 +46,10 @@ public class ActivityDefinitionDaoJdbc extends AbstractResourceDaoJdbc<ActivityD
4546
{
4647
private static final Logger logger = LoggerFactory.getLogger(ActivityDefinitionDaoJdbc.class);
4748

48-
private final ReadByUrlDaoJdbc<ActivityDefinition> readByUrl;
49+
private final ReadByUrlDao<ActivityDefinition> readByUrl;
4950

5051
public ActivityDefinitionDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource,
51-
FhirContext fhirContext)
52+
FhirContext fhirContext, ReadByUrlDaoFactory<ActivityDefinition> readByUrlDaoFactory)
5253
{
5354
super(dataSource, permanentDeleteDataSource, fhirContext, ActivityDefinition.class, "activity_definitions",
5455
"activity_definition", "activity_definition_id", ActivityDefinitionIdentityFilter::new,
@@ -64,10 +65,16 @@ public ActivityDefinitionDaoJdbc(DataSource dataSource, DataSource permanentDele
6465
ActivityDefinitionVersion.getNameModifiers())),
6566
List.of());
6667

67-
readByUrl = new ReadByUrlDaoJdbc<>(this::getDataSource, this::getResource, getResourceTable(),
68+
readByUrl = readByUrlDaoFactory.create(this::getDataSource, this::getResource, getResourceTable(),
6869
getResourceColumn());
6970
}
7071

72+
@Override
73+
public void onResourceCreated(ActivityDefinition resource)
74+
{
75+
readByUrl.onResourceCreated(resource);
76+
}
77+
7178
@Override
7279
protected ActivityDefinition copy(ActivityDefinition resource)
7380
{

dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/CodeSystemDaoJdbc.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import ca.uhn.fhir.context.FhirContext;
2828
import dev.dsf.fhir.dao.CodeSystemDao;
29+
import dev.dsf.fhir.dao.ReadByUrlDao;
2930
import dev.dsf.fhir.search.filter.CodeSystemIdentityFilter;
3031
import dev.dsf.fhir.search.parameters.CodeSystemDate;
3132
import dev.dsf.fhir.search.parameters.CodeSystemIdentifier;
@@ -36,9 +37,10 @@
3637

3738
public class CodeSystemDaoJdbc extends AbstractResourceDaoJdbc<CodeSystem> implements CodeSystemDao
3839
{
39-
private final ReadByUrlDaoJdbc<CodeSystem> readByUrl;
40+
private final ReadByUrlDao<CodeSystem> readByUrl;
4041

41-
public CodeSystemDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext)
42+
public CodeSystemDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSource, FhirContext fhirContext,
43+
ReadByUrlDaoFactory<CodeSystem> readByUrlDaoFactory)
4244
{
4345
super(dataSource, permanentDeleteDataSource, fhirContext, CodeSystem.class, "code_systems", "code_system",
4446
"code_system_id", CodeSystemIdentityFilter::new,
@@ -53,10 +55,16 @@ public CodeSystemDaoJdbc(DataSource dataSource, DataSource permanentDeleteDataSo
5355
CodeSystemVersion.getNameModifiers())),
5456
List.of());
5557

56-
readByUrl = new ReadByUrlDaoJdbc<>(this::getDataSource, this::getResource, getResourceTable(),
58+
readByUrl = readByUrlDaoFactory.create(this::getDataSource, this::getResource, getResourceTable(),
5759
getResourceColumn());
5860
}
5961

62+
@Override
63+
public void onResourceCreated(CodeSystem resource)
64+
{
65+
readByUrl.onResourceCreated(resource);
66+
}
67+
6068
@Override
6169
protected CodeSystem copy(CodeSystem resource)
6270
{

0 commit comments

Comments
 (0)