ENT-5264 Synchronise schema on the command line (#6353)

* Decouple DatabaseConfig and CordaPersistence etc.
* Add schema sync to schema migration + test
* Add command line parameters for synchronising schema
This commit is contained in:
Christian Sailer
2020-06-18 11:38:46 +01:00
committed by GitHub
parent 836dd559e8
commit 4091fdc8b1
12 changed files with 198 additions and 18 deletions

View File

@ -88,7 +88,7 @@ fun <T> withoutDatabaseAccess(block: () -> T): T {
val contextDatabaseOrNull: CordaPersistence? get() = _contextDatabase.get()
class CordaPersistence(
databaseConfig: DatabaseConfig,
exportHibernateJMXStatistics: Boolean,
schemas: Set<MappedSchema>,
val jdbcUrl: String,
cacheFactory: NamedCacheFactory,
@ -106,7 +106,7 @@ class CordaPersistence(
val hibernateConfig: HibernateConfiguration by lazy {
transaction {
try {
HibernateConfiguration(schemas, databaseConfig, attributeConverters, jdbcUrl, cacheFactory, customClassLoader, allowHibernateToManageAppSchema)
HibernateConfiguration(schemas, exportHibernateJMXStatistics, attributeConverters, jdbcUrl, cacheFactory, customClassLoader, allowHibernateToManageAppSchema)
} catch (e: Exception) {
when (e) {
is SchemaManagementException -> throw HibernateSchemaChangeException("Incompatible schema change detected. Please run schema migration scripts (node with sub-command run-migration-scripts). Reason: ${e.message}", e)

View File

@ -19,7 +19,7 @@ import javax.persistence.AttributeConverter
class HibernateConfiguration(
schemas: Set<MappedSchema>,
private val databaseConfig: DatabaseConfig,
private val exportHibernateJMXStatistics: Boolean,
private val attributeConverters: Collection<AttributeConverter<*, *>>,
jdbcUrl: String,
cacheFactory: NamedCacheFactory,
@ -65,10 +65,10 @@ class HibernateConfiguration(
fun sessionFactoryForSchemas(key: Set<MappedSchema>): SessionFactory = sessionFactories.get(key, ::makeSessionFactoryForSchemas)!!
private fun makeSessionFactoryForSchemas(schemas: Set<MappedSchema>): SessionFactory {
val sessionFactory = sessionFactoryFactory.makeSessionFactoryForSchemas(databaseConfig, schemas, customClassLoader, attributeConverters, allowHibernateToManageAppSchema)
val sessionFactory = sessionFactoryFactory.makeSessionFactoryForSchemas(schemas, customClassLoader, attributeConverters, allowHibernateToManageAppSchema)
// export Hibernate JMX statistics
if (databaseConfig.exportHibernateJMXStatistics)
if (exportHibernateJMXStatistics)
initStatistics(sessionFactory)
return sessionFactory

View File

@ -93,6 +93,32 @@ open class SchemaMigration(
}
}
/**
* Synchronises the changelog table with the schema descriptions passed in without applying any of the changes to the database.
* This can be used when migrating a CorDapp that had its schema generated by hibernate to liquibase schema migration, or when
* updating from a version of Corda that does not use liquibase for CorDapps
* **Warning** - this will not check if the matching schema changes have been applied, it will just generate the changelog
* It must not be run on a newly installed CorDapp.
* @param schemas The set of schemas to add to the changelog
* @param forceThrowOnMissingMigration throw an exception if a mapped schema is missing its migration resource
*/
fun synchroniseSchemas(schemas: Set<MappedSchema>, forceThrowOnMissingMigration: Boolean) {
val resourcesAndSourceInfo = prepareResources(schemas, forceThrowOnMissingMigration)
// current version of Liquibase appears to be non-threadsafe
// this is apparent when multiple in-process nodes are all running migrations simultaneously
mutex.withLock {
dataSource.connection.use { connection ->
val (runner, _, _) = prepareRunner(connection, resourcesAndSourceInfo)
try {
runner.changeLogSync(Contexts().toString())
} catch (exp: LiquibaseException) {
throw DatabaseMigrationException(exp.message, exp)
}
}
}
}
/** Create a resource accessor that aggregates the changelogs included in the schemas into one dynamic stream. */
protected class CustomResourceAccessor(val dynamicInclude: String, val changelogList: List<String?>, classLoader: ClassLoader) :
ClassLoaderResourceAccessor(classLoader) {

View File

@ -3,7 +3,6 @@ package net.corda.nodeapi.internal.persistence.factory
import net.corda.core.schemas.MappedSchema
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.toHexString
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
import org.hibernate.SessionFactory
@ -26,7 +25,7 @@ abstract class BaseSessionFactoryFactory : CordaSessionFactoryFactory {
private val logger = contextLogger()
}
open fun buildHibernateConfig(databaseConfig: DatabaseConfig, metadataSources: MetadataSources, allowHibernateToManageAppSchema: Boolean): Configuration {
open fun buildHibernateConfig(metadataSources: MetadataSources, allowHibernateToManageAppSchema: Boolean): Configuration {
val hbm2dll: String =
if (allowHibernateToManageAppSchema) {
"update"
@ -82,7 +81,6 @@ abstract class BaseSessionFactoryFactory : CordaSessionFactoryFactory {
}
final override fun makeSessionFactoryForSchemas(
databaseConfig: DatabaseConfig,
schemas: Set<MappedSchema>,
customClassLoader: ClassLoader?,
attributeConverters: Collection<AttributeConverter<*, *>>,
@ -91,7 +89,7 @@ abstract class BaseSessionFactoryFactory : CordaSessionFactoryFactory {
val serviceRegistry = BootstrapServiceRegistryBuilder().build()
val metadataSources = MetadataSources(serviceRegistry)
val config = buildHibernateConfig(databaseConfig, metadataSources, allowHibernateToMananageAppSchema)
val config = buildHibernateConfig(metadataSources, allowHibernateToMananageAppSchema)
schemas.forEach { schema ->
schema.mappedTypes.forEach { config.addAnnotatedClass(it) }
}

View File

@ -1,7 +1,6 @@
package net.corda.nodeapi.internal.persistence.factory
import net.corda.core.schemas.MappedSchema
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import org.hibernate.SessionFactory
import org.hibernate.boot.Metadata
import org.hibernate.boot.MetadataBuilder
@ -11,7 +10,6 @@ interface CordaSessionFactoryFactory {
val databaseType: String
fun canHandleDatabase(jdbcUrl: String): Boolean
fun makeSessionFactoryForSchemas(
databaseConfig: DatabaseConfig,
schemas: Set<MappedSchema>,
customClassLoader: ClassLoader?,
attributeConverters: Collection<AttributeConverter<*, *>>,

View File

@ -14,7 +14,7 @@ class HibernateConfigurationFactoryLoadingTest {
val cacheFactory = mock<NamedCacheFactory>()
HibernateConfiguration(
emptySet(),
DatabaseConfig(),
false,
emptyList(),
jdbcUrl,
cacheFactory)