NIFIREG-241 Added support for MySQL/MariaDB

- Added IT tests using testcontainers project to test against Postgres, MySql, and MariaDB
- Added documentation in admin guide and README

This closes #189.

Signed-off-by: Kevin Doran <kdoran@apache.org>
diff --git a/README.md b/README.md
index c7c3a9e..b280201 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,52 @@
 
     Logs will be available in `logs/nifi-registry-app.log`
 
+## Database Testing
+
+In order to ensure that NiFi Registry works correctly against different relational databases, the existing integration tests can be run against different databases by leveraging the [Testcontainers framework](https://www.testcontainers.org/).
+
+Spring profiles are used to control the DataSource factory that will be made available to the Spring application context. DataSource factories are provided that use the Testcontainers framework to start a Docker container for a given database and create a corresponding DataSource. If no profile is specified then an H2 DataSource will be used by default and no Docker containers are required.
+
+Assuming Docker is running on the system where the build is running, then the following commands can be run:
+
+* H2 (default)
+    
+      mvn clean install -Pcontrib-check,integration-tests
+      
+* Postgres
+
+      mvn clean install -Pcontrib-check,integration-tests -Dspring.profiles.active=postgres
+      
+* MySQL 5.6
+
+      mvn clean install -Pcontrib-check,integration-tests -Dspring.profiles.active=mysql-56
+      
+* MySQL 5.7
+
+      mvn clean install -Pcontrib-check,integration-tests -Dspring.profiles.active=mysql-57
+      
+* MySQL 8.0
+
+      mvn clean install -Pcontrib-check,integration-tests -Dspring.profiles.active=mysql-8
+      
+ When one of the Testcontainer profiles is activated, the test output should show logs that indicate a container has been started, such as the following:
+ 
+    2019-05-15 16:14:45.078  INFO 66091 --- [           main] 🐳 [mysql:5.7]                           : Creating container for image: mysql:5.7
+    2019-05-15 16:14:45.145  INFO 66091 --- [           main] o.t.utility.RegistryAuthLocator          : Credentials not found for host (index.docker.io) when using credential helper/store (docker-credential-osxkeychain)
+    2019-05-15 16:14:45.646  INFO 66091 --- [           main] 🐳 [mysql:5.7]                           : Starting container with ID: ca85c8c5a1990d2a898fad04c5897ddcdb3a9405e695cc11259f50f2ebe67c5f
+    2019-05-15 16:14:46.437  INFO 66091 --- [           main] 🐳 [mysql:5.7]                           : Container mysql:5.7 is starting: ca85c8c5a1990d2a898fad04c5897ddcdb3a9405e695cc11259f50f2ebe67c5f
+    2019-05-15 16:14:46.479  INFO 66091 --- [           main] 🐳 [mysql:5.7]                           : Waiting for database connection to become available at jdbc:mysql://localhost:33051/test?useSSL=false&allowPublicKeyRetrieval=true using query 'SELECT 1'
+
+The Flyway connection should also indicate the given database:
+
+    2019-05-15 16:15:02.114  INFO 66091 --- [           main] o.a.n.r.db.CustomFlywayConfiguration     : Determined database type is MYSQL
+    2019-05-15 16:15:02.115  INFO 66091 --- [           main] o.a.n.r.db.CustomFlywayConfiguration     : Setting migration locations to [classpath:db/migration/common, classpath:db/migration/mysql]
+    2019-05-15 16:15:02.373  INFO 66091 --- [           main] o.a.n.r.d.CustomFlywayMigrationStrategy  : First time initializing database...
+    2019-05-15 16:15:02.380  INFO 66091 --- [           main] o.f.c.internal.license.VersionPrinter    : Flyway Community Edition 5.2.1 by Boxfuse
+    2019-05-15 16:15:02.403  INFO 66091 --- [           main] o.f.c.internal.database.DatabaseFactory  : Database: jdbc:mysql://localhost:33051/test (MySQL 5.7)
+
+For a full list of the available DataSource factories, consult the `nifi-registry-test` module.
+
 ## License
 
 Except as otherwise noted this software is licensed under the
diff --git a/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/administration-guide.adoc b/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/administration-guide.adoc
index e05b0ff..2193734 100644
--- a/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-registry-core/nifi-registry-docs/src/main/asciidoc/administration-guide.adoc
@@ -1106,7 +1106,7 @@
 
 The metadata database maintains the knowledge of which buckets exist, which versioned items belong to which buckets, as well as the version history for each item.
 
-Currently, NiFi Registry supports H2 or Postgres for the relational database engine.
+Currently, NiFi Registry supports using H2, Postgres 9.x, and MySQL (5.6, 5.7, 8.0) for the relational database engine.
 
 NOTE: NiFi Registry 0.1.0 only supports H2.
 
@@ -1151,6 +1151,59 @@
   nifi.registry.db.username=nifireg
   nifi.registry.db.password=changeme
 
+== MySQL
+
+MySQL also provides the option to use an externally located database that also supports high availability.
+
+The following steps are required to use MySQL:
+
+1. Download the MySQL JDBC driver and place it somewhere accessible to NiFi Registry
+
+  /path/to/drivers/mysql-connector-java-8.0.16.jar
+
+2. Create a database inside MySQL (enter mysql shell using `mysql -u root -p`
+
+  CREATE DATABASE nifi_registry;
+
+3. Create a database user and grant privileges (for remote users, use `nifireg'@'<IP-ADDRESS>`, or `nifireg'@'%` for any remote host)
+
+  GRANT ALL PRIVILEGES ON nifi_registry.* TO 'nifireg'@'localhost' IDENTIFIED BY 'changeme';
+
+4. Configure the database properties in _nifi-registry.properties_
+
+  nifi.registry.db.url=jdbc:mysql://<MYSQL-HOSTNAME>/nifi_registry
+  nifi.registry.db.driver.class=com.mysql.cj.jdbc.Driver
+  nifi.registry.db.driver.directory=/path/to/drivers
+  nifi.registry.db.username=nifireg
+  nifi.registry.db.password=changeme
+
+== Schema Differences & Limitations
+
+Due to differences across database implementations, there are two versions of the schema for NiFi Registry's metadata database. The original version supports H2 and Postgres, and a second versions supports MySQL.
+
+MySQL has limitations on the maximum size of text columns that are part of an index, or unique key. This means the maximum length of some columns is significantly less when using MySQL vs. H2/Postgres.
+
+NOTE: If choosing to use MySQL it is important to understand these limitations and accept them.
+
+The following tables summarizes the schema differences in column lengths:
+
+|====
+|*Table.Column*|*H2/Postgres*|*MySQL*
+|BUCKET.NAME|1000|767
+|FLOW_SNAPSHOT.CREATED_BY|4096|767
+|SIGNING_KEY.TENANT_IDENTITY|4096|767
+|BUNDLE.GROUP_ID|500|200
+|BUNDLE.ARTIFACT_ID|500|200
+|BUNDLE_VERSION.CREATED_BY|4096|767
+|BUNDLE_VERSION.BUILT_BY|4096|767
+|BUNDLE_VERSION_DEPENDENCY.GROUP_ID|500|200
+|BUNDLE_VERSION_DEPENDENCY.ARTIFACT_ID|500|200
+|EXTENSION_PROVIDED_SERVICE_API.CLASS_NAME|500|200
+|EXTENSION_PROVIDED_SERVICE_API.GROUP_ID|500|200
+|EXTENSION_PROVIDED_SERVICE_API.ARTIFACT_ID|500|200
+|====
+
+
 == Persistence Providers
 
 NiFi Registry uses a pluggable persistence provider to store the content of each versioned item. Each type of versioned item, such as a versioned flow or extension bundle, has its own persistence provider.
diff --git a/nifi-registry-core/nifi-registry-framework/pom.xml b/nifi-registry-core/nifi-registry-framework/pom.xml
index d427678..75d70a2 100644
--- a/nifi-registry-core/nifi-registry-framework/pom.xml
+++ b/nifi-registry-core/nifi-registry-framework/pom.xml
@@ -328,9 +328,9 @@
         </dependency>
         <!-- Test Dependencies -->
         <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-test</artifactId>
-            <version>${spring.boot.version}</version>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-test</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -340,6 +340,14 @@
             <scope>test</scope>
             <exclusions>
                 <exclusion>
+                    <groupId>org.flywaydb</groupId>
+                    <artifactId>flyway-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-test</artifactId>
+                </exclusion>
+                <exclusion>
                     <groupId>org.springframework</groupId>
                     <artifactId>spring-context</artifactId>
                 </exclusion>
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/CustomFlywayConfiguration.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/CustomFlywayConfiguration.java
new file mode 100644
index 0000000..0cbf64f
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/CustomFlywayConfiguration.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.flywaydb.core.api.FlywayException;
+import org.flywaydb.core.api.configuration.FluentConfiguration;
+import org.flywaydb.core.internal.jdbc.DatabaseType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.flyway.FlywayConfigurationCustomizer;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+@Configuration
+public class CustomFlywayConfiguration implements FlywayConfigurationCustomizer {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CustomFlywayConfiguration.class);
+
+    private static final String LOCATION_COMMON = "classpath:db/migration/common";
+
+    private static final String LOCATION_DEFAULT = "classpath:db/migration/default";
+    private static final String[] LOCATIONS_DEFAULT = {LOCATION_COMMON, LOCATION_DEFAULT};
+
+    private static final String LOCATION_MYSQL = "classpath:db/migration/mysql";
+    private static final String[] LOCATIONS_MYSQL = {LOCATION_COMMON, LOCATION_MYSQL};
+
+    @Override
+    public void customize(final FluentConfiguration configuration) {
+        final DatabaseType databaseType = getDatabaseType(configuration.getDataSource());
+        LOGGER.info("Determined database type is {}", new Object[] {databaseType.name()});
+
+        switch (databaseType) {
+            case MYSQL:
+                LOGGER.info("Setting migration locations to {}", new Object[] {LOCATIONS_MYSQL});
+                configuration.locations(LOCATIONS_MYSQL);
+                break;
+            default:
+                LOGGER.info("Setting migration locations to {}", new Object[] {LOCATIONS_DEFAULT});
+                configuration.locations(LOCATIONS_DEFAULT);
+                break;
+        }
+    }
+
+    /**
+     * Determines the database type from the given data source.
+     *
+     * @param dataSource the data source
+     * @return the database type
+     */
+    private DatabaseType getDatabaseType(final DataSource dataSource) {
+        try (final Connection connection = dataSource.getConnection()) {
+            return DatabaseType.fromJdbcConnection(connection);
+        } catch (SQLException e) {
+            LOGGER.error(e.getMessage(), e);
+            throw new FlywayException("Unable to obtain connection from Flyway DataSource", e);
+        }
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseKeyService.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseKeyService.java
index b652308..b2daf2d 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseKeyService.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseKeyService.java
@@ -57,7 +57,7 @@
         Key key = null;
         readLock.lock();
         try {
-            final String sql = "SELECT * FROM signing_key WHERE id = ?";
+            final String sql = "SELECT * FROM SIGNING_KEY WHERE id = ?";
 
             KeyEntity keyEntity;
             try {
@@ -86,7 +86,7 @@
         Key key;
         writeLock.lock();
         try {
-            final String selectSql = "SELECT * FROM signing_key WHERE tenant_identity = ?";
+            final String selectSql = "SELECT * FROM SIGNING_KEY WHERE tenant_identity = ?";
 
             KeyEntity existingKeyEntity;
             try {
@@ -103,7 +103,7 @@
                 newKeyEntity.setTenantIdentity(tenantIdentity);
                 newKeyEntity.setKeyValue(UUID.randomUUID().toString());
 
-                final String insertSql = "INSERT INTO signing_key (ID, TENANT_IDENTITY, KEY_VALUE) VALUES (?, ?, ?)";
+                final String insertSql = "INSERT INTO SIGNING_KEY (ID, TENANT_IDENTITY, KEY_VALUE) VALUES (?, ?, ?)";
                 jdbcTemplate.update(insertSql, newKeyEntity.getId(), newKeyEntity.getTenantIdentity(), newKeyEntity.getKeyValue());
 
                 key = KeyMappings.map(newKeyEntity);
@@ -125,7 +125,7 @@
         writeLock.lock();
         try {
             logger.debug("Deleting key with identity='" + tenantIdentity + "'.");
-            final String deleteSql = "DELETE FROM signing_key WHERE tenant_identity = ?";
+            final String deleteSql = "DELETE FROM SIGNING_KEY WHERE tenant_identity = ?";
             jdbcTemplate.update(deleteSql, tenantIdentity);
         } finally {
             writeLock.unlock();
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java
index 4a3d1a2..3f75439 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java
@@ -76,7 +76,7 @@
 
     @Override
     public BucketEntity createBucket(final BucketEntity b) {
-        final String sql = "INSERT INTO bucket (ID, NAME, DESCRIPTION, CREATED, ALLOW_EXTENSION_BUNDLE_REDEPLOY) VALUES (?, ?, ?, ?, ?)";
+        final String sql = "INSERT INTO BUCKET (ID, NAME, DESCRIPTION, CREATED, ALLOW_EXTENSION_BUNDLE_REDEPLOY) VALUES (?, ?, ?, ?, ?)";
         jdbcTemplate.update(sql,
                 b.getId(),
                 b.getName(),
@@ -88,7 +88,7 @@
 
     @Override
     public BucketEntity getBucketById(final String bucketIdentifier) {
-        final String sql = "SELECT * FROM bucket WHERE id = ?";
+        final String sql = "SELECT * FROM BUCKET WHERE id = ?";
         try {
             return jdbcTemplate.queryForObject(sql, new BucketEntityRowMapper(), bucketIdentifier);
         } catch (EmptyResultDataAccessException e) {
@@ -98,13 +98,13 @@
 
     @Override
     public List<BucketEntity> getBucketsByName(final String name) {
-        final String sql = "SELECT * FROM bucket WHERE name = ? ORDER BY name ASC";
+        final String sql = "SELECT * FROM BUCKET WHERE name = ? ORDER BY name ASC";
         return jdbcTemplate.query(sql, new Object[] {name} , new BucketEntityRowMapper());
     }
 
     @Override
     public BucketEntity updateBucket(final BucketEntity bucket) {
-        final String sql = "UPDATE bucket SET name = ?, description = ?, allow_extension_bundle_redeploy = ? WHERE id = ?";
+        final String sql = "UPDATE BUCKET SET name = ?, description = ?, allow_extension_bundle_redeploy = ? WHERE id = ?";
         jdbcTemplate.update(sql, bucket.getName(), bucket.getDescription(), bucket.isAllowExtensionBundleRedeploy() ? 1 : 0, bucket.getId());
         return bucket;
     }
@@ -112,7 +112,7 @@
     @Override
     public void deleteBucket(final BucketEntity bucket) {
         // NOTE: Cascading deletes will delete from all child tables
-        final String sql = "DELETE FROM bucket WHERE id = ?";
+        final String sql = "DELETE FROM BUCKET WHERE id = ?";
         jdbcTemplate.update(sql, bucket.getId());
     }
 
@@ -122,7 +122,7 @@
             return Collections.emptyList();
         }
 
-        final StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM bucket WHERE ");
+        final StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM BUCKET WHERE ");
         addIdentifiersInClause(sqlBuilder, "id", bucketIds);
         sqlBuilder.append("ORDER BY name ASC");
 
@@ -131,7 +131,7 @@
 
     @Override
     public List<BucketEntity> getAllBuckets() {
-        final String sql = "SELECT * FROM bucket ORDER BY name ASC";
+        final String sql = "SELECT * FROM BUCKET ORDER BY name ASC";
         return jdbcTemplate.query(sql, new BucketEntityRowMapper());
     }
 
@@ -150,9 +150,9 @@
                 "eb.bundle_type as BUNDLE_TYPE, " +
                 "eb.group_id as BUNDLE_GROUP_ID, " +
                 "eb.artifact_id as BUNDLE_ARTIFACT_ID " +
-            "FROM bucket_item item " +
-            "INNER JOIN bucket b ON item.bucket_id = b.id " +
-            "LEFT JOIN bundle eb ON item.id = eb.id ";
+            "FROM BUCKET_ITEM item " +
+            "INNER JOIN BUCKET b ON item.bucket_id = b.id " +
+            "LEFT JOIN BUNDLE eb ON item.id = eb.id ";
 
     @Override
     public List<BucketItemEntity> getBucketItems(final String bucketIdentifier) {
@@ -207,7 +207,7 @@
     }
 
     private Map<String,Long> getFlowSnapshotCounts() {
-        final String sql = "SELECT flow_id, count(*) FROM flow_snapshot GROUP BY flow_id";
+        final String sql = "SELECT flow_id, count(*) FROM FLOW_SNAPSHOT GROUP BY flow_id";
 
         final Map<String,Long> results = new HashMap<>();
         jdbcTemplate.query(sql, (rs) -> {
@@ -217,7 +217,7 @@
     }
 
     private Long getFlowSnapshotCount(final String flowIdentifier) {
-        final String sql = "SELECT count(*) FROM flow_snapshot WHERE flow_id = ?";
+        final String sql = "SELECT count(*) FROM FLOW_SNAPSHOT WHERE flow_id = ?";
 
         return jdbcTemplate.queryForObject(sql, new Object[] {flowIdentifier}, (rs, num) -> {
             return rs.getLong(1);
@@ -225,7 +225,7 @@
     }
 
     private Map<String,Long> getExtensionBundleVersionCounts() {
-        final String sql = "SELECT bundle_id, count(*) FROM bundle_version GROUP BY bundle_id";
+        final String sql = "SELECT bundle_id, count(*) FROM BUNDLE_VERSION GROUP BY bundle_id";
 
         final Map<String,Long> results = new HashMap<>();
         jdbcTemplate.query(sql, (rs) -> {
@@ -235,7 +235,7 @@
     }
 
     private Long getExtensionBundleVersionCount(final String extensionBundleIdentifier) {
-        final String sql = "SELECT count(*) FROM bundle_version WHERE bundle_id = ?";
+        final String sql = "SELECT count(*) FROM BUNDLE_VERSION WHERE bundle_id = ?";
 
         return jdbcTemplate.queryForObject(sql, new Object[] {extensionBundleIdentifier}, (rs, num) -> {
             return rs.getLong(1);
@@ -246,7 +246,7 @@
 
     @Override
     public FlowEntity createFlow(final FlowEntity flow) {
-        final String itemSql = "INSERT INTO bucket_item (ID, NAME, DESCRIPTION, CREATED, MODIFIED, ITEM_TYPE, BUCKET_ID) VALUES (?, ?, ?, ?, ?, ?, ?)";
+        final String itemSql = "INSERT INTO BUCKET_ITEM (ID, NAME, DESCRIPTION, CREATED, MODIFIED, ITEM_TYPE, BUCKET_ID) VALUES (?, ?, ?, ?, ?, ?, ?)";
 
         jdbcTemplate.update(itemSql,
                 flow.getId(),
@@ -257,7 +257,7 @@
                 flow.getType().toString(),
                 flow.getBucketId());
 
-        final String flowSql = "INSERT INTO flow (ID) VALUES (?)";
+        final String flowSql = "INSERT INTO FLOW (ID) VALUES (?)";
 
         jdbcTemplate.update(flowSql, flow.getId());
 
@@ -266,7 +266,7 @@
 
     @Override
     public FlowEntity getFlowById(final String flowIdentifier) {
-        final String sql = "SELECT * FROM flow f, bucket_item item WHERE f.id = ? AND item.id = f.id";
+        final String sql = "SELECT * FROM FLOW f, BUCKET_ITEM item WHERE f.id = ? AND item.id = f.id";
         try {
             return jdbcTemplate.queryForObject(sql, new FlowEntityRowMapper(), flowIdentifier);
         } catch (EmptyResultDataAccessException e) {
@@ -291,19 +291,19 @@
 
     @Override
     public List<FlowEntity> getFlowsByName(final String name) {
-        final String sql = "SELECT * FROM flow f, bucket_item item WHERE item.name = ? AND item.id = f.id";
+        final String sql = "SELECT * FROM FLOW f, BUCKET_ITEM item WHERE item.name = ? AND item.id = f.id";
         return jdbcTemplate.query(sql, new Object[] {name}, new FlowEntityRowMapper());
     }
 
     @Override
     public List<FlowEntity> getFlowsByName(final String bucketIdentifier, final String name) {
-        final String sql = "SELECT * FROM flow f, bucket_item item WHERE item.name = ? AND item.id = f.id AND item.bucket_id = ?";
+        final String sql = "SELECT * FROM FLOW f, BUCKET_ITEM item WHERE item.name = ? AND item.id = f.id AND item.bucket_id = ?";
         return jdbcTemplate.query(sql, new Object[] {name, bucketIdentifier}, new FlowEntityRowMapper());
     }
 
     @Override
     public List<FlowEntity> getFlowsByBucket(final String bucketIdentifier) {
-        final String sql = "SELECT * FROM flow f, bucket_item item WHERE item.bucket_id = ? AND item.id = f.id";
+        final String sql = "SELECT * FROM FLOW f, BUCKET_ITEM item WHERE item.bucket_id = ? AND item.id = f.id";
         final List<FlowEntity> flows = jdbcTemplate.query(sql, new Object[] {bucketIdentifier}, new FlowEntityRowMapper());
 
         final Map<String,Long> snapshotCounts = getFlowSnapshotCounts();
@@ -321,7 +321,7 @@
     public FlowEntity updateFlow(final FlowEntity flow) {
         flow.setModified(new Date());
 
-        final String sql = "UPDATE bucket_item SET name = ?, description = ?, modified = ? WHERE id = ?";
+        final String sql = "UPDATE BUCKET_ITEM SET name = ?, description = ?, modified = ? WHERE id = ?";
         jdbcTemplate.update(sql, flow.getName(), flow.getDescription(), flow.getModified(), flow.getId());
         return flow;
     }
@@ -329,7 +329,7 @@
     @Override
     public void deleteFlow(final FlowEntity flow) {
         // NOTE: Cascading deletes will delete from child tables
-        final String itemDeleteSql = "DELETE FROM bucket_item WHERE id = ?";
+        final String itemDeleteSql = "DELETE FROM BUCKET_ITEM WHERE id = ?";
         jdbcTemplate.update(itemDeleteSql, flow.getId());
     }
 
@@ -337,7 +337,7 @@
 
     @Override
     public FlowSnapshotEntity createFlowSnapshot(final FlowSnapshotEntity flowSnapshot) {
-        final String sql = "INSERT INTO flow_snapshot (FLOW_ID, VERSION, CREATED, CREATED_BY, COMMENTS) VALUES (?, ?, ?, ?, ?)";
+        final String sql = "INSERT INTO FLOW_SNAPSHOT (FLOW_ID, VERSION, CREATED, CREATED_BY, COMMENTS) VALUES (?, ?, ?, ?, ?)";
 
         jdbcTemplate.update(sql,
                 flowSnapshot.getFlowId(),
@@ -359,9 +359,9 @@
                         "fs.created_by, " +
                         "fs.comments " +
                 "FROM " +
-                        "flow_snapshot fs, " +
-                        "flow f, " +
-                        "bucket_item item " +
+                        "FLOW_SNAPSHOT fs, " +
+                        "FLOW f, " +
+                        "BUCKET_ITEM item " +
                 "WHERE " +
                         "item.id = f.id AND " +
                         "f.id = ? AND " +
@@ -378,7 +378,7 @@
 
     @Override
     public FlowSnapshotEntity getLatestSnapshot(final String flowIdentifier) {
-        final String sql = "SELECT * FROM flow_snapshot WHERE flow_id = ? ORDER BY version DESC LIMIT 1";
+        final String sql = "SELECT * FROM FLOW_SNAPSHOT WHERE flow_id = ? ORDER BY version DESC LIMIT 1";
 
         try {
             return jdbcTemplate.queryForObject(sql, new FlowSnapshotEntityRowMapper(), flowIdentifier);
@@ -397,9 +397,9 @@
                         "fs.created_by, " +
                         "fs.comments " +
                 "FROM " +
-                        "flow_snapshot fs, " +
-                        "flow f, " +
-                        "bucket_item item " +
+                        "FLOW_SNAPSHOT fs, " +
+                        "FLOW f, " +
+                        "BUCKET_ITEM item " +
                 "WHERE " +
                         "item.id = f.id AND " +
                         "f.id = ? AND " +
@@ -411,7 +411,7 @@
 
     @Override
     public void deleteFlowSnapshot(final FlowSnapshotEntity flowSnapshot) {
-        final String sql = "DELETE FROM flow_snapshot WHERE flow_id = ? AND version = ?";
+        final String sql = "DELETE FROM FLOW_SNAPSHOT WHERE flow_id = ? AND version = ?";
         jdbcTemplate.update(sql, flowSnapshot.getFlowId(), flowSnapshot.getVersion());
     }
 
@@ -420,7 +420,7 @@
     @Override
     public BundleEntity createBundle(final BundleEntity extensionBundle) {
         final String itemSql =
-                "INSERT INTO bucket_item (" +
+                "INSERT INTO BUCKET_ITEM (" +
                     "ID, " +
                     "NAME, " +
                     "DESCRIPTION, " +
@@ -440,7 +440,7 @@
                 extensionBundle.getBucketId());
 
         final String bundleSql =
-                "INSERT INTO bundle (" +
+                "INSERT INTO BUNDLE (" +
                     "ID, " +
                     "BUCKET_ID, " +
                     "BUNDLE_TYPE, " +
@@ -471,9 +471,9 @@
                 "b.id as BUCKET_ID, " +
                 "b.name as BUCKET_NAME " +
             "FROM " +
-                "bundle eb, " +
-                "bucket_item item," +
-                "bucket b " +
+                "BUNDLE eb, " +
+                "BUCKET_ITEM item," +
+                "BUCKET b " +
             "WHERE " +
                 "eb.id = item.id AND " +
                 "item.bucket_id = b.id";
@@ -535,12 +535,12 @@
                     "b.id as BUCKET_ID, " +
                     "b.name as BUCKET_NAME ," +
                     "eb.bundle_type as BUNDLE_TYPE, " +
-                    "eb.group_id as BUNDLE_GROUP_ID, " +
-                    "eb.artifact_id as BUNDLE_ARTIFACT_ID " +
+                    "eb.group_id as GROUP_ID, " +
+                    "eb.artifact_id as ARTIFACT_ID " +
                 "FROM " +
-                    "bundle eb, " +
-                    "bucket_item item, " +
-                    "bucket b " +
+                    "BUNDLE eb, " +
+                    "BUCKET_ITEM item," +
+                    "BUCKET b " +
                 "WHERE " +
                     "item.id = eb.id AND " +
                     "b.id = item.bucket_id");
@@ -618,7 +618,7 @@
     @Override
     public void deleteBundle(final String extensionBundleId) {
         // NOTE: All of the foreign key constraints for extension related tables are set to cascade on delete
-        final String itemDeleteSql = "DELETE FROM bucket_item WHERE id = ?";
+        final String itemDeleteSql = "DELETE FROM BUCKET_ITEM WHERE id = ?";
         jdbcTemplate.update(itemDeleteSql, extensionBundleId);
     }
 
@@ -627,7 +627,7 @@
     @Override
     public BundleVersionEntity createBundleVersion(final BundleVersionEntity extensionBundleVersion) {
         final String sql =
-                "INSERT INTO bundle_version (" +
+                "INSERT INTO BUNDLE_VERSION (" +
                     "ID, " +
                     "BUNDLE_ID, " +
                     "VERSION, " +
@@ -689,7 +689,7 @@
                 "ebv.built AS BUILT, " +
                 "ebv.built_by AS BUILT_BY, " +
                 "eb.bucket_id AS BUCKET_ID " +
-            "FROM bundle eb, bundle_version ebv " +
+            "FROM BUNDLE eb, BUNDLE_VERSION ebv " +
             "WHERE eb.id = ebv.bundle_id ";
 
     @Override
@@ -804,7 +804,7 @@
     @Override
     public void deleteBundleVersion(final String extensionBundleVersionId) {
         // NOTE: All of the foreign key constraints for extension related tables are set to cascade on delete
-        final String sql = "DELETE FROM bundle_version WHERE id = ?";
+        final String sql = "DELETE FROM BUNDLE_VERSION WHERE id = ?";
         jdbcTemplate.update(sql, extensionBundleVersionId);
     }
 
@@ -813,7 +813,7 @@
     @Override
     public BundleVersionDependencyEntity createDependency(final BundleVersionDependencyEntity dependencyEntity) {
         final String dependencySql =
-                "INSERT INTO bundle_version_dependency (" +
+                "INSERT INTO BUNDLE_VERSION_DEPENDENCY (" +
                     "ID, " +
                     "BUNDLE_VERSION_ID, " +
                     "GROUP_ID, " +
@@ -833,7 +833,7 @@
 
     @Override
     public List<BundleVersionDependencyEntity> getDependenciesForBundleVersion(final String extensionBundleVersionId) {
-        final String sql = "SELECT * FROM bundle_version_dependency WHERE bundle_version_id = ?";
+        final String sql = "SELECT * FROM BUNDLE_VERSION_DEPENDENCY WHERE bundle_version_id = ?";
         final Object[] args = {extensionBundleVersionId};
         return jdbcTemplate.query(sql, args, new BundleVersionDependencyEntityRowMapper());
     }
@@ -859,10 +859,10 @@
                 "b.id AS BUCKET_ID, " +
                 "b.name as BUCKET_NAME " +
             "FROM " +
-                    "extension e, " +
-                    "bundle_version ebv, " +
-                    "bundle eb," +
-                    "bucket b " +
+                    "EXTENSION e, " +
+                    "BUNDLE_VERSION ebv, " +
+                    "BUNDLE eb," +
+                    "BUCKET b " +
             "WHERE " +
                     "e.bundle_version_id = ebv.id AND " +
                     "ebv.bundle_id = eb.id AND " +
@@ -871,7 +871,7 @@
     @Override
     public ExtensionEntity createExtension(final ExtensionEntity extension) {
         final String insertExtensionSql =
-                "INSERT INTO extension (" +
+                "INSERT INTO EXTENSION (" +
                     "ID, " +
                     "BUNDLE_VERSION_ID, " +
                     "NAME, " +
@@ -894,7 +894,7 @@
         );
 
         // insert tags...
-        final String insertTagSql = "INSERT INTO extension_tag (EXTENSION_ID, TAG) VALUES (?, ?);";
+        final String insertTagSql = "INSERT INTO EXTENSION_TAG (EXTENSION_ID, TAG) VALUES (?, ?);";
 
         final Set<String> tags = extension.getTags();
         if (tags != null) {
@@ -945,7 +945,7 @@
 
     @Override
     public ExtensionAdditionalDetailsEntity getExtensionAdditionalDetails(final String bundleVersionId, final String name) {
-        final String selectSql = "SELECT id, additional_details FROM extension WHERE bundle_version_id = ? AND name = ?";
+        final String selectSql = "SELECT id, additional_details FROM EXTENSION WHERE bundle_version_id = ? AND name = ?";
         try {
             final Object[] args = {bundleVersionId, name};
             return jdbcTemplate.queryForObject(selectSql, args, (rs, i) -> {
@@ -988,7 +988,7 @@
             final Collection<String> tags = filterParams.getTags();
             if (tags != null && !tags.isEmpty()) {
                 sqlBuilder.append(" AND e.id IN (")
-                        .append(" SELECT et.extension_id FROM extension_tag et WHERE ");
+                        .append(" SELECT et.extension_id FROM EXTENSION_TAG et WHERE ");
 
                 boolean first = true;
                 for (final String tag : tags) {
@@ -1022,7 +1022,7 @@
         args.addAll(bucketIdentifiers);
 
         sqlBuilder.append(" AND e.id IN (")
-                .append(" SELECT ep.extension_id FROM extension_provided_service_api ep")
+                .append(" SELECT ep.extension_id FROM EXTENSION_PROVIDED_SERVICE_API ep")
                 .append(" WHERE ep.class_name = ? ")
                 .append(" AND ep.group_id = ? ")
                 .append(" AND ep.artifact_id = ? ")
@@ -1049,7 +1049,7 @@
     public List<TagCountEntity> getAllExtensionTags() {
         final String selectSql =
                 "SELECT tag as TAG, count(*) as COUNT " +
-                "FROM extension_tag " +
+                "FROM EXTENSION_TAG " +
                 "GROUP BY tag " +
                 "ORDER BY tag ASC";
 
@@ -1059,7 +1059,7 @@
     @Override
     public void deleteExtension(final ExtensionEntity extension) {
         // NOTE: All of the foreign key constraints for extension related tables are set to cascade on delete
-        final String deleteSql = "DELETE FROM extension WHERE id = ?";
+        final String deleteSql = "DELETE FROM EXTENSION WHERE id = ?";
         jdbcTemplate.update(deleteSql, extension.getId());
     }
 
@@ -1067,7 +1067,7 @@
 
     private ExtensionProvidedServiceApiEntity createProvidedServiceApi(final ExtensionProvidedServiceApiEntity providedServiceApi) {
         final String sql =
-                "INSERT INTO extension_provided_service_api (" +
+                "INSERT INTO EXTENSION_PROVIDED_SERVICE_API (" +
                     "ID, " +
                     "EXTENSION_ID, " +
                     "CLASS_NAME, " +
@@ -1092,7 +1092,7 @@
 
     private ExtensionRestrictionEntity createRestriction(final ExtensionRestrictionEntity restriction) {
         final String sql =
-                "INSERT INTO extension_restriction(" +
+                "INSERT INTO EXTENSION_RESTRICTION (" +
                     "ID, " +
                     "EXTENSION_ID, " +
                     "REQUIRED_PERMISSION, " +
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V2__Initial.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V2__Initial.sql
similarity index 100%
rename from nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V2__Initial.sql
rename to nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V2__Initial.sql
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V3__AddExtensions.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V3__AddExtensions.sql
similarity index 100%
rename from nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V3__AddExtensions.sql
rename to nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V3__AddExtensions.sql
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V4__AddCascadeOnDelete.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V4__AddCascadeOnDelete.sql
similarity index 100%
rename from nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V4__AddCascadeOnDelete.sql
rename to nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/default/V4__AddCascadeOnDelete.sql
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V2__Initial.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V2__Initial.sql
new file mode 100644
index 0000000..8e37618
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V2__Initial.sql
@@ -0,0 +1,59 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+CREATE TABLE BUCKET (
+    ID VARCHAR(50) NOT NULL,
+    NAME VARCHAR(767) NOT NULL,
+    DESCRIPTION TEXT,
+    CREATED TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+    CONSTRAINT PK__BUCKET_ID PRIMARY KEY (ID),
+    CONSTRAINT UNIQUE__BUCKET_NAME UNIQUE (NAME)
+);
+
+CREATE TABLE BUCKET_ITEM (
+    ID VARCHAR(50) NOT NULL,
+    NAME VARCHAR(1000) NOT NULL,
+    DESCRIPTION TEXT,
+    CREATED TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+    MODIFIED TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+    ITEM_TYPE VARCHAR(50) NOT NULL,
+    BUCKET_ID VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__BUCKET_ITEM_ID PRIMARY KEY (ID),
+    CONSTRAINT FK__BUCKET_ITEM_BUCKET_ID FOREIGN KEY (BUCKET_ID) REFERENCES BUCKET(ID) ON DELETE CASCADE
+);
+
+CREATE TABLE FLOW (
+    ID VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__FLOW_ID PRIMARY KEY (ID),
+    CONSTRAINT FK__FLOW_BUCKET_ITEM_ID FOREIGN KEY (ID) REFERENCES BUCKET_ITEM(ID) ON DELETE CASCADE
+);
+
+CREATE TABLE FLOW_SNAPSHOT (
+    FLOW_ID VARCHAR(50) NOT NULL,
+    VERSION INT NOT NULL,
+    CREATED TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+    CREATED_BY VARCHAR(1000) NOT NULL,
+    COMMENTS TEXT,
+    CONSTRAINT PK__FLOW_SNAPSHOT_FLOW_ID_AND_VERSION PRIMARY KEY (FLOW_ID, VERSION),
+    CONSTRAINT FK__FLOW_SNAPSHOT_FLOW_ID FOREIGN KEY (FLOW_ID) REFERENCES FLOW(ID) ON DELETE CASCADE
+);
+
+CREATE TABLE SIGNING_KEY (
+    ID VARCHAR(50) NOT NULL,
+    TENANT_IDENTITY VARCHAR(767) NOT NULL,
+    KEY_VALUE VARCHAR(50) NOT NULL,
+    CONSTRAINT PK__SIGNING_KEY_ID PRIMARY KEY (ID),
+    CONSTRAINT UNIQUE__SIGNING_KEY_TENANT_IDENTITY UNIQUE (TENANT_IDENTITY)
+);
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V3__AddExtensions.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V3__AddExtensions.sql
new file mode 100644
index 0000000..081f86c
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V3__AddExtensions.sql
@@ -0,0 +1,105 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+CREATE TABLE BUNDLE (
+    ID VARCHAR(50) NOT NULL,
+    BUCKET_ID VARCHAR(50) NOT NULL,
+    BUNDLE_TYPE VARCHAR(200) NOT NULL,
+    GROUP_ID VARCHAR(200) NOT NULL,
+    ARTIFACT_ID VARCHAR(200) NOT NULL,
+    CONSTRAINT PK__EXTENSION_BUNDLE_ID PRIMARY KEY (ID),
+    CONSTRAINT FK__EXTENSION_BUNDLE_BUCKET_ITEM_ID FOREIGN KEY (ID) REFERENCES BUCKET_ITEM(ID) ON DELETE CASCADE,
+    CONSTRAINT FK__EXTENSION_BUNDLE_BUCKET_ID FOREIGN KEY(BUCKET_ID) REFERENCES BUCKET(ID) ON DELETE CASCADE,
+    CONSTRAINT UNIQUE__EXTENSION_BUNDLE_BUCKET_GROUP_ARTIFACT UNIQUE (BUCKET_ID, GROUP_ID, ARTIFACT_ID)
+);
+
+CREATE TABLE BUNDLE_VERSION (
+    ID VARCHAR(50) NOT NULL,
+    BUNDLE_ID VARCHAR(50) NOT NULL,
+    VERSION VARCHAR(100) NOT NULL,
+    CREATED TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+    CREATED_BY VARCHAR(767) NOT NULL,
+    DESCRIPTION TEXT,
+    SHA_256_HEX VARCHAR(512) NOT NULL,
+    SHA_256_SUPPLIED INT NOT NULL,
+    CONTENT_SIZE BIGINT NOT NULL,
+    SYSTEM_API_VERSION VARCHAR(50),
+    BUILD_TOOL VARCHAR(100),
+    BUILD_FLAGS VARCHAR(100),
+    BUILD_BRANCH VARCHAR(200),
+    BUILD_TAG VARCHAR(200),
+    BUILD_REVISION VARCHAR(100),
+    BUILT TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3),
+    BUILT_BY VARCHAR(767),
+    CONSTRAINT PK__BUNDLE_VERSION_ID PRIMARY KEY (ID),
+    CONSTRAINT FK__BUNDLE_VERSION_BUNDLE_ID FOREIGN KEY (BUNDLE_ID) REFERENCES BUNDLE(ID) ON DELETE CASCADE,
+    CONSTRAINT UNIQUE__BUNDLE_VERSION_BUNDLE_ID_VERSION UNIQUE (BUNDLE_ID, VERSION)
+);
+
+CREATE TABLE BUNDLE_VERSION_DEPENDENCY (
+    ID VARCHAR(50) NOT NULL,
+    BUNDLE_VERSION_ID VARCHAR(50) NOT NULL,
+    GROUP_ID VARCHAR(200) NOT NULL,
+    ARTIFACT_ID VARCHAR(200) NOT NULL,
+    VERSION VARCHAR(100) NOT NULL,
+    CONSTRAINT PK__BUNDLE_VERSION_DEPENDENCY_ID PRIMARY KEY (ID),
+    CONSTRAINT FK__BUNDLE_VERSION_DEPENDENCY_BUNDLE_VERSION_ID FOREIGN KEY (BUNDLE_VERSION_ID) REFERENCES BUNDLE_VERSION(ID) ON DELETE CASCADE,
+    CONSTRAINT UNIQUE__BUNDLE_VERSION_DEPENDENCY_BUNDLE_ID_GAV UNIQUE (BUNDLE_VERSION_ID, GROUP_ID, ARTIFACT_ID, VERSION)
+);
+
+CREATE TABLE EXTENSION (
+    ID VARCHAR(50) NOT NULL,
+    BUNDLE_VERSION_ID VARCHAR(50) NOT NULL,
+    NAME VARCHAR(500) NOT NULL,
+    DISPLAY_NAME VARCHAR(500) NOT NULL,
+    TYPE VARCHAR(100) NOT NULL,
+    CONTENT TEXT NOT NULL,
+    ADDITIONAL_DETAILS TEXT,
+    HAS_ADDITIONAL_DETAILS INT NOT NULL,
+    CONSTRAINT PK__EXTENSION_ID PRIMARY KEY (ID),
+    CONSTRAINT FK__EXTENSION_BUNDLE_VERSION_ID FOREIGN KEY (BUNDLE_VERSION_ID) REFERENCES BUNDLE_VERSION(ID) ON DELETE CASCADE,
+    CONSTRAINT UNIQUE__EXTENSION_BUNDLE_VERSION_ID_AND_NAME UNIQUE (BUNDLE_VERSION_ID, NAME)
+);
+
+CREATE TABLE EXTENSION_PROVIDED_SERVICE_API (
+    ID VARCHAR(50) NOT NULL,
+    EXTENSION_ID VARCHAR(50) NOT NULL,
+    CLASS_NAME VARCHAR (200) NOT NULL,
+    GROUP_ID VARCHAR(200) NOT NULL,
+    ARTIFACT_ID VARCHAR(200) NOT NULL,
+    VERSION VARCHAR(100) NOT NULL,
+    CONSTRAINT PK__EXTENSION_PROVIDED_SERVICE_API_ID PRIMARY KEY (ID),
+    CONSTRAINT FK__EXTENSION_PROVIDED_SERVICE_API_EXTENSION_ID FOREIGN KEY (EXTENSION_ID) REFERENCES EXTENSION(ID) ON DELETE CASCADE,
+    CONSTRAINT UNIQUE__EXTENSION_PROVIDED_SERVICE_API UNIQUE (EXTENSION_ID, CLASS_NAME, GROUP_ID, ARTIFACT_ID, VERSION)
+);
+
+CREATE TABLE EXTENSION_RESTRICTION (
+    ID VARCHAR(50) NOT NULL,
+    EXTENSION_ID VARCHAR(50) NOT NULL,
+    REQUIRED_PERMISSION VARCHAR(200) NOT NULL,
+    EXPLANATION VARCHAR (4096) NOT NULL,
+    CONSTRAINT PK__EXTENSION_RESTRICTION_ID PRIMARY KEY (ID),
+    CONSTRAINT FK__EXTENSION_RESTRICTION_EXTENSION_ID FOREIGN KEY (EXTENSION_ID) REFERENCES EXTENSION(ID) ON DELETE CASCADE,
+    CONSTRAINT UNIQUE__EXTENSION_RESTRICTION_EXTENSION_ID_REQUIRED_PERMISSION UNIQUE (EXTENSION_ID, REQUIRED_PERMISSION)
+);
+
+CREATE TABLE EXTENSION_TAG (
+    EXTENSION_ID VARCHAR(50) NOT NULL,
+    TAG VARCHAR(200) NOT NULL,
+    CONSTRAINT PK__EXTENSION_TAG_EXTENSION_ID_AND_TAG PRIMARY KEY (EXTENSION_ID, TAG),
+    CONSTRAINT FK__EXTENSION_TAG_EXTENSION_ID FOREIGN KEY (EXTENSION_ID) REFERENCES EXTENSION(ID) ON DELETE CASCADE
+);
+
+ALTER TABLE BUCKET ADD ALLOW_EXTENSION_BUNDLE_REDEPLOY INT NOT NULL DEFAULT 0;
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V4__AddCascadeOnDelete.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V4__AddCascadeOnDelete.sql
new file mode 100644
index 0000000..2bee820
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/mysql/V4__AddCascadeOnDelete.sql
@@ -0,0 +1,26 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+-- NOTE: This file is here to keep same version history as the default migrations, but since MySQL came
+-- later, the cascades are part of the original constraints in mysql/V2_Initial.sql
+
+--ALTER TABLE BUCKET_ITEM DROP CONSTRAINT FK__BUCKET_ITEM_BUCKET_ID;
+--ALTER TABLE BUCKET_ITEM ADD CONSTRAINT FK__BUCKET_ITEM_BUCKET_ID FOREIGN KEY (BUCKET_ID) REFERENCES BUCKET(ID) ON DELETE CASCADE;
+
+--ALTER TABLE FLOW DROP CONSTRAINT FK__FLOW_BUCKET_ITEM_ID;
+--ALTER TABLE FLOW ADD CONSTRAINT FK__FLOW_BUCKET_ITEM_ID FOREIGN KEY (ID) REFERENCES BUCKET_ITEM(ID) ON DELETE CASCADE;
+
+--ALTER TABLE FLOW_SNAPSHOT DROP CONSTRAINT FK__FLOW_SNAPSHOT_FLOW_ID;
+--ALTER TABLE FLOW_SNAPSHOT ADD CONSTRAINT FK__FLOW_SNAPSHOT_FLOW_ID FOREIGN KEY (FLOW_ID) REFERENCES FLOW(ID) ON DELETE CASCADE;
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/original/V1.2__IncreaseColumnSizes.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/original/V1.2__IncreaseColumnSizes.sql
similarity index 100%
rename from nifi-registry-core/nifi-registry-framework/src/main/resources/db/original/V1.2__IncreaseColumnSizes.sql
rename to nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/original/V1.2__IncreaseColumnSizes.sql
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/original/V1.3__DropBucketItemNameUniqueness.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/original/V1.3__DropBucketItemNameUniqueness.sql
similarity index 100%
rename from nifi-registry-core/nifi-registry-framework/src/main/resources/db/original/V1.3__DropBucketItemNameUniqueness.sql
rename to nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/original/V1.3__DropBucketItemNameUniqueness.sql
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/original/V1__Initial.sql b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/original/V1__Initial.sql
similarity index 100%
rename from nifi-registry-core/nifi-registry-framework/src/main/resources/db/original/V1__Initial.sql
rename to nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/original/V1__Initial.sql
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseTestApplication.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseTestApplication.java
index 0ce3812..dde331e 100644
--- a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseTestApplication.java
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/DatabaseTestApplication.java
@@ -49,4 +49,5 @@
     public NiFiRegistryProperties createNiFiRegistryProperties() {
         return Mockito.mock(NiFiRegistryProperties.class);
     }
+
 }
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
index b150707..027bd3a 100644
--- a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
@@ -75,7 +75,7 @@
         assertEquals(b.getId(), createdBucket.getId());
         assertEquals(b.getName(), createdBucket.getName());
         assertEquals(b.getDescription(), createdBucket.getDescription());
-        assertEquals(b.getCreated(), createdBucket.getCreated());
+        assertEquals(b.getCreated().getTime(), createdBucket.getCreated().getTime());
         assertFalse(b.isAllowExtensionBundleRedeploy());
     }
 
@@ -256,7 +256,7 @@
         assertEquals(flow.getBucketId(), createdFlow.getBucketId());
         assertEquals(flow.getName(), createdFlow.getName());
         assertEquals(flow.getDescription(), createdFlow.getDescription());
-        assertEquals(flow.getCreated(), createdFlow.getCreated());
+        assertEquals(flow.getCreated().getTime(), createdFlow.getCreated().getTime());
         assertEquals(flow.getModified(), createdFlow.getModified());
         assertEquals(flow.getType(), createdFlow.getType());
     }
@@ -360,7 +360,7 @@
         assertEquals(flowSnapshot.getFlowId(), createdFlowSnapshot.getFlowId());
         assertEquals(flowSnapshot.getVersion(), createdFlowSnapshot.getVersion());
         assertEquals(flowSnapshot.getComments(), createdFlowSnapshot.getComments());
-        assertEquals(flowSnapshot.getCreated(), createdFlowSnapshot.getCreated());
+        assertEquals(flowSnapshot.getCreated().getTime(), createdFlowSnapshot.getCreated().getTime());
         assertEquals(flowSnapshot.getCreatedBy(), createdFlowSnapshot.getCreatedBy());
     }
 
@@ -627,7 +627,7 @@
         assertEquals(bundleVersion.getBuildTag(), createdBundleVersion.getBuildTag());
         assertEquals(bundleVersion.getBuildRevision(), createdBundleVersion.getBuildRevision());
         assertEquals(bundleVersion.getBuiltBy(), createdBundleVersion.getBuiltBy());
-        assertEquals(bundleVersion.getBuilt(), createdBundleVersion.getBuilt());
+        assertEquals(bundleVersion.getBuilt().getTime(), createdBundleVersion.getBuilt().getTime());
     }
 
     @Test
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java
index 116ba35..6940331 100644
--- a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java
@@ -54,7 +54,7 @@
 
         flyway = Flyway.configure()
                 .dataSource(dataSource)
-                .locations("db/original")
+                .locations("db/migration/original")
                 .load();
 
         flyway.migrate();
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/resources/db/migration/V999999.1__test-setup.sql b/nifi-registry-core/nifi-registry-framework/src/test/resources/db/migration/V999999.1__test-setup.sql
deleted file mode 100644
index 54b5dc0..0000000
--- a/nifi-registry-core/nifi-registry-framework/src/test/resources/db/migration/V999999.1__test-setup.sql
+++ /dev/null
@@ -1,313 +0,0 @@
--- Licensed to the Apache Software Foundation (ASF) under one or more
--- contributor license agreements.  See the NOTICE file distributed with
--- this work for additional information regarding copyright ownership.
--- The ASF licenses this file to You under the Apache License, Version 2.0
--- (the "License"); you may not use this file except in compliance with
--- the License.  You may obtain a copy of the License at
---
---     http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
-
--- test data for buckets
-
-insert into bucket (id, name, description, created)
-  values ('1', 'Bucket 1', 'This is test bucket 1', parsedatetime('2017-09-11 12:51:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
-
-insert into bucket (id, name, description, created)
-  values ('2', 'Bucket 2', 'This is test bucket 2', parsedatetime('2017-09-11 12:52:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
-
-insert into bucket (id, name, description, created)
-  values ('3', 'Bucket 3', 'This is test bucket 3', parsedatetime('2017-09-11 12:53:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
-
-insert into bucket (id, name, description, created)
-  values ('4', 'Bucket 4', 'This is test bucket 4', parsedatetime('2017-09-11 12:54:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
-
-insert into bucket (id, name, description, created)
-  values ('5', 'Bucket 5', 'This is test bucket 5', parsedatetime('2017-09-11 12:55:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
-
-insert into bucket (id, name, description, created)
-  values ('6', 'Bucket 6', 'This is test bucket 6', parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
-
-
--- test data for flows
-
-insert into bucket_item (id, name, description, created, modified, item_type, bucket_id)
-  values ('1', 'Flow 1', 'This is flow 1 bucket 1', parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), 'FLOW', '1');
-
-insert into flow (id) values ('1');
-
-insert into bucket_item (id, name, description, created, modified, item_type, bucket_id)
-  values ('2', 'Flow 2', 'This is flow 2 bucket 1', parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), 'FLOW', '1');
-
-insert into flow (id) values ('2');
-
-insert into bucket_item (id, name, description, created, modified, item_type, bucket_id)
-  values ('3', 'Flow 1', 'This is flow 1 bucket 2', parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), 'FLOW', '2');
-
-insert into flow (id) values ('3');
-
-
--- test data for flow snapshots
-
-insert into flow_snapshot (flow_id, version, created, created_by, comments)
-  values ('1', 1, parsedatetime('2017-09-11 12:57:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), 'user1', 'This is flow 1 snapshot 1');
-
-insert into flow_snapshot (flow_id, version, created, created_by, comments)
-  values ('1', 2, parsedatetime('2017-09-11 12:58:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), 'user1', 'This is flow 1 snapshot 2');
-
-insert into flow_snapshot (flow_id, version, created, created_by, comments)
-  values ('1', 3, parsedatetime('2017-09-11 12:59:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), 'user1', 'This is flow 1 snapshot 3');
-
-
--- test data for signing keys
-
-insert into signing_key (id, tenant_identity, key_value)
-  values ('1', 'unit_test_tenant_identity', '0123456789abcdef');
-
--- test data for extension bundles
-
--- processors bundle, depends on service api bundle
-insert into bucket_item (
-  id,
-  name,
-  description,
-  created,
-  modified,
-  item_type,
-  bucket_id
-) values (
-  'eb1',
-  'nifi-example-processors-nar',
-  'Example processors bundle',
-  parsedatetime('2018-11-02 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'),
-  parsedatetime('2018-11-02 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'),
-  'BUNDLE',
-  '3'
-);
-
-insert into bundle (
-  id,
-  bucket_id,
-  bundle_type,
-  group_id,
-  artifact_id
-) values (
-  'eb1',
-  '3',
-  'NIFI_NAR',
-  'org.apache.nifi',
-  'nifi-example-processors-nar'
-);
-
-insert into bundle_version (
-  id,
-  bundle_id,
-  version,
-  created,
-  created_by,
-  description,
-  sha_256_hex,
-  sha_256_supplied,
-  content_size
-) values (
-  'eb1-v1',
-  'eb1',
-  '1.0.0',
-  parsedatetime('2018-11-02 13:00:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'),
-  'user1',
-  'First version of eb1',
-  '123456789',
-  '1',
-  1024
-);
-
-insert into bundle_version_dependency (
-  id,
-  bundle_version_id,
-  group_id,
-  artifact_id,
-  version
-) values (
-  'eb1-v1-dep1',
-  'eb1-v1',
-  'org.apache.nifi',
-  'nifi-example-service-api-nar',
-  '2.0.0'
-);
-
--- service impl bundle, depends on service api bundle
-insert into bucket_item (
-  id,
-  name,
-  description,
-  created,
-  modified,
-  item_type,
-  bucket_id
-) values (
-  'eb2',
-  'nifi-example-services-nar',
-  'Example services bundle',
-  parsedatetime('2018-11-02 12:57:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'),
-  parsedatetime('2018-11-02 12:57:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'),
-  'BUNDLE',
-  '3'
-);
-
-insert into bundle (
-  id,
-  bucket_id,
-  bundle_type,
-  group_id,
-  artifact_id
-) values (
-  'eb2',
-  '3',
-  'NIFI_NAR',
-  'com.foo',
-  'nifi-example-services-nar'
-);
-
-insert into bundle_version (
-  id,
-  bundle_id,
-  version,
-  created,
-  created_by,
-  description,
-  sha_256_hex,
-  sha_256_supplied,
-  content_size
-) values (
-  'eb2-v1',
-  'eb2',
-  '1.0.0',
-  parsedatetime('2018-11-02 13:00:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'),
-  'user1',
-  'First version of eb2',
-  '123456789',
-  '1',
-  1024
-);
-
-insert into bundle_version_dependency (
-  id,
-  bundle_version_id,
-  group_id,
-  artifact_id,
-  version
-) values (
-  'eb2-v1-dep1',
-  'eb2-v1',
-  'org.apache.nifi',
-  'nifi-example-service-api-nar',
-  '2.0.0'
-);
-
--- service api bundle
-insert into bucket_item (
-  id,
-  name,
-  description,
-  created,
-  modified,
-  item_type,
-  bucket_id
-) values (
-  'eb3',
-  'nifi-example-service-api-nar',
-  'Example service API bundle',
-  parsedatetime('2018-11-02 12:58:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'),
-  parsedatetime('2017-11-02 12:58:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'),
-  'BUNDLE',
-  '3'
-);
-
-insert into bundle (
-  id,
-  bucket_id,
-  bundle_type,
-  group_id,
-  artifact_id
-) values (
-  'eb3',
-  '3',
-  'NIFI_NAR',
-  'org.apache.nifi',
-  'nifi-example-service-api-nar'
-);
-
-insert into bundle_version (
-  id,
-  bundle_id,
-  version,
-  created,
-  created_by,
-  description,
-  sha_256_hex,
-  sha_256_supplied,
-  content_size
-) values (
-  'eb3-v1',
-  'eb3',
-  '2.0.0',
-  parsedatetime('2018-11-02 13:00:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'),
-  'user1',
-  'First version of eb3',
-  '123456789',
-  '1',
-  1024
-);
-
--- test data for extensions
-
-insert into extension (
-  id, bundle_version_id, name, display_name, type, content, has_additional_details
-) values (
-  'e1', 'eb1-v1', 'org.apache.nifi.ExampleProcessor', 'ExampleProcessor', 'PROCESSOR', '{ "name" : "org.apache.nifi.ExampleProcessor", "type" : "PROCESSOR" }', 0
-);
-
-insert into extension (
-  id, bundle_version_id, name, display_name, type, content, has_additional_details
-) values (
-  'e2', 'eb1-v1', 'org.apache.nifi.ExampleProcessorRestricted', 'ExampleProcessorRestricted', 'PROCESSOR', '{ "name" : "org.apache.nifi.ExampleProcessorRestricted", "type" : "PROCESSOR" }', 0
-);
-
-insert into extension (
-  id, bundle_version_id, name, display_name, type, content, additional_details, has_additional_details
-) values (
-  'e3', 'eb2-v1', 'org.apache.nifi.ExampleService', 'ExampleService', 'CONTROLLER_SERVICE', '{ "name" : "org.apache.nifi.ExampleService", "type" : "CONTROLLER_SERVICE" }', 'extra docs', 1
-);
-
--- test data for extension restrictions
-
-insert into extension_restriction (
-  id, extension_id, required_permission, explanation
-) values (
-  'er1', 'e2', 'write filesystem', 'This writes to the filesystem'
-);
-
--- test data for extension provided service apis
-
-insert into extension_provided_service_api (
-  id, extension_id, class_name, group_id, artifact_id, version
-) values (
-  'epapi1', 'e3', 'org.apache.nifi.ExampleServiceAPI', 'org.apache.nifi', 'nifi-example-service-api-nar', '2.0.0'
-);
-
--- test data for extension tags
-
-insert into extension_tag (extension_id, tag) values ('e1', 'example');
-insert into extension_tag (extension_id, tag) values ('e1', 'processor');
-
-insert into extension_tag (extension_id, tag) values ('e2', 'example');
-insert into extension_tag (extension_id, tag) values ('e2', 'processor');
-insert into extension_tag (extension_id, tag) values ('e2', 'restricted');
-
-insert into extension_tag (extension_id, tag) values ('e3', 'example');
-insert into extension_tag (extension_id, tag) values ('e3', 'service');
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/resources/db/migration/common/V999999.1__test-setup.sql b/nifi-registry-core/nifi-registry-framework/src/test/resources/db/migration/common/V999999.1__test-setup.sql
new file mode 100644
index 0000000..c2218b7
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/resources/db/migration/common/V999999.1__test-setup.sql
@@ -0,0 +1,313 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+-- test data for buckets
+
+insert into BUCKET (id, name, description, created)
+  values ('1', 'Bucket 1', 'This is test bucket 1', DATE'2017-09-11');
+
+insert into BUCKET (id, name, description, created)
+  values ('2', 'Bucket 2', 'This is test bucket 2', DATE'2017-09-12');
+
+insert into BUCKET (id, name, description, created)
+  values ('3', 'Bucket 3', 'This is test bucket 3', DATE'2017-09-13');
+
+insert into BUCKET (id, name, description, created)
+  values ('4', 'Bucket 4', 'This is test bucket 4', DATE'2017-09-14');
+
+insert into BUCKET (id, name, description, created)
+  values ('5', 'Bucket 5', 'This is test bucket 5', DATE'2017-09-15');
+
+insert into BUCKET (id, name, description, created)
+  values ('6', 'Bucket 6', 'This is test bucket 6', DATE'2017-09-16');
+
+
+-- test data for flows
+
+insert into BUCKET_ITEM (id, name, description, created, modified, item_type, bucket_id)
+  values ('1', 'Flow 1', 'This is flow 1 bucket 1', DATE'2017-09-11', DATE'2017-09-11', 'FLOW', '1');
+
+insert into FLOW (id) values ('1');
+
+insert into BUCKET_ITEM (id, name, description, created, modified, item_type, bucket_id)
+  values ('2', 'Flow 2', 'This is flow 2 bucket 1', DATE'2017-09-11', DATE'2017-09-11', 'FLOW', '1');
+
+insert into FLOW (id) values ('2');
+
+insert into BUCKET_ITEM (id, name, description, created, modified, item_type, bucket_id)
+  values ('3', 'Flow 1', 'This is flow 1 bucket 2', DATE'2017-09-11', DATE'2017-09-11', 'FLOW', '2');
+
+insert into FLOW (id) values ('3');
+
+
+-- test data for flow snapshots
+
+insert into FLOW_SNAPSHOT (flow_id, version, created, created_by, comments)
+  values ('1', 1, DATE'2017-09-11', 'user1', 'This is flow 1 snapshot 1');
+
+insert into FLOW_SNAPSHOT (flow_id, version, created, created_by, comments)
+  values ('1', 2, DATE'2017-09-12', 'user1', 'This is flow 1 snapshot 2');
+
+insert into FLOW_SNAPSHOT (flow_id, version, created, created_by, comments)
+  values ('1', 3, DATE'2017-09-11', 'user1', 'This is flow 1 snapshot 3');
+
+
+-- test data for signing keys
+
+insert into SIGNING_KEY (id, tenant_identity, key_value)
+  values ('1', 'unit_test_tenant_identity', '0123456789abcdef');
+
+-- test data for extension bundles
+
+-- processors bundle, depends on service api bundle
+insert into BUCKET_ITEM (
+  id,
+  name,
+  description,
+  created,
+  modified,
+  item_type,
+  bucket_id
+) values (
+  'eb1',
+  'nifi-example-processors-nar',
+  'Example processors bundle',
+  DATE'2018-11-02',
+  DATE'2018-11-02',
+  'BUNDLE',
+  '3'
+);
+
+insert into BUNDLE (
+  id,
+  bucket_id,
+  bundle_type,
+  group_id,
+  artifact_id
+) values (
+  'eb1',
+  '3',
+  'NIFI_NAR',
+  'org.apache.nifi',
+  'nifi-example-processors-nar'
+);
+
+insert into BUNDLE_VERSION (
+  id,
+  bundle_id,
+  version,
+  created,
+  created_by,
+  description,
+  sha_256_hex,
+  sha_256_supplied,
+  content_size
+) values (
+  'eb1-v1',
+  'eb1',
+  '1.0.0',
+  DATE'2018-11-02',
+  'user1',
+  'First version of eb1',
+  '123456789',
+  '1',
+  1024
+);
+
+insert into BUNDLE_VERSION_DEPENDENCY (
+  id,
+  bundle_version_id,
+  group_id,
+  artifact_id,
+  version
+) values (
+  'eb1-v1-dep1',
+  'eb1-v1',
+  'org.apache.nifi',
+  'nifi-example-service-api-nar',
+  '2.0.0'
+);
+
+-- service impl bundle, depends on service api bundle
+insert into BUCKET_ITEM (
+  id,
+  name,
+  description,
+  created,
+  modified,
+  item_type,
+  bucket_id
+) values (
+  'eb2',
+  'nifi-example-services-nar',
+  'Example services bundle',
+  DATE'2018-11-03',
+  DATE'2018-11-03',
+  'BUNDLE',
+  '3'
+);
+
+insert into BUNDLE (
+  id,
+  bucket_id,
+  bundle_type,
+  group_id,
+  artifact_id
+) values (
+  'eb2',
+  '3',
+  'NIFI_NAR',
+  'com.foo',
+  'nifi-example-services-nar'
+);
+
+insert into BUNDLE_VERSION (
+  id,
+  bundle_id,
+  version,
+  created,
+  created_by,
+  description,
+  sha_256_hex,
+  sha_256_supplied,
+  content_size
+) values (
+  'eb2-v1',
+  'eb2',
+  '1.0.0',
+  DATE'2018-11-03',
+  'user1',
+  'First version of eb2',
+  '123456789',
+  '1',
+  1024
+);
+
+insert into BUNDLE_VERSION_DEPENDENCY (
+  id,
+  bundle_version_id,
+  group_id,
+  artifact_id,
+  version
+) values (
+  'eb2-v1-dep1',
+  'eb2-v1',
+  'org.apache.nifi',
+  'nifi-example-service-api-nar',
+  '2.0.0'
+);
+
+-- service api bundle
+insert into BUCKET_ITEM (
+  id,
+  name,
+  description,
+  created,
+  modified,
+  item_type,
+  bucket_id
+) values (
+  'eb3',
+  'nifi-example-service-api-nar',
+  'Example service API bundle',
+  DATE'2018-11-04',
+  DATE'2017-11-04',
+  'BUNDLE',
+  '3'
+);
+
+insert into BUNDLE (
+  id,
+  bucket_id,
+  bundle_type,
+  group_id,
+  artifact_id
+) values (
+  'eb3',
+  '3',
+  'NIFI_NAR',
+  'org.apache.nifi',
+  'nifi-example-service-api-nar'
+);
+
+insert into BUNDLE_VERSION (
+  id,
+  bundle_id,
+  version,
+  created,
+  created_by,
+  description,
+  sha_256_hex,
+  sha_256_supplied,
+  content_size
+) values (
+  'eb3-v1',
+  'eb3',
+  '2.0.0',
+  DATE'2018-11-04',
+  'user1',
+  'First version of eb3',
+  '123456789',
+  '1',
+  1024
+);
+
+-- test data for extensions
+
+insert into EXTENSION (
+  id, bundle_version_id, name, display_name, type, content, has_additional_details
+) values (
+  'e1', 'eb1-v1', 'org.apache.nifi.ExampleProcessor', 'ExampleProcessor', 'PROCESSOR', '{ "name" : "org.apache.nifi.ExampleProcessor", "type" : "PROCESSOR" }', 0
+);
+
+insert into EXTENSION (
+  id, bundle_version_id, name, display_name, type, content, has_additional_details
+) values (
+  'e2', 'eb1-v1', 'org.apache.nifi.ExampleProcessorRestricted', 'ExampleProcessorRestricted', 'PROCESSOR', '{ "name" : "org.apache.nifi.ExampleProcessorRestricted", "type" : "PROCESSOR" }', 0
+);
+
+insert into EXTENSION (
+  id, bundle_version_id, name, display_name, type, content, additional_details, has_additional_details
+) values (
+  'e3', 'eb2-v1', 'org.apache.nifi.ExampleService', 'ExampleService', 'CONTROLLER_SERVICE', '{ "name" : "org.apache.nifi.ExampleService", "type" : "CONTROLLER_SERVICE" }', 'extra docs', 1
+);
+
+-- test data for extension restrictions
+
+insert into EXTENSION_RESTRICTION (
+  id, extension_id, required_permission, explanation
+) values (
+  'er1', 'e2', 'write filesystem', 'This writes to the filesystem'
+);
+
+-- test data for extension provided service apis
+
+insert into EXTENSION_PROVIDED_SERVICE_API (
+  id, extension_id, class_name, group_id, artifact_id, version
+) values (
+  'epapi1', 'e3', 'org.apache.nifi.ExampleServiceAPI', 'org.apache.nifi', 'nifi-example-service-api-nar', '2.0.0'
+);
+
+-- test data for extension tags
+
+insert into EXTENSION_TAG (extension_id, tag) values ('e1', 'example');
+insert into EXTENSION_TAG (extension_id, tag) values ('e1', 'processor');
+
+insert into EXTENSION_TAG (extension_id, tag) values ('e2', 'example');
+insert into EXTENSION_TAG (extension_id, tag) values ('e2', 'processor');
+insert into EXTENSION_TAG (extension_id, tag) values ('e2', 'restricted');
+
+insert into EXTENSION_TAG (extension_id, tag) values ('e3', 'example');
+insert into EXTENSION_TAG (extension_id, tag) values ('e3', 'service');
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-test/pom.xml b/nifi-registry-core/nifi-registry-test/pom.xml
new file mode 100644
index 0000000..3509e3c
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
+    license agreements. See the NOTICE file distributed with this work for additional 
+    information regarding copyright ownership. The ASF licenses this file to 
+    You under the Apache License, Version 2.0 (the "License"); you may not use 
+    this file except in compliance with the License. You may obtain a copy of 
+    the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required 
+    by applicable law or agreed to in writing, software distributed under the 
+    License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
+    OF ANY KIND, either express or implied. See the License for the specific 
+    language governing permissions and limitations under the License. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi.registry</groupId>
+        <artifactId>nifi-registry-core</artifactId>
+        <version>0.4.0-SNAPSHOT</version>
+    </parent>
+    
+    <artifactId>nifi-registry-test</artifactId>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <version>${spring.boot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>mysql</artifactId>
+            <version>${testcontainers.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>jcl-over-slf4j</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>mariadb</artifactId>
+            <version>${testcontainers.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>postgresql</artifactId>
+            <version>${testcontainers.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.15</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mariadb.jdbc</groupId>
+            <artifactId>mariadb-java-client</artifactId>
+            <version>2.4.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+            <version>42.2.5</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/DatabaseProfileValueSource.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/DatabaseProfileValueSource.java
new file mode 100644
index 0000000..1a9e5d9
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/DatabaseProfileValueSource.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.springframework.test.annotation.ProfileValueSource;
+
+/**
+ * This {@link ProfileValueSource} offers a set of keys  {@code current.database.is.<database>} and
+ * {@code current.database.is.not.<database>} where {@code <database> } is a database as used in active profiles
+ * to enable integration tests to run with a certain database. The value returned for these keys is
+ * {@code "true"} or {@code "false"} depending on if the database is actually the one currently used by integration tests.
+ *
+ * @author Jens Schauder
+ */
+public class DatabaseProfileValueSource implements ProfileValueSource {
+
+    private static final String MYSQL = "mysql";
+    private static final String MARIADB = "mariadb";
+    private static final String POSTGRES = "postgres";
+    private static final String H2 = "h2";
+
+    private String currentDatabase;
+
+    DatabaseProfileValueSource() {
+        final String activeProfiles = System.getProperty("spring.profiles.active", H2);
+
+        if (activeProfiles.contains(H2)) {
+            currentDatabase = H2;
+        } else if (activeProfiles.contains(MYSQL)) {
+            currentDatabase = MYSQL;
+        } else if (activeProfiles.contains(MARIADB)) {
+            currentDatabase = MARIADB;
+        } else if (activeProfiles.contains(POSTGRES)) {
+            currentDatabase = POSTGRES;
+        }
+    }
+
+    @Override
+    public String get(String key) {
+        if (!key.startsWith("current.database.is.")) {
+            return null;
+        }
+        if (key.startsWith("current.database.is.not.")) {
+            return Boolean.toString(!key.endsWith(currentDatabase)).toLowerCase();
+        }
+        return Boolean.toString(key.endsWith(currentDatabase)).toLowerCase();
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDB10DataSourceFactory.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDB10DataSourceFactory.java
new file mode 100644
index 0000000..140af67
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDB10DataSourceFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.testcontainers.containers.MariaDBContainer;
+
+@Configuration
+@Profile("mariadb-10")
+public class MariaDB10DataSourceFactory extends MariaDBDataSourceFactory {
+
+    private static final MariaDBContainer MARIA_DB_CONTAINER = new MariaDBCustomContainer("mariadb:10.0");
+
+    static {
+        MARIA_DB_CONTAINER.start();
+    }
+
+    @Override
+    protected MariaDBContainer mariaDBContainer() {
+        return MARIA_DB_CONTAINER;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDB10_2DataSourceFactory.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDB10_2DataSourceFactory.java
new file mode 100644
index 0000000..34a5bc2
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDB10_2DataSourceFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.testcontainers.containers.MariaDBContainer;
+
+@Configuration
+@Profile("mariadb-10-2")
+public class MariaDB10_2DataSourceFactory extends MariaDBDataSourceFactory {
+
+    private static final MariaDBContainer MARIA_DB_CONTAINER = new MariaDBCustomContainer("mariadb:10.2");
+
+    static {
+        MARIA_DB_CONTAINER.start();
+    }
+
+    @Override
+    protected MariaDBContainer mariaDBContainer() {
+        return MARIA_DB_CONTAINER;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDB10_3DataSourceFactory.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDB10_3DataSourceFactory.java
new file mode 100644
index 0000000..92389f1
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDB10_3DataSourceFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.testcontainers.containers.MariaDBContainer;
+
+@Configuration
+@Profile("mariadb-10-3")
+public class MariaDB10_3DataSourceFactory extends MariaDBDataSourceFactory {
+
+    private static final MariaDBContainer MARIA_DB_CONTAINER = new MariaDBCustomContainer("mariadb:10.3");
+
+    static {
+        MARIA_DB_CONTAINER.start();
+    }
+
+    @Override
+    protected MariaDBContainer mariaDBContainer() {
+        return MARIA_DB_CONTAINER;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDBCustomContainer.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDBCustomContainer.java
new file mode 100644
index 0000000..3a99312
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDBCustomContainer.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.testcontainers.containers.MariaDBContainer;
+
+/**
+ * Custom container to override the JDBC URL and add additional query parameters.
+ *
+ * NOTE: At the time of implementing this, Flyway does not support some versions of MariaDB because the driver returns an unexpected DB name:
+ *
+ * https://github.com/flyway/flyway/issues/2339
+ *
+ * The work around is to add the useMysqlMetadata=true to the URL.
+ */
+public class MariaDBCustomContainer extends MariaDBContainer {
+
+    public MariaDBCustomContainer(String dockerImageName) {
+        super(dockerImageName);
+    }
+
+    @Override
+    public String getJdbcUrl() {
+        return super.getJdbcUrl() + "?useMysqlMetadata=true";
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDBDataSourceFactory.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDBDataSourceFactory.java
new file mode 100644
index 0000000..9133947
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MariaDBDataSourceFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.mariadb.jdbc.MariaDbDataSource;
+import org.testcontainers.containers.MariaDBContainer;
+import org.testcontainers.delegate.DatabaseDelegate;
+import org.testcontainers.jdbc.JdbcDatabaseDelegate;
+
+import javax.annotation.PostConstruct;
+import javax.script.ScriptException;
+import javax.sql.DataSource;
+import java.sql.SQLException;
+
+public abstract class MariaDBDataSourceFactory extends TestDataSourceFactory {
+
+    protected abstract MariaDBContainer mariaDBContainer();
+
+    @Override
+    protected DataSource createDataSource() {
+        try {
+            final MariaDBContainer container = mariaDBContainer();
+            final MariaDbDataSource dataSource = new MariaDbDataSource();
+            dataSource.setUrl(container.getJdbcUrl());
+            dataSource.setUser(container.getUsername());
+            dataSource.setPassword(container.getPassword());
+            dataSource.setDatabaseName(container.getDatabaseName());
+            return dataSource;
+        } catch (SQLException e) {
+            throw new RuntimeException("Unable to create MariaDB DataSource", e);
+        }
+    }
+
+    @PostConstruct
+    public void initDatabase() throws SQLException, ScriptException {
+        DatabaseDelegate databaseDelegate = new JdbcDatabaseDelegate(mariaDBContainer(), "");
+        databaseDelegate.execute("DROP DATABASE test; CREATE DATABASE test;", "", 0, false, true);
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySql6DataSourceFactory.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySql6DataSourceFactory.java
new file mode 100644
index 0000000..69fc987
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySql6DataSourceFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.testcontainers.containers.MySQLContainer;
+
+@Configuration
+@Profile("mysql-56")
+public class MySql6DataSourceFactory extends MySqlDataSourceFactory {
+
+    private static final MySQLContainer MYSQL_CONTAINER = new MySqlCustomContainer("mysql:5.6");
+
+    static {
+        MYSQL_CONTAINER.start();
+    }
+
+    @Override
+    protected MySQLContainer mysqlContainer() {
+        return MYSQL_CONTAINER;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySql7DataSourceFactory.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySql7DataSourceFactory.java
new file mode 100644
index 0000000..d0a1fde
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySql7DataSourceFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.testcontainers.containers.MySQLContainer;
+
+@Configuration
+@Profile({"mysql", "mysql-57"})
+public class MySql7DataSourceFactory extends MySqlDataSourceFactory {
+
+    private static final MySQLContainer MYSQL_CONTAINER = new MySqlCustomContainer("mysql:5.7");
+
+    static {
+        MYSQL_CONTAINER.start();
+    }
+
+    @Override
+    protected MySQLContainer mysqlContainer() {
+        return MYSQL_CONTAINER;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySql8DataSourceFactory.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySql8DataSourceFactory.java
new file mode 100644
index 0000000..b069438
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySql8DataSourceFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.testcontainers.containers.MySQLContainer;
+
+@Configuration
+@Profile({"mysql", "mysql-8"})
+public class MySql8DataSourceFactory extends MySqlDataSourceFactory {
+
+    private static final MySQLContainer MYSQL_CONTAINER = new MySqlCustomContainer("mysql:8.0");
+
+    static {
+        MYSQL_CONTAINER.start();
+    }
+
+    @Override
+    protected MySQLContainer mysqlContainer() {
+        return MYSQL_CONTAINER;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySqlCustomContainer.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySqlCustomContainer.java
new file mode 100644
index 0000000..202a030
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySqlCustomContainer.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.testcontainers.containers.MySQLContainer;
+
+/**
+ * Custom container to override the JDBC URL and add additional query parameters.
+ *
+ * NOTE: At the time of implementing this, testcontainers could not start a container for MySQL 8, see this issue:
+ *
+ * https://github.com/testcontainers/testcontainers-java/issues/736
+ *
+ * The work around is to add the allowPublicKeyRetrieval=true to the URL.
+ */
+public class MySqlCustomContainer extends MySQLContainer {
+
+    public MySqlCustomContainer(String dockerImageName) {
+        super(dockerImageName);
+    }
+
+    @Override
+    public String getJdbcUrl() {
+        return super.getJdbcUrl() + "?useSSL=false&allowPublicKeyRetrieval=true";
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySqlDataSourceFactory.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySqlDataSourceFactory.java
new file mode 100644
index 0000000..3d1833f
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/MySqlDataSourceFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import com.mysql.cj.jdbc.MysqlDataSource;
+import org.testcontainers.containers.MySQLContainer;
+import org.testcontainers.delegate.DatabaseDelegate;
+import org.testcontainers.jdbc.JdbcDatabaseDelegate;
+
+import javax.annotation.PostConstruct;
+import javax.script.ScriptException;
+import javax.sql.DataSource;
+import java.sql.SQLException;
+
+public abstract class MySqlDataSourceFactory extends TestDataSourceFactory {
+
+    protected abstract MySQLContainer mysqlContainer();
+
+    @Override
+    protected DataSource createDataSource() {
+        final MySQLContainer container = mysqlContainer();
+        final MysqlDataSource dataSource = new MysqlDataSource();
+        dataSource.setUrl(container.getJdbcUrl());
+        dataSource.setUser(container.getUsername());
+        dataSource.setPassword(container.getPassword());
+        dataSource.setDatabaseName(container.getDatabaseName());
+        return dataSource;
+    }
+
+    @PostConstruct
+    public void initDatabase() throws SQLException, ScriptException {
+        DatabaseDelegate databaseDelegate = new JdbcDatabaseDelegate(mysqlContainer(), "");
+        databaseDelegate.execute("DROP DATABASE test; CREATE DATABASE test;", "", 0, false, true);
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/PostgresDataSourceFactory.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/PostgresDataSourceFactory.java
new file mode 100644
index 0000000..01fe05d
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/PostgresDataSourceFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.postgresql.ds.PGSimpleDataSource;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.delegate.DatabaseDelegate;
+import org.testcontainers.jdbc.JdbcDatabaseDelegate;
+
+import javax.annotation.PostConstruct;
+import javax.script.ScriptException;
+import javax.sql.DataSource;
+import java.sql.SQLException;
+
+@Configuration
+@Profile("postgres")
+public class PostgresDataSourceFactory extends TestDataSourceFactory {
+
+    private static final PostgreSQLContainer POSTGRESQL_CONTAINER = new PostgreSQLContainer();
+
+    static {
+        POSTGRESQL_CONTAINER.start();
+    }
+
+    @Override
+    protected DataSource createDataSource() {
+        PGSimpleDataSource dataSource = new PGSimpleDataSource();
+        dataSource.setUrl(POSTGRESQL_CONTAINER.getJdbcUrl());
+        dataSource.setUser(POSTGRESQL_CONTAINER.getUsername());
+        dataSource.setPassword(POSTGRESQL_CONTAINER.getPassword());
+        return dataSource;
+    }
+
+    @PostConstruct
+    public void initDatabase() throws SQLException, ScriptException {
+        DatabaseDelegate databaseDelegate = new JdbcDatabaseDelegate(POSTGRESQL_CONTAINER, "");
+        databaseDelegate.execute("DROP DATABASE test; CREATE DATABASE test;", "", 0, false, true);
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/TestDataSourceFactory.java b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/TestDataSourceFactory.java
new file mode 100644
index 0000000..0625f0f
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-test/src/main/java/org/apache/nifi/registry/db/TestDataSourceFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.db;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+import javax.sql.DataSource;
+
+@Configuration
+public abstract class TestDataSourceFactory {
+
+    @Bean
+    @Primary
+    DataSource dataSource() {
+        return createDataSource();
+    }
+
+    protected abstract DataSource createDataSource();
+
+}
diff --git a/nifi-registry-core/nifi-registry-web-api/pom.xml b/nifi-registry-core/nifi-registry-web-api/pom.xml
index 7451f32..2634620 100644
--- a/nifi-registry-core/nifi-registry-web-api/pom.xml
+++ b/nifi-registry-core/nifi-registry-web-api/pom.xml
@@ -389,14 +389,14 @@
         <!-- Test Dependencies -->
         <dependency>
             <groupId>org.apache.nifi.registry</groupId>
-            <artifactId>nifi-registry-client</artifactId>
+            <artifactId>nifi-registry-test</artifactId>
             <version>0.4.0-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-test</artifactId>
-            <version>${spring.boot.version}</version>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-client</artifactId>
+            <version>0.4.0-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/NiFiRegistryTestApiApplication.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/NiFiRegistryTestApiApplication.java
index 04514dd..ac56d1b 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/NiFiRegistryTestApiApplication.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/NiFiRegistryTestApiApplication.java
@@ -22,6 +22,8 @@
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.FilterType;
 
+import java.util.TimeZone;
+
 @SpringBootApplication
 @ComponentScan(
         excludeFilters = {
@@ -37,4 +39,9 @@
         })
 public class NiFiRegistryTestApiApplication extends SpringBootServletInitializer {
 
+        // Since H2 uses the JVM's timezone, setting UTC here ensures that the JVM has a consistent timezone set
+        // before the H2 DB is created, regardless of platform (i.e. local build vs Travis)
+        static {
+                TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+        }
 }
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/BucketsIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/BucketsIT.java
index 99c04c7..7edd73e 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/BucketsIT.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/BucketsIT.java
@@ -19,6 +19,7 @@
 import org.apache.nifi.registry.bucket.Bucket;
 import org.junit.Test;
 import org.skyscreamer.jsonassert.JSONAssert;
+import org.springframework.test.annotation.IfProfileValue;
 import org.springframework.test.context.jdbc.Sql;
 
 import javax.ws.rs.client.Entity;
@@ -49,7 +50,10 @@
         assertEquals(0, buckets.length);
     }
 
+    // NOTE: The tests that seed the DB directly from SQL end up with different results for the timestamp depending on
+    // which DB is used, so for now these types of tests only run against H2.
     @Test
+    @IfProfileValue(name="current.database.is.h2", value="true")
     @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = {"classpath:db/clearDB.sql", "classpath:db/BucketsIT.sql"})
     public void testGetBuckets() throws Exception {
 
@@ -58,19 +62,19 @@
         String expected = "[" +
                 "{\"identifier\":\"1\"," +
                 "\"name\":\"Bucket 1\"," +
-                "\"createdTimestamp\":1505091060000," +
+                "\"createdTimestamp\":1505134260000," +
                 "\"description\":\"This is test bucket 1\"," +
                 "\"permissions\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
                 "\"link\":{\"params\":{\"rel\":\"self\"},\"href\":\"buckets/1\"}}," +
                 "{\"identifier\":\"2\"," +
                 "\"name\":\"Bucket 2\"," +
-                "\"createdTimestamp\":1505091120000," +
+                "\"createdTimestamp\":1505134320000," +
                 "\"description\":\"This is test bucket 2\"," +
                 "\"permissions\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
                 "\"link\":{\"params\":{\"rel\":\"self\"},\"href\":\"buckets/2\"}}," +
                 "{\"identifier\":\"3\"," +
                 "\"name\":\"Bucket 3\"," +
-                "\"createdTimestamp\":1505091180000," +
+                "\"createdTimestamp\":1505134380000," +
                 "\"description\":\"This is test bucket 3\"," +
                 "\"permissions\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
                 "\"link\":{\"params\":{\"rel\":\"self\"},\"href\":\"buckets/3\"}}" +
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/FlowsIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/FlowsIT.java
index eea6969..5770a15 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/FlowsIT.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/FlowsIT.java
@@ -24,6 +24,7 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.skyscreamer.jsonassert.JSONAssert;
+import org.springframework.test.annotation.IfProfileValue;
 import org.springframework.test.context.jdbc.Sql;
 
 import javax.ws.rs.WebApplicationException;
@@ -62,7 +63,10 @@
         assertEquals(0, flows.length);
     }
 
+    // NOTE: The tests that seed the DB directly from SQL end up with different results for the timestamp depending on
+    // which DB is used, so for now these types of tests only run against H2.
     @Test
+    @IfProfileValue(name="current.database.is.h2", value="true")
     public void testGetFlows() throws Exception {
 
         // Given: a few buckets and flows have been populated in the DB (see FlowsIT.sql)
@@ -73,16 +77,16 @@
                 "\"name\":\"Flow 1\"," +
                 "\"description\":\"This is flow 1\"," +
                 "\"bucketIdentifier\":\"1\"," +
-                "\"createdTimestamp\":1505091360000," +
-                "\"modifiedTimestamp\":1505091360000," +
+                "\"createdTimestamp\":1505088000000," +
+                "\"modifiedTimestamp\":1505088000000," +
                 "\"type\":\"Flow\"," +
                 "\"permissions\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
                 "\"link\":{\"params\":{\"rel\":\"self\"},\"href\":\"buckets/1/flows/1\"}}," +
                 "{\"identifier\":\"2\",\"name\":\"Flow 2\"," +
                 "\"description\":\"This is flow 2\"," +
                 "\"bucketIdentifier\":\"1\"," +
-                "\"createdTimestamp\":1505091360000," +
-                "\"modifiedTimestamp\":1505091360000," +
+                "\"createdTimestamp\":1505088000000," +
+                "\"modifiedTimestamp\":1505088000000," +
                 "\"type\":\"Flow\"," +
                 "\"permissions\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," +
                 "\"versionCount\":0," +
@@ -265,7 +269,10 @@
         assertEquals(0, flowSnapshots.length);
     }
 
+    // NOTE: The tests that seed the DB directly from SQL end up with different results for the timestamp depending on
+    // which DB is used, so for now these types of tests only run against H2.
     @Test
+    @IfProfileValue(name="current.database.is.h2", value="true")
     public void testGetFlowVersions() throws Exception {
 
         // Given: a bucket "1" with flow "1" with existing snapshots has been populated in the DB (see FlowsIT.sql)
@@ -277,14 +284,14 @@
                 "{\"bucketIdentifier\":\"1\"," +
                 "\"flowIdentifier\":\"1\"," +
                 "\"version\":2," +
-                "\"timestamp\":1505091480000," +
+                "\"timestamp\":1505174400000," +
                 "\"author\" : \"user2\"," +
                 "\"comments\":\"This is flow 1 snapshot 2\"," +
                 "\"link\":{\"params\":{\"rel\":\"content\"},\"href\":\"buckets/1/flows/1/versions/2\"}}," +
                 "{\"bucketIdentifier\":\"1\"," +
                 "\"flowIdentifier\":\"1\"," +
                 "\"version\":1," +
-                "\"timestamp\":1505091420000," +
+                "\"timestamp\":1505088000000," +
                 "\"author\" : \"user1\"," +
                 "\"comments\":\"This is flow 1 snapshot 1\"," +
                 "\"link\":{\"params\":{\"rel\":\"content\"},\"href\":\"buckets/1/flows/1/versions/1\"}}" +
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/IntegrationTestBase.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/IntegrationTestBase.java
index 9f3d439..0fe0538 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/IntegrationTestBase.java
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/IntegrationTestBase.java
@@ -21,6 +21,7 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
 import org.apache.nifi.registry.client.NiFiRegistryClientConfig;
+import org.apache.nifi.registry.db.DatabaseProfileValueSource;
 import org.apache.nifi.registry.properties.NiFiRegistryProperties;
 import org.glassfish.jersey.client.ClientConfig;
 import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJaxbJsonProvider;
@@ -30,6 +31,7 @@
 import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
 import org.springframework.boot.web.server.LocalServerPort;
 import org.springframework.context.annotation.Bean;
+import org.springframework.test.annotation.ProfileValueSourceConfiguration;
 
 import javax.annotation.PostConstruct;
 import javax.net.ssl.HostnameVerifier;
@@ -44,6 +46,7 @@
 /**
  * A base class to simplify creating integration tests against an API application running with an embedded server and volatile DB.
  */
+@ProfileValueSourceConfiguration(DatabaseProfileValueSource.class)
 public abstract class IntegrationTestBase {
 
     private static final String CONTEXT_PATH = "/nifi-registry-api";
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/db/BucketsIT.sql b/nifi-registry-core/nifi-registry-web-api/src/test/resources/db/BucketsIT.sql
index 2d6bd23..72385df 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/resources/db/BucketsIT.sql
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/db/BucketsIT.sql
@@ -15,12 +15,12 @@
 
 -- test data for buckets
 
-insert into bucket (id, name, description, created)
-  values ('1', 'Bucket 1', 'This is test bucket 1', parsedatetime('2017-09-11 12:51:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
+insert into BUCKET (id, name, description, created)
+  values ('1', 'Bucket 1', 'This is test bucket 1', TIMESTAMP'2017-09-11 12:51:00.000');
 
-insert into bucket (id, name, description, created)
-  values ('2', 'Bucket 2', 'This is test bucket 2', parsedatetime('2017-09-11 12:52:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
+insert into BUCKET (id, name, description, created)
+  values ('2', 'Bucket 2', 'This is test bucket 2', TIMESTAMP'2017-09-11 12:52:00.000');
 
-insert into bucket (id, name, description, created)
-  values ('3', 'Bucket 3', 'This is test bucket 3', parsedatetime('2017-09-11 12:53:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
+insert into BUCKET (id, name, description, created)
+  values ('3', 'Bucket 3', 'This is test bucket 3', TIMESTAMP'2017-09-11 12:53:00.000');
 
diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/resources/db/FlowsIT.sql b/nifi-registry-core/nifi-registry-web-api/src/test/resources/db/FlowsIT.sql
index c36f987..1cb7c50 100644
--- a/nifi-registry-core/nifi-registry-web-api/src/test/resources/db/FlowsIT.sql
+++ b/nifi-registry-core/nifi-registry-web-api/src/test/resources/db/FlowsIT.sql
@@ -15,36 +15,36 @@
 
 -- test data for buckets
 
-insert into bucket (id, name, description, created)
-  values ('1', 'Bucket 1', 'This is test bucket 1', parsedatetime('2017-09-11 12:51:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
+insert into BUCKET (id, name, description, created)
+  values ('1', 'Bucket 1', 'This is test bucket 1', '2017-09-11');
 
-insert into bucket (id, name, description, created)
-  values ('2', 'Bucket 2', 'This is test bucket 2', parsedatetime('2017-09-11 12:52:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
+insert into BUCKET (id, name, description, created)
+  values ('2', 'Bucket 2', 'This is test bucket 2', '2017-09-12');
 
-insert into bucket (id, name, description, created)
-  values ('3', 'Bucket 3', 'This is test bucket 3', parsedatetime('2017-09-11 12:53:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'));
+insert into BUCKET (id, name, description, created)
+  values ('3', 'Bucket 3', 'This is test bucket 3', '2017-09-13');
 
 -- test data for flows
 
-insert into bucket_item (id, name, description, created, modified, item_type, bucket_id)
-  values ('1', 'Flow 1', 'This is flow 1', parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), 'FLOW', '1');
+insert into BUCKET_ITEM (id, name, description, created, modified, item_type, bucket_id)
+  values ('1', 'Flow 1', 'This is flow 1', '2017-09-11', '2017-09-11', 'FLOW', '1');
 
-insert into flow (id) values ('1');
+insert into FLOW (id) values ('1');
 
-insert into bucket_item (id, name, description, created, modified, item_type, bucket_id)
-  values ('2', 'Flow 2', 'This is flow 2', parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), 'FLOW', '1');
+insert into BUCKET_ITEM (id, name, description, created, modified, item_type, bucket_id)
+  values ('2', 'Flow 2', 'This is flow 2', '2017-09-11', '2017-09-11', 'FLOW', '1');
 
-insert into flow (id) values ('2');
+insert into FLOW (id) values ('2');
 
-insert into bucket_item (id, name, description, created, modified, item_type, bucket_id)
-  values ('3', 'Flow 3', 'This is flow 3', parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), parsedatetime('2017-09-11 12:56:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), 'FLOW', '2');
+insert into BUCKET_ITEM (id, name, description, created, modified, item_type, bucket_id)
+  values ('3', 'Flow 3', 'This is flow 3', '2017-09-11', '2017-09-11', 'FLOW', '2');
 
-insert into flow (id) values ('3');
+insert into FLOW (id) values ('3');
 
 -- test data for flow snapshots
 
-insert into flow_snapshot (flow_id, version, created, created_by, comments)
-  values ('1', 1, parsedatetime('2017-09-11 12:57:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), 'user1', 'This is flow 1 snapshot 1');
+insert into FLOW_SNAPSHOT (flow_id, version, created, created_by, comments)
+  values ('1', 1, '2017-09-11', 'user1', 'This is flow 1 snapshot 1');
 
-insert into flow_snapshot (flow_id, version, created, created_by, comments)
-  values ('1', 2, parsedatetime('2017-09-11 12:58:00.000 UTC', 'yyyy-MM-dd hh:mm:ss.SSS z'), 'user2', 'This is flow 1 snapshot 2');
+insert into FLOW_SNAPSHOT (flow_id, version, created, created_by, comments)
+  values ('1', 2, '2017-09-12', 'user2', 'This is flow 1 snapshot 2');
diff --git a/nifi-registry-core/pom.xml b/nifi-registry-core/pom.xml
index 47fe429..887a650 100644
--- a/nifi-registry-core/pom.xml
+++ b/nifi-registry-core/pom.xml
@@ -46,6 +46,7 @@
         <module>nifi-registry-client</module>
         <module>nifi-registry-docker</module>
         <module>nifi-registry-bundle-utils</module>
+        <module>nifi-registry-test</module>
     </modules>
 
     <dependencyManagement>
diff --git a/pom.xml b/pom.xml
index b26438a..aa4cd2b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -100,6 +100,7 @@
         <flyway.version>5.2.1</flyway.version>
         <flyway.tests.version>5.1.0</flyway.tests.version>
         <swagger.ui.version>3.12.0</swagger.ui.version>
+        <testcontainers.version>1.11.2</testcontainers.version>
     </properties>
 
     <repositories>