From 5e7d2f00ae85f01b2488c562958bc48ac0736c8a Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Mon, 15 Jan 2018 15:19:32 +0000 Subject: [PATCH] ENT-1389 Modify the HibernateObserver to persist states by schema (and only create a session per schema, not one per state per schema) (#2366) --- .../node/services/schema/HibernateObserver.kt | 41 ++++++++----- .../persistence/HibernateConfigurationTest.kt | 58 +++++++++++-------- 2 files changed, 60 insertions(+), 39 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt index 17fab1f4ac..eb58d0871e 100644 --- a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt @@ -10,11 +10,18 @@ import net.corda.core.schemas.PersistentStateRef import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug import net.corda.node.services.api.SchemaService -import net.corda.nodeapi.internal.persistence.HibernateConfiguration import net.corda.nodeapi.internal.persistence.DatabaseTransactionManager +import net.corda.nodeapi.internal.persistence.HibernateConfiguration import org.hibernate.FlushMode import rx.Observable + +/** + * Small data class bundling together a ContractState and a StateRef (as opposed to a TransactionState and StateRef + * in StateAndRef) + */ +data class ContractStateAndRef(val state: ContractState, val ref: StateRef) + /** * A vault observer that extracts Object Relational Mappings for contract states that support it, and persists them with Hibernate. */ @@ -30,27 +37,33 @@ class HibernateObserver private constructor(private val config: HibernateConfigu } private fun persist(produced: Set>) { - produced.forEach { persistState(it) } - } - - private fun persistState(stateAndRef: StateAndRef) { - val state = stateAndRef.state.data - log.debug { "Asked to persist state ${stateAndRef.ref}" } - schemaService.selectSchemas(state).forEach { persistStateWithSchema(state, stateAndRef.ref, it) } + val stateBySchema: MutableMap> = mutableMapOf() + // map all states by their referenced schemas + produced.forEach { + val contractStateAndRef = ContractStateAndRef(it.state.data, it.ref) + log.debug { "Asked to persist state ${it.ref}" } + schemaService.selectSchemas(contractStateAndRef.state).forEach { + stateBySchema.getOrPut(it) { mutableListOf() }.add(contractStateAndRef) + } + } + // then persist all states for each schema + stateBySchema.forEach { persistStatesWithSchema(it.value, it.key) } } @VisibleForTesting - internal fun persistStateWithSchema(state: ContractState, stateRef: StateRef, schema: MappedSchema) { + internal fun persistStatesWithSchema(statesAndRefs: List, schema: MappedSchema) { val sessionFactory = config.sessionFactoryForSchemas(setOf(schema)) val session = sessionFactory.withOptions(). connection(DatabaseTransactionManager.current().connection). flushMode(FlushMode.MANUAL). openSession() - session.use { - val mappedObject = schemaService.generateMappedObject(state, schema) - mappedObject.stateRef = PersistentStateRef(stateRef) - it.persist(mappedObject) - it.flush() + session.use { thisSession -> + statesAndRefs.forEach { + val mappedObject = schemaService.generateMappedObject(it.state, schema) + mappedObject.stateRef = PersistentStateRef(it.ref) + thisSession.persist(mappedObject) + } + thisSession.flush() } } } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index 6b56a5d291..4b6c3ab0bd 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -28,10 +28,11 @@ import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.SampleCashSchemaV2 import net.corda.finance.schemas.SampleCashSchemaV3 import net.corda.finance.utils.sumCash +import net.corda.node.internal.configureDatabase +import net.corda.node.services.schema.ContractStateAndRef import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.vault.VaultSchemaV1 -import net.corda.node.internal.configureDatabase import net.corda.node.services.api.IdentityServiceInternal import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig @@ -503,11 +504,12 @@ class HibernateConfigurationTest { fun `count CashStates in V2`() { database.transaction { // persist cash states explicitly with V2 schema - cashStates.forEach { + val stateAndRefs = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV2) + ContractStateAndRef(dummyFungibleState, it.ref) } + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV2) } // structure query @@ -525,11 +527,12 @@ class HibernateConfigurationTest { database.transaction { vaultFiller.fillWithSomeTestLinearStates(5) // persist cash states explicitly with V2 schema - cashStates.forEach { + val stateAndRefs = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV2) + ContractStateAndRef(dummyFungibleState, it.ref) } + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV2) } // structure query @@ -620,11 +623,12 @@ class HibernateConfigurationTest { fun `select fungible states by owner party`() { database.transaction { // persist original cash states explicitly with V3 schema - cashStates.forEach { + val stateAndRefs = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) + ContractStateAndRef(dummyFungibleState, it.ref) } + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV3) } // structure query @@ -643,19 +647,20 @@ class HibernateConfigurationTest { fun `query fungible states by owner party`() { database.transaction { // persist original cash states explicitly with V3 schema - cashStates.forEach { + val stateAndRefs: MutableList = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) - } + ContractStateAndRef(dummyFungibleState, it.ref) + }.toMutableList() vaultFiller.fillWithSomeTestCash(100.DOLLARS, issuerServices, 2, issuer.ref(1), ALICE, Random(0L)) val cashStates = vaultFiller.fillWithSomeTestCash(100.DOLLARS, services, 2, identity.ref(0)).states // persist additional cash states explicitly with V3 schema - cashStates.forEach { + stateAndRefs.addAll(cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) - } + ContractStateAndRef(dummyFungibleState, it.ref) + }) + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV3) } val sessionFactory = sessionFactoryForSchemas(VaultSchemaV1, CommonSchemaV1, SampleCashSchemaV3) val criteriaBuilder = sessionFactory.criteriaBuilder @@ -694,12 +699,13 @@ class HibernateConfigurationTest { @Test fun `select fungible states by participants`() { database.transaction { - // persist cash states explicitly with V2 schema - cashStates.forEach { + // persist cash states explicitly with V3 schema + val stateAndRefs = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) + ContractStateAndRef(dummyFungibleState, it.ref) } + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV3) } // structure query @@ -720,25 +726,27 @@ class HibernateConfigurationTest { val firstCashState = database.transaction { // persist original cash states explicitly with V3 schema - cashStates.forEach { + val stateAndRefs: MutableList = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) - } + ContractStateAndRef(dummyFungibleState, it.ref) + }.toMutableList() + val moreCash = vaultFiller.fillWithSomeTestCash(100.DOLLARS, services, 2, identity.ref(0), identity, Random(0L)).states // persist additional cash states explicitly with V3 schema - moreCash.forEach { + stateAndRefs.addAll(moreCash.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) - } + ContractStateAndRef(dummyFungibleState, it.ref) + }) val cashStates = vaultFiller.fillWithSomeTestCash(100.DOLLARS, issuerServices, 2, issuer.ref(1), ALICE, Random(0L)).states // persist additional cash states explicitly with V3 schema - cashStates.forEach { + stateAndRefs.addAll(cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) - } + ContractStateAndRef(dummyFungibleState, it.ref) + }) + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV3) cashStates.first() }