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:
josecoll
2017-08-04 09:26:27 +01:00
committed by GitHub
parent 818cbce789
commit 014387162d
12 changed files with 368 additions and 38 deletions

View File

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

View File

@ -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 {

View File

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

View File

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

View File

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

View File

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