mirror of
https://github.com/corda/corda.git
synced 2025-06-17 14:48:16 +00:00
Expose a JDBC connection (session) via the ServiceHub for generic JDB… (#1000)
* Expose a JDBC connection (session) via the ServiceHub for generic JDBC usage. * Updated documentation. * Fix failing JUnit following rebase from master. * JDBC session Tutorial WIP * Fix broken JUnits following update to consumeCash() using observable event update as return. * Fixed broken JUnits. * Refactoring createSession() into new CordaPersistence class following rebase from master. * Updated fully working example. * Minor updates following feedback from MH. * Fixed compiler error following rebase. * Fixes following rebase from master. * Further updates and clarifications to documentation.
This commit is contained in:
@ -62,6 +62,8 @@ import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.node.services.vault.VaultSoftLockManager
|
||||
import net.corda.node.utilities.*
|
||||
import net.corda.node.utilities.AddOrRemove.ADD
|
||||
import net.corda.node.utilities.AffinityExecutor
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.slf4j.Logger
|
||||
@ -77,6 +79,7 @@ import java.security.KeyPair
|
||||
import java.security.KeyStoreException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.sql.Connection
|
||||
import java.time.Clock
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@ -814,6 +817,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
super.recordTransactions(txs)
|
||||
}
|
||||
}
|
||||
override fun jdbcSession(): Connection = database.createSession()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,13 @@ class CordaPersistence(var dataSource: HikariDataSource, databaseProperties: Pro
|
||||
return DatabaseTransactionManager.currentOrNew(transactionIsolationLevel)
|
||||
}
|
||||
|
||||
fun createSession(): Connection {
|
||||
// We need to set the database for the current [Thread] or [Fiber] here as some tests share threads across databases.
|
||||
DatabaseTransactionManager.dataSource = this
|
||||
val ctx = DatabaseTransactionManager.currentOrNull()
|
||||
return ctx?.connection ?: throw IllegalStateException("Was expecting to find database transaction: must wrap calling code within a transaction.")
|
||||
}
|
||||
|
||||
fun <T> isolatedTransaction(block: DatabaseTransaction.() -> T): T {
|
||||
val context = DatabaseTransactionManager.setThreadLocalTx(null)
|
||||
return try {
|
||||
|
@ -49,36 +49,34 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
|
||||
public void setUp() {
|
||||
Properties dataSourceProps = makeTestDataSourceProperties(SecureHash.randomSHA256().toString());
|
||||
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties());
|
||||
|
||||
Set<MappedSchema> customSchemas = new HashSet<>(Collections.singletonList(DummyLinearStateSchemaV1.INSTANCE));
|
||||
HibernateConfiguration hibernateConfig = new HibernateConfiguration(new NodeSchemaService(customSchemas), makeTestDatabaseProperties());
|
||||
database.transaction(
|
||||
statement -> { services = new MockServices(getMEGA_CORP_KEY()) {
|
||||
database.transaction(statement -> {
|
||||
Set<MappedSchema> customSchemas = new HashSet<>(Collections.singletonList(DummyLinearStateSchemaV1.INSTANCE));
|
||||
HibernateConfiguration hibernateConfig = new HibernateConfiguration(new NodeSchemaService(customSchemas), makeTestDatabaseProperties());
|
||||
services = new MockServices(getMEGA_CORP_KEY()) {
|
||||
@NotNull
|
||||
@Override
|
||||
public VaultService getVaultService() {
|
||||
if (vaultSvc != null) return vaultSvc;
|
||||
return makeVaultService(dataSourceProps, hibernateConfig);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public VaultQueryService getVaultQueryService() {
|
||||
return new HibernateVaultQueryImpl(hibernateConfig, getVaultService().getUpdatesPublisher());
|
||||
}
|
||||
@NotNull
|
||||
@Override
|
||||
public VaultQueryService getVaultQueryService() {
|
||||
return new HibernateVaultQueryImpl(hibernateConfig, vaultSvc.getUpdatesPublisher());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordTransactions(@NotNull Iterable<SignedTransaction> txs) {
|
||||
for (SignedTransaction stx : txs) {
|
||||
getValidatedTransactions().addTransaction(stx);
|
||||
}
|
||||
|
||||
Stream<WireTransaction> wtxn = StreamSupport.stream(txs.spliterator(), false).map(SignedTransaction::getTx);
|
||||
getVaultService().notifyAll(wtxn.collect(Collectors.toList()));
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void recordTransactions(@NotNull Iterable<SignedTransaction> txs) {
|
||||
for (SignedTransaction stx : txs) {
|
||||
getValidatedTransactions().addTransaction(stx);
|
||||
}
|
||||
Stream<WireTransaction> wtxn = StreamSupport.stream(txs.spliterator(), false).map(SignedTransaction::getTx);
|
||||
vaultSvc.notifyAll(wtxn.collect(Collectors.toList()));
|
||||
}
|
||||
};
|
||||
vaultSvc = services.getVaultService();
|
||||
vaultQuerySvc = services.getVaultQueryService();
|
||||
|
||||
return services;
|
||||
});
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import net.corda.testing.node.MockAttachmentStorage
|
||||
import net.corda.testing.node.MockNetworkMapCache
|
||||
import net.corda.testing.node.MockStateMachineRecordedTransactionMappingStorage
|
||||
import net.corda.testing.node.MockTransactionStorage
|
||||
import java.sql.Connection
|
||||
import java.time.Clock
|
||||
|
||||
open class MockServiceHubInternal(
|
||||
@ -77,4 +78,6 @@ open class MockServiceHubInternal(
|
||||
}
|
||||
|
||||
override fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>? = null
|
||||
|
||||
override fun jdbcSession(): Connection = database.createSession()
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.contracts.asset.DummyFungibleContract
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.VaultService
|
||||
@ -34,6 +35,7 @@ import org.assertj.core.api.Assertions
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.hibernate.SessionFactory
|
||||
import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.time.Instant
|
||||
@ -64,17 +66,10 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
|
||||
val defaultDatabaseProperties = makeTestDatabaseProperties()
|
||||
database = configureDatabase(dataSourceProps, defaultDatabaseProperties)
|
||||
val customSchemas = setOf(VaultSchemaV1, CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3)
|
||||
|
||||
database.transaction {
|
||||
|
||||
hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas), makeTestDatabaseProperties())
|
||||
|
||||
services = object : MockServices(BOB_KEY) {
|
||||
override val vaultService: VaultService get() {
|
||||
val vaultService = NodeVaultService(this, dataSourceProps, makeTestDatabaseProperties())
|
||||
hibernatePersister = HibernateObserver(vaultService.rawUpdates, hibernateConfig)
|
||||
return vaultService
|
||||
}
|
||||
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
|
||||
|
||||
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
||||
for (stx in txs) {
|
||||
@ -83,7 +78,9 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
|
||||
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
|
||||
vaultService.notifyAll(txs.map { it.tx })
|
||||
}
|
||||
override fun jdbcSession() = database.createSession()
|
||||
}
|
||||
hibernatePersister = services.hibernatePersister
|
||||
}
|
||||
setUpDb()
|
||||
|
||||
@ -852,4 +849,26 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
|
||||
assertThat(queryResults).hasSize(6)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invoking SQL query using JDBC connection (session)
|
||||
*/
|
||||
@Test
|
||||
fun `test calling an arbitrary JDBC native query`() {
|
||||
// DOCSTART JdbcSession
|
||||
val nativeQuery = "SELECT v.transaction_id, v.output_index FROM vault_states v WHERE v.state_status = 0"
|
||||
|
||||
database.transaction {
|
||||
val jdbcSession = database.createSession()
|
||||
val prepStatement = jdbcSession.prepareStatement(nativeQuery)
|
||||
val rs = prepStatement.executeQuery()
|
||||
// DOCEND JdbcSession
|
||||
var count = 0
|
||||
while (rs.next()) {
|
||||
val stateRef = StateRef(SecureHash.parse(rs.getString(1)), rs.getInt(2))
|
||||
Assert.assertTrue(cashStates.map { it.ref }.contains(stateRef))
|
||||
count++
|
||||
}
|
||||
Assert.assertEquals(cashStates.count(), count)
|
||||
}
|
||||
}
|
||||
}
|
@ -243,8 +243,9 @@ class VaultQueryTests : TestDependencyInjectionBase() {
|
||||
stateRefs.addAll(issuedStateRefs)
|
||||
|
||||
val spentStates = services.consumeCash(25.DOLLARS)
|
||||
var spentStateRefs = spentStates.states.map { it.ref }.toList()
|
||||
stateRefs.addAll(spentStateRefs)
|
||||
var consumedStateRefs = spentStates.consumed.map { it.ref }.toList()
|
||||
var producedStateRefs = spentStates.produced.map { it.ref }.toList()
|
||||
stateRefs.addAll(consumedStateRefs.plus(producedStateRefs))
|
||||
|
||||
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
|
||||
val criteria = VaultQueryCriteria()
|
||||
|
Reference in New Issue
Block a user