Corda-3941: initialiseSchema property couples corda node and cordapp ddl handling (#4277)

New property database.initialiseAppSchema introduced that allows to turn-off Hibernate DDL generation while leaving Node-specific Liquibase handling enabled.
This commit is contained in:
Remo
2018-12-13 14:34:58 +01:00
committed by Joel Dudley
parent 0c880fb7a7
commit eb4a33e438
6 changed files with 44 additions and 5 deletions

View File

@ -7,6 +7,11 @@ release, see :doc:`upgrade-notes`.
Unreleased Unreleased
---------- ----------
* New configuration property `database.initialiseAppSchema` with values `UPDATE`, `VALIDATE` and `NONE`.
The property controls the behavior of the Hibernate DDL generation. `UPDATE` performs an update of CorDapp schemas, while
`VALID` only verifies their integrity. The property does not affect the node-specific DDL handling and
complements `database.initialiseSchema` to disable DDL handling altogether.
* ``JacksonSupport.createInMemoryMapper`` was incorrectly marked as deprecated and is no longer so. * ``JacksonSupport.createInMemoryMapper`` was incorrectly marked as deprecated and is no longer so.
* Transaction building and verification enforces new contract attachment version non-downgrade rule. * Transaction building and verification enforces new contract attachment version non-downgrade rule.

View File

@ -79,9 +79,18 @@ The available config fields are listed below.
:database: Database configuration: :database: Database configuration:
:transactionIsolationLevel: Transaction isolation level as defined by the ``TRANSACTION_`` constants in :transactionIsolationLevel: Transaction isolation level as defined by the ``TRANSACTION_`` constants in
``java.sql.Connection``, but without the ``TRANSACTION_`` prefix. Defaults to REPEATABLE_READ. ``java.sql.Connection``, but without the ``TRANSACTION_`` prefix. Defaults to ``REPEATABLE_READ``.
:exportHibernateJMXStatistics: Whether to export Hibernate JMX statistics (caution: expensive run-time overhead) :exportHibernateJMXStatistics: Whether to export Hibernate JMX statistics (caution: expensive run-time overhead)
:initialiseSchema: Boolean on whether to update database schema at startup (or create when node starts for the first time).
Defaults to ``true``. If set to ``false`` on startup, the node will validate if it's running against the compatible database schema.
:initialiseAppSchema: The property allows to override (downgrade) ``database.initialiseSchema`` for the Hibernate
DDL generation for CorDapp schemas. ``UPDATE`` performs an update of CorDapp schemas, while ``VALID`` only verifies
their integrity and ``NONE`` performs no check. By default (if the property is not specified) CorDapp schemas
creation is controlled by ``initialiseSchema``. When ``initialiseSchema`` is set to false then ``initialiseAppSchema``
may be set as ``VALID`` or ``NONE`` only.
:dataSourceProperties: This section is used to configure the jdbc connection and database driver used for the nodes persistence. :dataSourceProperties: This section is used to configure the jdbc connection and database driver used for the nodes persistence.
Currently the defaults in ``/node/src/main/resources/reference.conf`` are as shown in the first example. This is currently Currently the defaults in ``/node/src/main/resources/reference.conf`` are as shown in the first example. This is currently
the only configuration that has been tested, although in the future full support for other storage layers will be validated. the only configuration that has been tested, although in the future full support for other storage layers will be validated.

View File

@ -20,15 +20,23 @@ import javax.sql.DataSource
*/ */
const val NODE_DATABASE_PREFIX = "node_" const val NODE_DATABASE_PREFIX = "node_"
enum class SchemaInitializationType{
NONE,
VALIDATE,
UPDATE
}
// This class forms part of the node config and so any changes to it must be handled with care // This class forms part of the node config and so any changes to it must be handled with care
data class DatabaseConfig( data class DatabaseConfig(
val initialiseSchema: Boolean = Defaults.initialiseSchema, val initialiseSchema: Boolean = Defaults.initialiseSchema,
val initialiseAppSchema: SchemaInitializationType = Defaults.initialiseAppSchema,
val transactionIsolationLevel: TransactionIsolationLevel = Defaults.transactionIsolationLevel, val transactionIsolationLevel: TransactionIsolationLevel = Defaults.transactionIsolationLevel,
val exportHibernateJMXStatistics: Boolean = Defaults.exportHibernateJMXStatistics, val exportHibernateJMXStatistics: Boolean = Defaults.exportHibernateJMXStatistics,
val mappedSchemaCacheSize: Long = Defaults.mappedSchemaCacheSize val mappedSchemaCacheSize: Long = Defaults.mappedSchemaCacheSize
) { ) {
object Defaults { object Defaults {
val initialiseSchema = true val initialiseSchema = true
val initialiseAppSchema = SchemaInitializationType.UPDATE
val transactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ val transactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ
val exportHibernateJMXStatistics = false val exportHibernateJMXStatistics = false
val mappedSchemaCacheSize = 100L val mappedSchemaCacheSize = 100L

View File

@ -73,12 +73,22 @@ class HibernateConfiguration(
logger.info("Creating session factory for schemas: $schemas") logger.info("Creating session factory for schemas: $schemas")
val serviceRegistry = BootstrapServiceRegistryBuilder().build() val serviceRegistry = BootstrapServiceRegistryBuilder().build()
val metadataSources = MetadataSources(serviceRegistry) val metadataSources = MetadataSources(serviceRegistry)
val hbm2dll: String =
if(databaseConfig.initialiseSchema && databaseConfig.initialiseAppSchema == SchemaInitializationType.UPDATE) {
"update"
} else if((!databaseConfig.initialiseSchema && databaseConfig.initialiseAppSchema == SchemaInitializationType.UPDATE)
|| databaseConfig.initialiseAppSchema == SchemaInitializationType.VALIDATE) {
"validate"
} else {
"none"
}
// We set a connection provider as the auto schema generation requires it. The auto schema generation will not // We set a connection provider as the auto schema generation requires it. The auto schema generation will not
// necessarily remain and would likely be replaced by something like Liquibase. For now it is very convenient though. // necessarily remain and would likely be replaced by something like Liquibase. For now it is very convenient though.
// TODO: replace auto schema generation as it isn't intended for production use, according to Hibernate docs.
val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", NodeDatabaseConnectionProvider::class.java.name) val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", NodeDatabaseConnectionProvider::class.java.name)
.setProperty("hibernate.hbm2ddl.auto", if (databaseConfig.initialiseSchema) "update" else "validate")
.setProperty("hibernate.format_sql", "true") .setProperty("hibernate.format_sql", "true")
.setProperty("hibernate.hbm2ddl.auto", hbm2dll)
.setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString()) .setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString())
schemas.forEach { schema -> schemas.forEach { schema ->

View File

@ -14,6 +14,7 @@ import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.config.SslConfiguration import net.corda.nodeapi.internal.config.SslConfiguration
import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.SchemaInitializationType
import net.corda.tools.shell.SSHDConfiguration import net.corda.tools.shell.SSHDConfiguration
import java.net.URL import java.net.URL
import java.nio.file.Path import java.nio.file.Path
@ -112,7 +113,11 @@ data class NodeConfigurationImpl(
fun messagingServerExternal(messagingServerAddress: NetworkHostAndPort?) = messagingServerAddress != null fun messagingServerExternal(messagingServerAddress: NetworkHostAndPort?) = messagingServerAddress != null
fun database(devMode: Boolean) = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode) fun database(devMode: Boolean) = DatabaseConfig(
initialiseSchema = devMode,
initialiseAppSchema = if(devMode) SchemaInitializationType.UPDATE else SchemaInitializationType.VALIDATE,
exportHibernateJMXStatistics = devMode
)
} }
companion object { companion object {

View File

@ -42,6 +42,7 @@ import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
import net.corda.nodeapi.internal.persistence.SchemaInitializationType
import net.corda.tools.shell.SSHDConfiguration import net.corda.tools.shell.SSHDConfiguration
internal object UserSpec : Configuration.Specification<User>("User") { internal object UserSpec : Configuration.Specification<User>("User") {
@ -203,12 +204,13 @@ internal object SSHDConfigurationSpec : Configuration.Specification<SSHDConfigur
internal object DatabaseConfigSpec : Configuration.Specification<DatabaseConfig>("DatabaseConfig") { internal object DatabaseConfigSpec : Configuration.Specification<DatabaseConfig>("DatabaseConfig") {
private val initialiseSchema by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.initialiseSchema) private val initialiseSchema by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.initialiseSchema)
private val initialiseAppSchema by enum(SchemaInitializationType::class).optional().withDefaultValue(DatabaseConfig.Defaults.initialiseAppSchema)
private val transactionIsolationLevel by enum(TransactionIsolationLevel::class).optional().withDefaultValue(DatabaseConfig.Defaults.transactionIsolationLevel) private val transactionIsolationLevel by enum(TransactionIsolationLevel::class).optional().withDefaultValue(DatabaseConfig.Defaults.transactionIsolationLevel)
private val exportHibernateJMXStatistics by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.exportHibernateJMXStatistics) private val exportHibernateJMXStatistics by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.exportHibernateJMXStatistics)
private val mappedSchemaCacheSize by long().optional().withDefaultValue(DatabaseConfig.Defaults.mappedSchemaCacheSize) private val mappedSchemaCacheSize by long().optional().withDefaultValue(DatabaseConfig.Defaults.mappedSchemaCacheSize)
override fun parseValid(configuration: Config): Valid<DatabaseConfig> { override fun parseValid(configuration: Config): Valid<DatabaseConfig> {
return valid(DatabaseConfig(configuration[initialiseSchema], configuration[transactionIsolationLevel], configuration[exportHibernateJMXStatistics], configuration[mappedSchemaCacheSize])) return valid(DatabaseConfig(configuration[initialiseSchema], configuration[initialiseAppSchema], configuration[transactionIsolationLevel], configuration[exportHibernateJMXStatistics], configuration[mappedSchemaCacheSize]))
} }
} }