This commit is contained in:
Michael Buckley 2024-09-26 02:08:10 +00:00 committed by GitHub
commit 0dbf828567
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 1729 additions and 11 deletions

View File

@ -22,6 +22,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpa</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
@ -49,6 +55,12 @@
<artifactId>jakarta.servlet-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-testing</artifactId>
<version>6.5.2.Final</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,48 @@
package ca.uhn.fhir.jpa.model.pkspike;
import jakarta.annotation.Nonnull;
public class BasicEntityTestFixture<R extends IRootEntity<J>,J extends IJoinEntity<R>> {
/** does this scenario support null partition_id? */
boolean myNullPartitionSupportFlag = true;
BasicEntityTestFixture(Class<R> theRootType, Class<J> theJoinType) {
myRootType = theRootType;
myJoinType = theJoinType;
}
public final Class<R> myRootType;
public final Class<J> myJoinType;
public R buildRootEntity() {
return buildInstance(myRootType);
}
public J buildJoinEntity() {
return buildInstance(myJoinType);
}
public boolean isSupportNullPartitionId() {
return myNullPartitionSupportFlag;
}
public static <R extends IRootEntity<J>,J extends IJoinEntity<R>> BasicEntityTestFixture<R,J> build(Class<R> theRootType, Class<J> theJoinType) {
return new BasicEntityTestFixture<>(theRootType, theJoinType);
}
public static <R extends IRootEntity<J>,J extends IJoinEntity<R>> BasicEntityTestFixture<R,J> buildNoNullPartition(Class<R> theRootType, Class<J> theJoinType) {
BasicEntityTestFixture<R, J> entityFixture = new BasicEntityTestFixture<>(theRootType, theJoinType);
entityFixture.myNullPartitionSupportFlag = false;
return entityFixture;
}
static <T> @Nonnull T buildInstance(Class<T> theClass) {
try {
return theClass.getDeclaredConstructor().newInstance();
} catch (Exception theE) {
throw new RuntimeException(theE);
}
}
}

View File

@ -0,0 +1,238 @@
package ca.uhn.fhir.jpa.model.pkspike;
import jakarta.annotation.Nonnull;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
abstract public class BasicEntityTestTemplate<R extends IRootEntity<J>,J extends IJoinEntity<R>> {
private static final Logger ourLog = LoggerFactory.getLogger(BasicEntityTestTemplate.class);
@Autowired
EntityManagerFactory myEntityManagerFactory;
@Autowired
TransactionTemplate myTransactionTemplate;
@Autowired
JdbcTemplate myJdbcTemplate;
@RegisterExtension
SchemaCleanerExtension mySchemaCleanerExtension = new SchemaCleanerExtension();
final BasicEntityTestFixture<R,J> myFixture;
public BasicEntityTestTemplate(BasicEntityTestFixture<R,J> theFixture) {
myFixture = theFixture;
}
static List<Integer> getPartitions(BasicEntityTestFixture<?,?> theFixture) {
var result = new ArrayList<Integer>();
if (theFixture.isSupportNullPartitionId()) {
result.add(null);
}
result.add(12);
return result;
}
@Test
void rootEntityBoundToTable() {
// given
myJdbcTemplate.execute("insert into res_root(res_id, partition_id, string_col) values (-1, -1, 'hello!')");
doInTx(em->{
long count = queryCountAll(em, myFixture.myRootType);
assertEquals(1, count);
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<R> cr = cb.createQuery(myFixture.myRootType);
cr.select(cr.from(myFixture.myRootType));
R readback = em.createQuery(cr).getSingleResult();
assertEquals(-1, readback.getResId());
assertEquals(-1, readback.getPartitionId());
assertEquals("hello!", readback.getString());
});
}
@ParameterizedTest
@MethodSource("getPartitions")
void roundTripResourceTable(Integer thePartitionId) {
doInTx(em->{
R root = myFixture.buildRootEntity();
root.setPartitionId(thePartitionId);
root.setString("goodbye!");
em.persist(root);
em.flush();
em.clear();
Object id = myEntityManagerFactory.getPersistenceUnitUtil().getIdentifier(root);
ourLog.info("flushed root entity. Id is {}", id);
R readback = em.find(myFixture.myRootType, id);
assertNotNull(readback);
assertEquals(root.getResId(), readback.getResId());
assertNotNull(readback.getResId());
assertEquals(thePartitionId, readback.getPartitionId());
assertEquals("goodbye!", readback.getString());
});
}
@ParameterizedTest
@MethodSource("getPartitions")
void updateResourceTable(Integer thePartitionId) {
doInTx(em->{
R root = myFixture.buildRootEntity();
root.setPartitionId(thePartitionId);
root.setString("hello!");
em.persist(root);
em.flush();
em.clear();
Object id = myEntityManagerFactory.getPersistenceUnitUtil().getIdentifier(root);
ourLog.info("flushed root entity. Id is {}", id);
R readback = em.find(myFixture.myRootType, id);
readback.setString("goodbye!");
em.flush();
em.clear();
readback = em.find(myFixture.myRootType, id);
assertNotNull(readback);
assertEquals("goodbye!", readback.getString());
});
}
@ParameterizedTest
@MethodSource("getPartitions")
void roundTripJoin(Integer thePartitionId) {
doInTx(em->{
var root = myFixture.buildRootEntity();
root.setPartitionId(thePartitionId);
root.setString("parent");
var join = myFixture.buildJoinEntity();
join.setParent(root);
join.setString("child");
join.setPartitionId(thePartitionId);
em.persist(root);
em.persist(join);
em.flush();
em.clear();
Object id = myEntityManagerFactory.getPersistenceUnitUtil().getIdentifier(root);
ourLog.info("flushed root entity. Id is {}", id);
R readback = em.find(myFixture.myRootType, id);
assertNotNull(readback);
assertEquals(root.getResId(), readback.getResId());
assertNotNull(readback.getResId());
assertEquals(thePartitionId, readback.getPartitionId());
assertEquals("parent", readback.getString());
Collection<J> joins = readback.getJoins();
assertNotNull(joins);
assertEquals(1, joins.size());
J joinReadback = joins.iterator().next();
assertNotNull(joinReadback);
assertNotNull(joinReadback.getResId());
assertEquals(root.getResId(), joinReadback.getResId());
assertEquals(thePartitionId, joinReadback.getPartitionId());
assertEquals("child", joinReadback.getString());
});
}
@ParameterizedTest
@MethodSource("getPartitions")
void fetchJoinQuery(Integer thePartitionId) {
doInTx(em -> {
var root0 = myFixture.buildRootEntity();
root0.setPartitionId(thePartitionId);
root0.setString("parent");
var join0 = myFixture.buildJoinEntity();
join0.setParent(root0);
join0.setString("child");
join0.setPartitionId(thePartitionId);
var join1 = myFixture.buildJoinEntity();
join1.setParent(root0);
join1.setString("child1");
join1.setPartitionId(thePartitionId);
em.persist(root0);
em.persist(join0);
em.persist(join1);
em.flush();
em.clear();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<R> cr = cb.createQuery(myFixture.myRootType);
Root<R> from = cr.from(myFixture.myRootType);
from.fetch("myJoinEntities");
cr.select(from);
List<R> resultList = em.createQuery(cr).getResultList();
assertEquals(1,resultList.size());
resultList.forEach(e-> {
ourLog.info("root: {}", e);
assertNotNull(e);
assertNotNull(e.getJoins());
assertEquals(2, e.getJoins().size());
assertNotNull(e.getJoins().iterator().next());
e.getJoins().forEach(j-> ourLog.info("join: {}", j));
});
});
}
private void doInTx(Consumer<EntityManager> theCallback) {
myTransactionTemplate.execute(status-> {
theCallback.accept(getEntityManagerOrThrow());
return null;
});
}
private long queryCountAll(EntityManager em, Class<R> rootType) {
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
CriteriaQuery<Long> 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));
}
}

View File

@ -0,0 +1,17 @@
package ca.uhn.fhir.jpa.model.pkspike;
public interface IJoinEntity<P> {
Long getPid();
void setString(String theString);
void setParent(P theRoot);
String getString();
void setPartitionId(Integer thePartitionId);
Integer getPartitionId();
Long getResId();
}

View File

@ -0,0 +1,17 @@
package ca.uhn.fhir.jpa.model.pkspike;
import java.util.Collection;
public interface IRootEntity<J> {
Long getResId();
void setPartitionId(Integer thePartitionId);
Integer getPartitionId();
String getString();
void setString(String theString);
Collection<J> getJoins();
}

View File

@ -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<HapiFhirLocalContainerEntityManagerFactoryBean> 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;
}
}

View File

@ -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");
}
}

View File

@ -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)");
}
}

View File

@ -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<T> implements ParameterResolver {
private final T myValue;
public static <T> ValueTypeBasedParameterResolver<T> 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;
}
}

View File

@ -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<ResRootEmbeddedIdEntity, ResJoinEmbeddedIdEntity> ourFixture = BasicEntityTestFixture.buildNoNullPartition(ResRootEmbeddedIdEntity.class, ResJoinEmbeddedIdEntity.class);
@RegisterExtension
static final ParameterResolver ourResolver = ValueTypeBasedParameterResolver.build(ourFixture);
@Nested
class Common extends BasicEntityTestTemplate<ResRootEmbeddedIdEntity, ResJoinEmbeddedIdEntity> {
Common() {
super(ourFixture);
}
}
}

View File

@ -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()
);
}
}

View File

@ -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<ResRootEmbeddedIdEntity> {
@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();
}
}

View File

@ -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<ResJoinEmbeddedIdEntity> {
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<ResJoinEmbeddedIdEntity> 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<ResJoinEmbeddedIdEntity> 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);
}
}
}

View File

@ -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()
);
}
}

View File

@ -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<HapiFhirLocalContainerEntityManagerFactoryBean> entityManagerFactoryCustomizer() {
return em->{
ourLog.info("Injecting custom persistence.xml");
em.setMappingResources("/ca/uhn/fhir/jpa/model/pkspike/idclass/ormComposite.xml");
};
}
}
static final BasicEntityTestFixture<ResRootIdClassEntity, ResJoinIdClassEntity> ourFixture = BasicEntityTestFixture.buildNoNullPartition(ResRootIdClassEntity.class, ResJoinIdClassEntity.class);
@RegisterExtension
static final ParameterResolver ourFixtureResolver = ValueTypeBasedParameterResolver.build(ourFixture);
@Nested
class Common extends BasicEntityTestTemplate<ResRootIdClassEntity, ResJoinIdClassEntity> {
Common() {
super(ourFixture);
}
}
}

View File

@ -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<HapiFhirLocalContainerEntityManagerFactoryBean> entityManagerFactoryCustomizer() {
return em->{
ourLog.info("Injecting custom persistence.xml");
em.setMappingResources("/ca/uhn/fhir/jpa/model/pkspike/idclass/ormLong.xml");
};
}
}
public static final BasicEntityTestFixture<ResRootIdClassEntity, ResJoinIdClassEntity> ourFixture = BasicEntityTestFixture.build(ResRootIdClassEntity.class, ResJoinIdClassEntity.class);
@RegisterExtension
static final ParameterResolver ourFixtureResolver = ValueTypeBasedParameterResolver.build(ourFixture);
@Nested
class Common extends BasicEntityTestTemplate<ResRootIdClassEntity, ResJoinIdClassEntity> {
Common() {
super(ourFixture);
}
}
}

View File

@ -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<ResRootIdClassEntity> {
@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;
}
}
}

View File

@ -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<ResJoinIdClassEntity> {
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<ResJoinIdClassEntity> 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<ResJoinIdClassEntity> 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;
}
}
}

View File

@ -0,0 +1,29 @@
/**
* Various tests of a parent-child JPA relationship exercising configurable pk definition and joins.
*<p>
* We have a test template that does some basic queries ( {@link ca.uhn.fhir.jpa.model.pkspike.BasicEntityTestTemplate}).
* <ul>
* <li>See {@link ca.uhn.fhir.jpa.model.pkspike.primitive.SimplePkJpaBindingTest} for the normal case. Supports null partition_id.
* <li>See {@link ca.uhn.fhir.jpa.model.pkspike.embeddedid.EmbeddedIdPkJpaBindingTest} for an embedded Id class path. Does not support null partition_id.
* <li>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.
* <li>See {@link ca.uhn.fhir.jpa.model.pkspike.idclass.IdClassPkJpaBindingTest}. Supports null partition_id.
* <li>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.
* </ul>
* <p>
* Things we learned:
* <ul>
* <li>Hibernate can not fetch any entity with a composite key if any component is null.
* <li>It's a real pain to merge orm.xml with annotations when we have duplicate columns. E.g. PARTITION_ID used as a key and join fight over who should be "insert=true" vs false.
* <p>
* Further hacking Ideas:
* <ul>
* <li>Try to wrap the partition_id column with a user type (e.g. Optional?) so we can pretend it isn't null.
* </p>
* <p>
* Add this to logback to explore the sql.
* <pre>
* <logger name="org.hibernate.SQL" level="trace"/>
* <logger name="org.hibernate.orm.jdbc.bind" level="trace"/>
* </pre>
*/
package ca.uhn.fhir.jpa.model.pkspike;

View File

@ -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<ResRootPartitionEntity, ResJoinPartitionEntity> ourConfig = BasicEntityTestFixture.buildNoNullPartition(ResRootPartitionEntity.class, ResJoinPartitionEntity.class);
@RegisterExtension
static final ParameterResolver ourResolver = ValueTypeBasedParameterResolver.build(ourConfig);
@Nested
class Common extends BasicEntityTestTemplate<ResRootPartitionEntity, ResJoinPartitionEntity> {
Common() {
super(ourConfig);
}
}
}

View File

@ -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()
);
}
}

View File

@ -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<ResRootPartitionEntity> {
@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;
}
}

View File

@ -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<ResJoinPartitionEntity> {
@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<ResJoinPartitionEntity> myJoinEntities = new ArrayList<>();
public Long getId() {
return myId;
}
public String getString() {
return myString;
}
public void setString(String theString) {
myString = theString;
}
@Override
public Collection<ResJoinPartitionEntity> 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;
}
}

View File

@ -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<ResRootEntity> {
@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;
}
}

View File

@ -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<ResJoinEntity> {
@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<ResJoinEntity> 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<ResJoinEntity> getJoins() {
return myJoinEntities;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}

View File

@ -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<ResRootEntity, ResJoinEntity> {
public static final BasicEntityTestFixture<ResRootEntity, ResJoinEntity> ourFixture = BasicEntityTestFixture.build(ResRootEntity.class, ResJoinEntity.class);
@RegisterExtension
static final ParameterResolver ourFixtureResolver = ValueTypeBasedParameterResolver.build(ourFixture);
public SimplePkJpaBindingTest() {
super(ourFixture);
}
}

View File

@ -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()
);
}
}

View File

@ -0,0 +1,40 @@
<entity-mappings
xmlns="http://java.sun.com/xml/ns/persistence/orm"
version="2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_2_2.xsd"
>
<entity class="ca.uhn.fhir.jpa.model.pkspike.idclass.ResRootIdClassEntity" name="ResRootIdClassEntity">
<!-- <id-class class="ca.uhn.fhir.jpa.model.pkspike.idclass.ResRootIdClassEntity$ResRootCompositePK"/>-->
<attributes>
<!-- <id name="myId" access="FIELD">-->
<!-- <column name="RES_ID"/>-->
<!-- <generated-value strategy="SEQUENCE"/>-->
<!-- </id>-->
<!-- <id name="myPartitionId" access="FIELD">-->
<!-- <column name="PARTITION_ID" insertable="true" updatable="false"/>-->
<!-- </id>-->
</attributes>
</entity>
<entity class="ca.uhn.fhir.jpa.model.pkspike.idclass.ResJoinIdClassEntity">
<!-- <id-class class="ca.uhn.fhir.jpa.model.pkspike.idclass.ResJoinIdClassEntity$ResJoinCompositePK"/>-->
<attributes>
<!-- <id name="myId" access="FIELD">-->
<!-- <column name="PID"/>-->
<!-- <generated-value strategy="SEQUENCE"/>-->
<!-- </id>-->
<basic name="myPartitionId" access="FIELD">
<column name="PARTITION_ID" insertable="false" updatable="false"/>
</basic>
<!-- <basic name="myPartitionId" access="FIELD">-->
<!-- <column name="PARTITION_ID" insertable="false" updatable="false"/>-->
<!-- </basic>-->
<many-to-one name="myResource" access="FIELD">
<join-column name="RES_ID" referenced-column-name="RES_ID"/>
<join-column name="PARTITION_ID" referenced-column-name="PARTITION_ID"/>
</many-to-one>
</attributes>
</entity>
</entity-mappings>

View File

@ -0,0 +1,25 @@
<entity-mappings
xmlns="http://java.sun.com/xml/ns/persistence/orm"
version="2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_2_2.xsd"
>
<entity class="ca.uhn.fhir.jpa.model.pkspike.idclass.ResRootIdClassEntity" name="ResRootIdClassEntity">
<attributes>
<basic name="myPartitionId" access="FIELD">
<column name="PARTITION_ID" insertable="true" updatable="false"/>
</basic>
</attributes>
</entity>
<entity class="ca.uhn.fhir.jpa.model.pkspike.idclass.ResJoinIdClassEntity">
<attributes>
<basic name="myPartitionId" access="FIELD">
<column name="PARTITION_ID" insertable="true" updatable="false"/>
</basic>
<!-- <many-to-one name="myResource" access="FIELD">-->
<!-- <join-column name="RES_ID" referenced-column-name="RES_ID"/>-->
<!-- </many-to-one>-->
</attributes>
</entity>
</entity-mappings>

View File

@ -0,0 +1,24 @@
<persistence-unit>
<!--
name="educationPU"
transaction-type="JTA"
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.coe.jpa.StudentProfile</class>
>
<mapping-file>classpath:/ca/uhn/fhir/jpa/model/pkspike/idclass/overrideMappings.xml</mapping-file>
-->
<mapping-file>./overrideMappings.xml</mapping-file>
<properties>
<!--
<property name="hibernate.connection.driver_class"
value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url"
value="jdbc:mysql://localhost:3306/COE" />
<property name="hibernate.connection.username" value="root" />
<property name="show_sql" value="true" />
<property name="dialect" value="org.hibernate.dialect.MySQLDialect" />
-->
</properties>
</persistence-unit>

View File

@ -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 <a href="mailto:brian@btmatthews.com">Brian Matthews</a>
* @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();
}
}