select = cq.select(qb.count(cq.from(rootType)));
+ long count = em.createQuery(select).getSingleResult();
+ return count;
+ }
+
+ @Nonnull
+ EntityManager getEntityManagerOrThrow() {
+ return Objects.requireNonNull(EntityManagerFactoryUtils.getTransactionalEntityManager(myEntityManagerFactory));
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/IJoinEntity.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/IJoinEntity.java
new file mode 100644
index 00000000000..4e0c3f45e83
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/IJoinEntity.java
@@ -0,0 +1,17 @@
+package ca.uhn.fhir.jpa.model.pkspike;
+
+public interface IJoinEntity {
+ Long getPid();
+
+ void setString(String theString);
+
+ void setParent(P theRoot);
+
+ String getString();
+
+ void setPartitionId(Integer thePartitionId);
+
+ Integer getPartitionId();
+
+ Long getResId();
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/IRootEntity.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/IRootEntity.java
new file mode 100644
index 00000000000..4acf4fabb18
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/IRootEntity.java
@@ -0,0 +1,17 @@
+package ca.uhn.fhir.jpa.model.pkspike;
+
+import java.util.Collection;
+
+public interface IRootEntity {
+ Long getResId();
+
+ void setPartitionId(Integer thePartitionId);
+
+ Integer getPartitionId();
+
+ String getString();
+
+ void setString(String theString);
+
+ Collection getJoins();
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/PKSpikeDefaultJPAConfig.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/PKSpikeDefaultJPAConfig.java
new file mode 100644
index 00000000000..a5d1ff202de
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/PKSpikeDefaultJPAConfig.java
@@ -0,0 +1,109 @@
+package ca.uhn.fhir.jpa.model.pkspike;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
+import ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect;
+import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean;
+import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import jakarta.inject.Inject;
+import jakarta.persistence.EntityManagerFactory;
+import org.apache.commons.dbcp2.BasicDataSource;
+import org.hibernate.jpa.HibernatePersistenceProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import javax.sql.DataSource;
+import java.time.Duration;
+import java.util.Properties;
+import java.util.function.Consumer;
+
+@Configuration
+public class PKSpikeDefaultJPAConfig {
+
+ @Inject
+ FhirContext myFhirContext;
+
+ @Bean
+ DataSource datasource() {
+ BasicDataSource dataSource = new BasicDataSource();
+ dataSource.setDriver(new org.h2.Driver());
+ dataSource.setUrl("jdbc:h2:mem:testdb_r4" + System.currentTimeMillis());
+ dataSource.setMaxWait(Duration.ofMillis(30000));
+ dataSource.setUsername("");
+ dataSource.setPassword("");
+ dataSource.setMaxTotal(10);
+
+ SchemaInit.initSchema(dataSource);
+
+ return dataSource;
+ }
+
+
+ @Bean
+ public HapiFhirLocalContainerEntityManagerFactoryBean entityManagerFactory(
+// ModuleMigrationMetadata theModuleMigrationMetadata,
+ ConfigurableListableBeanFactory theConfigurableListableBeanFactory,
+ DataSource theDataSource,
+ PersistenceManagedTypes theManagedTypes,
+ @Autowired(required = false) @Nullable Consumer theEntityManagerFactoryCustomizer) {
+
+ HapiFhirLocalContainerEntityManagerFactoryBean entityManagerFactoryBean =
+ new HapiFhirLocalContainerEntityManagerFactoryBean(theConfigurableListableBeanFactory);
+
+ entityManagerFactoryBean.setJpaDialect(new HapiFhirHibernateJpaDialect(myFhirContext.getLocalizer()));
+ HibernatePersistenceProvider persistenceProvider = new HibernatePersistenceProvider();
+ entityManagerFactoryBean.setPersistenceProvider(persistenceProvider);
+ Properties jpaProperties = new Properties();
+ jpaProperties.put("hibernate.search.enabled", "false");
+ jpaProperties.put("hibernate.format_sql", "false");
+ jpaProperties.put("hibernate.show_sql", "false");
+ jpaProperties.put("hibernate.integration.envers.enabled=false", "false");
+ jpaProperties.put("hibernate.hbm2ddl.auto", "none");
+ jpaProperties.put("hibernate.dialect", HapiFhirH2Dialect.class.getName());
+ entityManagerFactoryBean.setJpaProperties(jpaProperties);
+
+ entityManagerFactoryBean.setPersistenceUnitName("HapiPU");
+ entityManagerFactoryBean.setDataSource(theDataSource);
+ entityManagerFactoryBean.setManagedTypes(theManagedTypes);
+
+ if (theEntityManagerFactoryCustomizer != null) {
+ theEntityManagerFactoryCustomizer.accept(entityManagerFactoryBean);
+ }
+
+ return entityManagerFactoryBean;
+ }
+
+ @Bean
+ public PlatformTransactionManager transactionManager(EntityManagerFactory theEntityManagerFactory) {
+ JpaTransactionManager retVal = new JpaTransactionManager();
+ retVal.setEntityManagerFactory(theEntityManagerFactory);
+ return retVal;
+ }
+
+
+ @Bean
+ public TransactionTemplate transactionTemplate(PlatformTransactionManager theTransactionManager) {
+ return new TransactionTemplate(theTransactionManager);
+ }
+
+ @Bean
+ public JdbcTemplate jdbcTemplate(DataSource theDataSource) {
+ return new JdbcTemplate(theDataSource);
+ }
+
+ @Bean
+ @Nonnull
+ JpaStorageSettings storageSettings() {
+ JpaStorageSettings jpaStorageSettings = new JpaStorageSettings();
+ return jpaStorageSettings;
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/SchemaCleanerExtension.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/SchemaCleanerExtension.java
new file mode 100644
index 00000000000..69005b5ee2f
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/SchemaCleanerExtension.java
@@ -0,0 +1,22 @@
+package ca.uhn.fhir.jpa.model.pkspike;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.springframework.context.ApplicationContext;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import javax.sql.DataSource;
+
+public class SchemaCleanerExtension implements AfterEachCallback {
+
+ @Override
+ public void afterEach(ExtensionContext theExtensionContext) throws Exception {
+ ApplicationContext springContext = SpringExtension.getApplicationContext(theExtensionContext);
+ var dataSource = springContext.getBean(DataSource.class);
+
+ JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
+ jdbcTemplate.execute("delete from res_join");
+ jdbcTemplate.execute("delete from res_root");
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/SchemaInit.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/SchemaInit.java
new file mode 100644
index 00000000000..feceaf51e99
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/SchemaInit.java
@@ -0,0 +1,16 @@
+package ca.uhn.fhir.jpa.model.pkspike;
+
+import org.apache.commons.dbcp2.BasicDataSource;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+class SchemaInit {
+ public static void initSchema(BasicDataSource theDataSource) {
+ var t = new JdbcTemplate(theDataSource);
+
+ t.execute("CREATE SEQUENCE RES_ROOT_SEQ increment by 50");
+ t.execute("CREATE SEQUENCE RES_JOIN_SEQ increment by 50");
+ t.execute("create table res_root (RES_ID IDENTITY PRIMARY KEY, PARTITION_ID numeric, STRING_COL varchar)");
+ t.execute("create table res_join (PID IDENTITY PRIMARY KEY, RES_ID numeric, PARTITION_ID numeric, STRING_COL varchar)");
+
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/ValueTypeBasedParameterResolver.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/ValueTypeBasedParameterResolver.java
new file mode 100644
index 00000000000..9ab2bada6c0
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/ValueTypeBasedParameterResolver.java
@@ -0,0 +1,33 @@
+package ca.uhn.fhir.jpa.model.pkspike;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+
+public class ValueTypeBasedParameterResolver implements ParameterResolver {
+
+ private final T myValue;
+
+ public static ValueTypeBasedParameterResolver build(T theValue) {
+ return new ValueTypeBasedParameterResolver<>(theValue);
+ }
+
+ ValueTypeBasedParameterResolver(T theValue) {
+ myValue = theValue;
+ }
+
+ @Override
+ public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+ return parameterContext.getParameter().getType().isAssignableFrom(myValue.getClass());
+ }
+
+ @Override
+ public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+ return myValue;
+ }
+
+ public T get() {
+ return myValue;
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/EmbeddedIdPkJpaBindingTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/EmbeddedIdPkJpaBindingTest.java
new file mode 100644
index 00000000000..408fe64b947
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/EmbeddedIdPkJpaBindingTest.java
@@ -0,0 +1,37 @@
+package ca.uhn.fhir.jpa.model.pkspike.embeddedid;
+
+import ca.uhn.fhir.jpa.config.r4.FhirContextR4Config;
+import ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestTemplate;
+import ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestFixture;
+import ca.uhn.fhir.jpa.model.pkspike.PKSpikeDefaultJPAConfig;
+import ca.uhn.fhir.jpa.model.pkspike.ValueTypeBasedParameterResolver;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * Spike to assess variable binding against a db.
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {
+ EmbeddedIdTypesConfig.class, PKSpikeDefaultJPAConfig.class, FhirContextR4Config.class
+})
+public class EmbeddedIdPkJpaBindingTest {
+
+ static final BasicEntityTestFixture ourFixture = BasicEntityTestFixture.buildNoNullPartition(ResRootEmbeddedIdEntity.class, ResJoinEmbeddedIdEntity.class);
+
+ @RegisterExtension
+ static final ParameterResolver ourResolver = ValueTypeBasedParameterResolver.build(ourFixture);
+
+
+ @Nested
+ class Common extends BasicEntityTestTemplate {
+ Common() {
+ super(ourFixture);
+ }
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/EmbeddedIdTypesConfig.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/EmbeddedIdTypesConfig.java
new file mode 100644
index 00000000000..7a290ca3107
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/EmbeddedIdTypesConfig.java
@@ -0,0 +1,17 @@
+package ca.uhn.fhir.jpa.model.pkspike.embeddedid;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
+
+@Configuration
+public class EmbeddedIdTypesConfig {
+ @Bean
+ PersistenceManagedTypes getManagedTypes() {
+ return PersistenceManagedTypes.of(
+ ResRootEmbeddedIdEntity.class.getName(),
+ ResJoinEmbeddedIdEntity.class.getName()
+ );
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/ResJoinEmbeddedIdEntity.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/ResJoinEmbeddedIdEntity.java
new file mode 100644
index 00000000000..b31af0d033b
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/ResJoinEmbeddedIdEntity.java
@@ -0,0 +1,82 @@
+package ca.uhn.fhir.jpa.model.pkspike.embeddedid;
+
+import ca.uhn.fhir.jpa.model.pkspike.IJoinEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.JoinColumns;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@SuppressWarnings("JpaDataSourceORMInspection")
+@Entity
+@Table(
+ name = "RES_JOIN"
+)
+public class ResJoinEmbeddedIdEntity implements IJoinEntity {
+ @Id
+// @GenericGenerator(name = "SEQ_RESOURCE_ID", type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class)
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
+ @Column(name = "PID")
+ Long myId;
+ @Column(name = "PARTITION_ID", nullable = true, insertable = false, updatable = false)
+ Integer myPartitionId;
+ @Column(name = "RES_ID", nullable = false, updatable = false, insertable = false)
+ Long myResourceId;
+ @Column(name = "STRING_COL")
+ String myString;
+
+ // fixme mb which side controls vs reads?
+ @ManyToOne(
+ optional = false)
+ @JoinColumns({
+ @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID"),
+ @JoinColumn(name = "PARTITION_ID", referencedColumnName = "PARTITION_ID")
+ })
+ ResRootEmbeddedIdEntity myResource;
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+
+ @Override
+ public Long getPid() {
+ return myId;
+ }
+
+ @Override
+ public void setString(String theString) {
+ myString = theString;
+ }
+
+ @Override
+ public void setParent(ResRootEmbeddedIdEntity theRoot) {
+ myResource = theRoot;
+ }
+
+ @Override
+ public String getString() {
+ return myString;
+ }
+
+ @Override
+ public void setPartitionId(Integer thePartitionId) {
+ myPartitionId = thePartitionId;
+ }
+
+ @Override
+ public Integer getPartitionId() {
+ return myPartitionId;
+ }
+
+ @Override
+ public Long getResId() {
+ // fixme keep copy
+ return myResource==null?null:myResource.getResId();
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/ResRootEmbeddedIdEntity.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/ResRootEmbeddedIdEntity.java
new file mode 100644
index 00000000000..093e6e22383
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/embeddedid/ResRootEmbeddedIdEntity.java
@@ -0,0 +1,116 @@
+package ca.uhn.fhir.jpa.model.pkspike.embeddedid;
+
+import ca.uhn.fhir.jpa.model.pkspike.IRootEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+import jakarta.persistence.EmbeddedId;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * fixme MB IdClass vs embeddable?
+ *
+ */
+@SuppressWarnings("JpaDataSourceORMInspection")
+@Entity
+@Table(name = "RES_ROOT")
+public class ResRootEmbeddedIdEntity implements IRootEntity {
+ private static final Logger ourLog = LoggerFactory.getLogger(ResRootEmbeddedIdEntity.class);
+
+ @EmbeddedId
+ ResRootPK myId = new ResRootPK();
+
+ @Column(name = "PARTITION_ID", nullable = true, insertable = false, updatable = false)
+ Integer myPartitionId;
+
+ @Column(name = "STRING_COL")
+ String myString;
+
+ @OneToMany(mappedBy = "myResource")
+ Collection myJoinEntities = new ArrayList<>();
+
+ public ResRootEmbeddedIdEntity() {
+ ourLog.info("new ResRootCompositeEntity()");
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+
+ @Override
+ public Long getResId() {
+ return myId==null?null:myId.myId;
+ }
+
+ @Override
+ public void setPartitionId(Integer thePartitionId) {
+ myPartitionId = thePartitionId;
+ myId.myPartitionId = thePartitionId;
+ }
+
+ @Override
+ public Integer getPartitionId() {
+ return myPartitionId;
+ }
+
+ @Override
+ public String getString() {
+ return myString;
+ }
+
+ @Override
+ public void setString(String theString) {
+ myString = theString;
+ }
+
+ @Override
+ public Collection getJoins() {
+ return myJoinEntities;
+ }
+
+ @Embeddable
+ static class ResRootPK {
+ @GeneratedValue()
+ @Column(name = "RES_ID")
+ public Long myId;
+
+ @Column(name = "PARTITION_ID", nullable = true, insertable = true, updatable = false)
+ public Integer myPartitionId;
+
+ /** For Hibernate */
+ protected ResRootPK() {}
+
+ public ResRootPK(Long theId, Integer thePartitionId) {
+ myId = theId;
+ myPartitionId = thePartitionId;
+ }
+
+ @Override
+ public boolean equals(Object theO) {
+ return EqualsBuilder.reflectionEquals(this,theO);
+ }
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/IdClassKeyTypesConfig.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/IdClassKeyTypesConfig.java
new file mode 100644
index 00000000000..2ea0f03e60e
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/IdClassKeyTypesConfig.java
@@ -0,0 +1,17 @@
+package ca.uhn.fhir.jpa.model.pkspike.idclass;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
+
+@Configuration
+public class IdClassKeyTypesConfig {
+ @Bean
+ PersistenceManagedTypes getManagedTypes() {
+ return PersistenceManagedTypes.of(
+ ResRootIdClassEntity.class.getName(),
+ ResJoinIdClassEntity.class.getName()
+ );
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/IdClassPkCustomXmlJpaBindingTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/IdClassPkCustomXmlJpaBindingTest.java
new file mode 100644
index 00000000000..597a289fe68
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/IdClassPkCustomXmlJpaBindingTest.java
@@ -0,0 +1,53 @@
+package ca.uhn.fhir.jpa.model.pkspike.idclass;
+
+import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean;
+import ca.uhn.fhir.jpa.config.r4.FhirContextR4Config;
+import ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestTemplate;
+import ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestFixture;
+import ca.uhn.fhir.jpa.model.pkspike.PKSpikeDefaultJPAConfig;
+import ca.uhn.fhir.jpa.model.pkspike.ValueTypeBasedParameterResolver;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.function.Consumer;
+
+/**
+ * Override the JPA annotations with an orm.xml file to add PARTITION_ID to the root PK, and the join expressions.
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {
+ IdClassPkCustomXmlJpaBindingTest.Config.class, IdClassKeyTypesConfig.class, PKSpikeDefaultJPAConfig.class, FhirContextR4Config.class
+})
+public class IdClassPkCustomXmlJpaBindingTest {
+ private static final Logger ourLog = LoggerFactory.getLogger(IdClassPkCustomXmlJpaBindingTest.class);
+
+ @Configuration
+ static class Config {
+ @Bean
+ Consumer entityManagerFactoryCustomizer() {
+ return em->{
+ ourLog.info("Injecting custom persistence.xml");
+ em.setMappingResources("/ca/uhn/fhir/jpa/model/pkspike/idclass/ormComposite.xml");
+ };
+ }
+ }
+
+ static final BasicEntityTestFixture ourFixture = BasicEntityTestFixture.buildNoNullPartition(ResRootIdClassEntity.class, ResJoinIdClassEntity.class);
+ @RegisterExtension
+ static final ParameterResolver ourFixtureResolver = ValueTypeBasedParameterResolver.build(ourFixture);
+
+ @Nested
+ class Common extends BasicEntityTestTemplate {
+ Common() {
+ super(ourFixture);
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/IdClassPkJpaBindingTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/IdClassPkJpaBindingTest.java
new file mode 100644
index 00000000000..41786f1b91a
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/IdClassPkJpaBindingTest.java
@@ -0,0 +1,54 @@
+package ca.uhn.fhir.jpa.model.pkspike.idclass;
+
+import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean;
+import ca.uhn.fhir.jpa.config.r4.FhirContextR4Config;
+import ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestTemplate;
+import ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestFixture;
+import ca.uhn.fhir.jpa.model.pkspike.PKSpikeDefaultJPAConfig;
+import ca.uhn.fhir.jpa.model.pkspike.ValueTypeBasedParameterResolver;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.function.Consumer;
+
+/**
+ * Use an IdClass even though the PK is only a single column.
+ * This allows us to extend the PK next door in the {@link IdClassPkCustomXmlJpaBindingTest}.
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {
+ IdClassPkJpaBindingTest.Config.class, IdClassKeyTypesConfig.class, PKSpikeDefaultJPAConfig.class, FhirContextR4Config.class
+})
+public class IdClassPkJpaBindingTest {
+ private static final Logger ourLog = LoggerFactory.getLogger(IdClassPkJpaBindingTest.class);
+
+ @Configuration
+ static class Config {
+ @Bean
+ Consumer entityManagerFactoryCustomizer() {
+ return em->{
+ ourLog.info("Injecting custom persistence.xml");
+ em.setMappingResources("/ca/uhn/fhir/jpa/model/pkspike/idclass/ormLong.xml");
+ };
+ }
+ }
+
+ public static final BasicEntityTestFixture ourFixture = BasicEntityTestFixture.build(ResRootIdClassEntity.class, ResJoinIdClassEntity.class);
+ @RegisterExtension
+ static final ParameterResolver ourFixtureResolver = ValueTypeBasedParameterResolver.build(ourFixture);
+
+ @Nested
+ class Common extends BasicEntityTestTemplate {
+ Common() {
+ super(ourFixture);
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/ResJoinIdClassEntity.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/ResJoinIdClassEntity.java
new file mode 100644
index 00000000000..8d32df41d0e
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/ResJoinIdClassEntity.java
@@ -0,0 +1,137 @@
+package ca.uhn.fhir.jpa.model.pkspike.idclass;
+
+import ca.uhn.fhir.jpa.model.pkspike.IJoinEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.IdClass;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.JoinColumns;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.Transient;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.hibernate.annotations.PartitionKey;
+
+@SuppressWarnings("JpaDataSourceORMInspection")
+@Entity
+@Table(
+ name = "RES_JOIN"
+)
+@IdClass(ResJoinIdClassEntity.ResJoinPK.class)
+public class ResJoinIdClassEntity implements IJoinEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
+ @Column(name = "PID")
+ Long myId;
+
+ @PartitionKey
+ @Column(name = "PARTITION_ID", nullable = true, insertable = true, updatable = false)
+ Integer myPartitionId;
+
+ @Column(name = "STRING_COL")
+ String myString;
+
+ // fixme mb which side controls vs reads?
+ @ManyToOne(
+ optional = false)
+ @JoinColumns({
+ @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID"),
+// @JoinColumn(name = "PARTITION_ID", referencedColumnName = "PARTITION_ID")
+ })
+ ResRootIdClassEntity myResource;
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+
+ @Override
+ public Long getPid() {
+ return myId;
+ }
+
+ @Override
+ public void setString(String theString) {
+ myString = theString;
+ }
+
+ @Override
+ public void setParent(ResRootIdClassEntity theRoot) {
+ myResource = theRoot;
+ }
+
+ @Override
+ public String getString() {
+ return myString;
+ }
+
+ @Override
+ public void setPartitionId(Integer thePartitionId) {
+ myPartitionId = thePartitionId;
+ }
+
+ @Override
+ public Integer getPartitionId() {
+ return myPartitionId;
+ }
+
+ @Override
+ public Long getResId() {
+ // fixme keep copy
+ return myResource == null? null: myResource.myId;
+ }
+
+
+ static class ResJoinPK {
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
+ @Column(name = "PID")
+ Long myId;
+
+ /** for Hibernate */
+ public ResJoinPK() {}
+
+
+ public ResJoinPK(Long theId) {
+ myId = theId;
+ }
+
+ @Override
+ public boolean equals(Object theO) {
+ return EqualsBuilder.reflectionEquals(this,theO);
+ }
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+ }
+
+ static class ResJoinCompositePK extends ResJoinPK {
+ @Id
+ @Column(name = "PARTITION_ID", nullable = false, insertable = false, updatable = false)
+ Integer myPartitionId;
+
+ /** for Hibernate */
+ public ResJoinCompositePK() {}
+
+ public ResJoinCompositePK(Long theId, Integer thePartitionId) {
+ super(theId);
+ myPartitionId = thePartitionId;
+ }
+
+
+
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/ResRootIdClassEntity.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/ResRootIdClassEntity.java
new file mode 100644
index 00000000000..23d624cad83
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/idclass/ResRootIdClassEntity.java
@@ -0,0 +1,141 @@
+package ca.uhn.fhir.jpa.model.pkspike.idclass;
+
+import ca.uhn.fhir.jpa.model.pkspike.IRootEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.IdClass;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.hibernate.annotations.PartitionKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * fixme MB IdClass vs embeddable?
+ *
+ */
+@SuppressWarnings("JpaDataSourceORMInspection")
+@Entity
+@Table(name = "RES_ROOT")
+@IdClass(ResRootIdClassEntity.ResRootPK.class)
+public class ResRootIdClassEntity implements IRootEntity {
+ private static final Logger ourLog = LoggerFactory.getLogger(ResRootIdClassEntity.class);
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ @Column(name = "RES_ID")
+ Long myId;
+
+// @Id
+ //@PartitionKey
+ @Column(name = "PARTITION_ID", nullable = true, insertable = true, updatable = false)
+ Integer myPartitionId;
+
+// ResRootPK getPK() {
+// return new ResRootPK(myId, myPartitionId);
+// }
+
+ @Column(name = "STRING_COL")
+ String myString;
+
+ @OneToMany(mappedBy = "myResource")
+ Collection myJoinEntities = new ArrayList<>();
+
+ public ResRootIdClassEntity() {
+ ourLog.info("new ResRootCompositeEntity()");
+ }
+
+ public Long getId() {
+ return myId;
+ }
+
+ @Override
+ public Long getResId() {
+ return myId;
+ }
+
+ @Override
+ public void setPartitionId(Integer thePartitionId) {
+ myPartitionId = thePartitionId;
+ }
+
+ @Override
+ public Integer getPartitionId() {
+ return myPartitionId;
+ }
+
+ public String getString() {
+ return myString;
+ }
+
+ public void setString(String theString) {
+ myString = theString;
+ }
+
+ @Override
+ public Collection getJoins() {
+ return myJoinEntities;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+
+ static class ResRootPK {
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ @Column(name = "RES_ID")
+ Long myId;
+
+ /** for Hibernate */
+ public ResRootPK() {}
+
+
+ public ResRootPK(Long theId) {
+ myId = theId;
+ }
+
+ @Override
+ public boolean equals(Object theO) {
+ return EqualsBuilder.reflectionEquals(this,theO);
+ }
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+ }
+
+ static class ResRootCompositePK extends ResRootPK {
+ @Column(name = "PARTITION_ID", nullable = true, insertable = true, updatable = false)
+ Integer myPartitionId;
+
+ /** for Hibernate */
+ public ResRootCompositePK() {}
+
+ public ResRootCompositePK(Long theId, Integer thePartitionId) {
+ super(theId);
+ myPartitionId = thePartitionId;
+ }
+
+
+
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/package-info.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/package-info.java
new file mode 100644
index 00000000000..7a8cd08e981
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/package-info.java
@@ -0,0 +1,29 @@
+/**
+ * Various tests of a parent-child JPA relationship exercising configurable pk definition and joins.
+ *
+ * We have a test template that does some basic queries ( {@link ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestTemplate}).
+ *
+ * - See {@link ca.uhn.fhir.jpa.model.pkspike.primitive.SimplePkJpaBindingTest} for the normal case. Supports null partition_id.
+ *
- See {@link ca.uhn.fhir.jpa.model.pkspike.embeddedid.EmbeddedIdPkJpaBindingTest} for an embedded Id class path. Does not support null partition_id.
+ *
- See {@link ca.uhn.fhir.jpa.model.pkspike.partitionkey.PartitionJpaBindingTest} for the new Hibernate 6 @{@link org.hibernate.annotations.PartitionKey} annotation. Does support null partition_id.
+ *
- See {@link ca.uhn.fhir.jpa.model.pkspike.idclass.IdClassPkJpaBindingTest}. Supports null partition_id.
+ *
- See {@link ca.uhn.fhir.jpa.model.pkspike.idclass.IdClassPkCustomXmlJpaBindingTest} which adds PARTITION_ID to the pk and join to the mappings defined in IdClassPkJpaBindingTest by adding an orm.xml file. Does not support null partition_id.
+ *
+ *
+ * Things we learned:
+ *
+ *
+ * Add this to logback to explore the sql.
+ *
+ *
+ *
+ *
+ */
+package ca.uhn.fhir.jpa.model.pkspike;
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/PartitionJpaBindingTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/PartitionJpaBindingTest.java
new file mode 100644
index 00000000000..c44595ce4b0
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/PartitionJpaBindingTest.java
@@ -0,0 +1,37 @@
+package ca.uhn.fhir.jpa.model.pkspike.partitionkey;
+
+import ca.uhn.fhir.jpa.config.r4.FhirContextR4Config;
+import ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestTemplate;
+import ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestFixture;
+import ca.uhn.fhir.jpa.model.pkspike.PKSpikeDefaultJPAConfig;
+import ca.uhn.fhir.jpa.model.pkspike.ValueTypeBasedParameterResolver;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * Try out the new @{@link org.hibernate.annotations.PartitionKey} annotation.
+ * This annotation annotates columns to include in entity update/delete statements, so they can be efficient in a partitioned table.
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {
+ PartitionTypesConfig.class, PKSpikeDefaultJPAConfig.class, FhirContextR4Config.class
+})
+class PartitionJpaBindingTest {
+
+ static final BasicEntityTestFixture ourConfig = BasicEntityTestFixture.buildNoNullPartition(ResRootPartitionEntity.class, ResJoinPartitionEntity.class);
+
+ @RegisterExtension
+ static final ParameterResolver ourResolver = ValueTypeBasedParameterResolver.build(ourConfig);
+
+ @Nested
+ class Common extends BasicEntityTestTemplate {
+ Common() {
+ super(ourConfig);
+ }
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/PartitionTypesConfig.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/PartitionTypesConfig.java
new file mode 100644
index 00000000000..2d64ee5c8a2
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/PartitionTypesConfig.java
@@ -0,0 +1,17 @@
+package ca.uhn.fhir.jpa.model.pkspike.partitionkey;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
+
+@Configuration
+public class PartitionTypesConfig {
+ @Bean
+ PersistenceManagedTypes getManagedTypes() {
+ return PersistenceManagedTypes.of(
+ ResRootPartitionEntity.class.getName(),
+ ResJoinPartitionEntity.class.getName()
+ );
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/ResJoinPartitionEntity.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/ResJoinPartitionEntity.java
new file mode 100644
index 00000000000..01a09dd4e3e
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/ResJoinPartitionEntity.java
@@ -0,0 +1,86 @@
+package ca.uhn.fhir.jpa.model.pkspike.partitionkey;
+
+import ca.uhn.fhir.jpa.model.pkspike.IJoinEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.JoinColumns;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.hibernate.annotations.PartitionKey;
+
+@SuppressWarnings("JpaDataSourceORMInspection")
+@Entity
+@Table(
+ name = "RES_JOIN"
+)
+public class ResJoinPartitionEntity implements IJoinEntity {
+ @Id
+// @GenericGenerator(name = "SEQ_RESOURCE_ID", type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class)
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
+ @Column(name = "PID")
+ Long myId;
+
+ @PartitionKey
+ @Column(name = "PARTITION_ID", nullable = true, insertable = false, updatable = false)
+ Integer myPartitionId;
+
+ @Column(name = "STRING_COL")
+ String myString;
+
+ @Column(name = "RES_ID", nullable = false, insertable = false, updatable = false)
+ Long myResId;
+
+ @ManyToOne(
+ optional = false)
+ @JoinColumns({
+ @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, insertable = true, updatable = false),
+ @JoinColumn(name = "PARTITION_ID", referencedColumnName = "PARTITION_ID", nullable = false, insertable = true, updatable = false)
+ })
+ ResRootPartitionEntity myResource;
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ @Override
+ public Long getPid() {
+ return myId;
+ }
+
+ @Override
+ public void setString(String theString) {
+ myString = theString;
+ }
+
+ @Override
+ public void setParent(ResRootPartitionEntity theRoot) {
+ myResource = theRoot;
+ }
+
+ @Override
+ public String getString() {
+ return myString;
+ }
+
+ @Override
+ public void setPartitionId(Integer thePartitionId) {
+ myPartitionId = thePartitionId;
+ }
+
+ @Override
+ public Integer getPartitionId() {
+ return myPartitionId;
+ }
+
+ @Override
+ public Long getResId() {
+ return myResId;
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/ResRootPartitionEntity.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/ResRootPartitionEntity.java
new file mode 100644
index 00000000000..aed3ac1d675
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/partitionkey/ResRootPartitionEntity.java
@@ -0,0 +1,74 @@
+package ca.uhn.fhir.jpa.model.pkspike.partitionkey;
+
+import ca.uhn.fhir.jpa.model.pkspike.IRootEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.hibernate.annotations.PartitionKey;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+@SuppressWarnings("JpaDataSourceORMInspection")
+@Entity
+@Table(name = "RES_ROOT")
+public class ResRootPartitionEntity implements IRootEntity {
+ @Id
+ @GeneratedValue()
+ @Column(name = "RES_ID")
+ Long myId;
+
+ @PartitionKey
+ @Column(name = "PARTITION_ID", nullable = true, insertable = true, updatable = false)
+ Integer myPartitionId;
+
+ @Column(name = "STRING_COL")
+ String myString;
+
+ @OneToMany(mappedBy = "myResource", fetch = FetchType.EAGER)
+ Collection myJoinEntities = new ArrayList<>();
+
+ public Long getId() {
+ return myId;
+ }
+
+ public String getString() {
+ return myString;
+ }
+
+ public void setString(String theString) {
+ myString = theString;
+ }
+
+ @Override
+ public Collection getJoins() {
+ return myJoinEntities;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ @Override
+ public void setPartitionId(Integer thePartitionId) {
+ myPartitionId = thePartitionId;
+ }
+
+ @Override
+ public Integer getPartitionId() {
+ return myPartitionId;
+ }
+
+ @Override
+ public Long getResId() {
+ return myId;
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/ResJoinEntity.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/ResJoinEntity.java
new file mode 100644
index 00000000000..1b023cc7316
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/ResJoinEntity.java
@@ -0,0 +1,83 @@
+package ca.uhn.fhir.jpa.model.pkspike.primitive;
+
+import ca.uhn.fhir.jpa.model.pkspike.IJoinEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+@SuppressWarnings("JpaDataSourceORMInspection")
+@Entity
+@Table(
+ name = "RES_JOIN"
+)
+public class ResJoinEntity implements IJoinEntity {
+ @Id
+// @GenericGenerator(name = "SEQ_RESOURCE_ID", type = ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator.class)
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
+ @Column(name = "PID")
+ Long myId;
+ @Column(name = "PARTITION_ID", nullable = true, insertable = true, updatable = false)
+ Integer myPartitionId;
+
+ @Column(name = "STRING_COL")
+ String myString;
+
+ @Column(name = "RES_ID", nullable = false, insertable = false, updatable = false)
+ Long myResId;
+
+ @ManyToOne(
+ optional = false)
+ @JoinColumn(
+ name = "RES_ID",
+ referencedColumnName = "RES_ID",
+ nullable = false,
+ updatable = false)
+ ResRootEntity myResource;
+
+ @Override
+ public Long getResId() {
+ return myResId;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ @Override
+ public Long getPid() {
+ return myId;
+ }
+
+ @Override
+ public void setString(String theString) {
+ myString = theString;
+ }
+
+ @Override
+ public void setParent(ResRootEntity theRoot) {
+ myResource = theRoot;
+ }
+
+ @Override
+ public String getString() {
+ return myString;
+ }
+
+ @Override
+ public void setPartitionId(Integer thePartitionId) {
+ myPartitionId = thePartitionId;
+ }
+
+ @Override
+ public Integer getPartitionId() {
+ return myPartitionId;
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/ResRootEntity.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/ResRootEntity.java
new file mode 100644
index 00000000000..5883f43f64c
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/ResRootEntity.java
@@ -0,0 +1,71 @@
+package ca.uhn.fhir.jpa.model.pkspike.primitive;
+
+import ca.uhn.fhir.jpa.model.pkspike.IRootEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+@SuppressWarnings("JpaDataSourceORMInspection")
+@Entity
+@Table(name = "RES_ROOT")
+public class ResRootEntity implements IRootEntity {
+ @Id
+ @GeneratedValue()
+ @Column(name = "RES_ID")
+ Long myId;
+
+ @Column(name = "PARTITION_ID", nullable = true, insertable = true, updatable = false)
+ Integer myPartitionId;
+
+ @Column(name = "STRING_COL")
+ String myString;
+
+ @OneToMany(mappedBy = "myResource", fetch = FetchType.EAGER)
+ Collection myJoinEntities = new ArrayList<>();
+
+ public Long getId() {
+ return myId;
+ }
+
+ @Override
+ public Long getResId() {
+ return myId;
+ }
+
+ @Override
+ public void setPartitionId(Integer thePartitionId) {
+ myPartitionId = thePartitionId;
+ }
+
+ @Override
+ public Integer getPartitionId() {
+ return myPartitionId;
+ }
+
+ public String getString() {
+ return myString;
+ }
+
+ public void setString(String theString) {
+ myString = theString;
+ }
+
+ @Override
+ public Collection getJoins() {
+ return myJoinEntities;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/SimplePkJpaBindingTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/SimplePkJpaBindingTest.java
new file mode 100644
index 00000000000..3eb47bf7628
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/SimplePkJpaBindingTest.java
@@ -0,0 +1,31 @@
+package ca.uhn.fhir.jpa.model.pkspike.primitive;
+
+import ca.uhn.fhir.jpa.config.r4.FhirContextR4Config;
+import ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestTemplate;
+import ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestFixture;
+import ca.uhn.fhir.jpa.model.pkspike.PKSpikeDefaultJPAConfig;
+import ca.uhn.fhir.jpa.model.pkspike.ValueTypeBasedParameterResolver;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * The simple scenario - single column keys and joins.
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {
+ SimpleTypesConfig.class, PKSpikeDefaultJPAConfig.class, FhirContextR4Config.class
+})
+public class SimplePkJpaBindingTest extends BasicEntityTestTemplate {
+
+ public static final BasicEntityTestFixture ourFixture = BasicEntityTestFixture.build(ResRootEntity.class, ResJoinEntity.class);
+ @RegisterExtension
+ static final ParameterResolver ourFixtureResolver = ValueTypeBasedParameterResolver.build(ourFixture);
+
+ public SimplePkJpaBindingTest() {
+ super(ourFixture);
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/SimpleTypesConfig.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/SimpleTypesConfig.java
new file mode 100644
index 00000000000..3fc13c708b2
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/model/pkspike/primitive/SimpleTypesConfig.java
@@ -0,0 +1,17 @@
+package ca.uhn.fhir.jpa.model.pkspike.primitive;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
+
+@Configuration
+public class SimpleTypesConfig {
+ @Bean
+ PersistenceManagedTypes getManagedTypes() {
+ return PersistenceManagedTypes.of(
+ ResRootEntity.class.getName(),
+ ResJoinEntity.class.getName()
+ );
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/ca/uhn/fhir/jpa/model/pkspike/idclass/ormComposite.xml b/hapi-fhir-jpaserver-test-r4/src/test/resources/ca/uhn/fhir/jpa/model/pkspike/idclass/ormComposite.xml
new file mode 100644
index 00000000000..9e34c54f5e0
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/ca/uhn/fhir/jpa/model/pkspike/idclass/ormComposite.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/ca/uhn/fhir/jpa/model/pkspike/idclass/ormLong.xml b/hapi-fhir-jpaserver-test-r4/src/test/resources/ca/uhn/fhir/jpa/model/pkspike/idclass/ormLong.xml
new file mode 100644
index 00000000000..cb11bbb269e
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/ca/uhn/fhir/jpa/model/pkspike/idclass/ormLong.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/ca/uhn/fhir/jpa/model/pkspike/idclass/persistence.xml b/hapi-fhir-jpaserver-test-r4/src/test/resources/ca/uhn/fhir/jpa/model/pkspike/idclass/persistence.xml
new file mode 100644
index 00000000000..b3b19041c32
--- /dev/null
+++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/ca/uhn/fhir/jpa/model/pkspike/idclass/persistence.xml
@@ -0,0 +1,24 @@
+
+
+ ./overrideMappings.xml
+
+
+
+
+
+
diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/LoggingExtension.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/LoggingExtension.java
index de0630eeb66..7667b0c9808 100644
--- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/LoggingExtension.java
+++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/LoggingExtension.java
@@ -35,14 +35,15 @@
package ca.uhn.fhir.test.utilities;
+import jakarta.annotation.Nonnull;
import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.text.MessageFormat;
-
/**
* This JUnit rule generates log messages to delineate the start and finish of a JUnit test case and also to note any exceptions
* that are thrown.
@@ -50,17 +51,34 @@ import java.text.MessageFormat;
* @author Brian Matthews
* @version 1.0.0
*/
-public class LoggingExtension implements BeforeEachCallback, AfterEachCallback {
-
- @Override
- public void afterEach(ExtensionContext context) {
- final Logger logger = LoggerFactory.getLogger(context.getTestClass().get());
- logger.info(MessageFormat.format("Finished test case [{0}]", context.getTestMethod().get().getName()));
- }
+public class LoggingExtension implements BeforeEachCallback, BeforeTestExecutionCallback, AfterEachCallback, AfterTestExecutionCallback {
@Override
public void beforeEach(ExtensionContext context) {
- final Logger logger = LoggerFactory.getLogger(context.getTestClass().get());
- logger.info(MessageFormat.format("Starting test case [{0}]", context.getTestMethod().get().getName()));
+ getLoggerForTestClass(context).info("Starting setup for test case [{}]", getMethodName(context));
}
+
+ @Override
+ public void beforeTestExecution(ExtensionContext context) {
+ getLoggerForTestClass(context).info("Starting test case [{}]", getMethodName(context));
+
+ }
+ @Override
+ public void afterTestExecution(ExtensionContext context) {
+ getLoggerForTestClass(context).info("Finished test case [{}]", getMethodName(context));
+ }
+
+ @Override
+ public void afterEach(ExtensionContext context) {
+ getLoggerForTestClass(context).info("Finished teardown for test case [{}]", getMethodName(context));
+ }
+
+ private static Logger getLoggerForTestClass(ExtensionContext context) {
+ return LoggerFactory.getLogger(context.getTestClass().orElseThrow());
+ }
+
+ private static @Nonnull String getMethodName(ExtensionContext context) {
+ return context.getTestMethod().orElseThrow().getName();
+ }
+
}