tools repeatable mvn migrations instalar como java database postgresql jdbc flyway

java - repeatable - ¿Cómo crear una base de datos con flyway?



mysql flyway (4)

Pregunta: ¿Es posible crear una nueva base de datos en un script de migración y luego conectarse a ella? ¿Cómo?

Mi Escenario: Estoy tratando de usar el corredor aéreo en mi proyecto Java (aplicación RESTful usando Jersey2.4 + tomcat 7 + PostgreSQL 9.3.1 + EclipseLink) para administrar los cambios entre diferentes desarrolladores que usan git. Escribí mi guión de inicio y lo ejecuté con:

PGPASSWORD=''123456'' psql -U postgres -f migration/V1__initDB.sql

y funcionó bien. El problema es que no puedo crear una nueva base de datos con mis scripts. Cuando incluyo la siguiente línea en mi script:

CREATE DATABASE my_database OWNER postgres ENCODING ''UTF8'';

Me sale este error:

org.postgresql.util.PSQLException: ERROR: CREATE DATABASE cannot run inside a transaction block at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2157) at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1886) at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:255) at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:555) at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:403) at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:395) at com.googlecode.flyway.core.dbsupport.JdbcTemplate.executeStatement(JdbcTemplate.java:230) at com.googlecode.flyway.core.dbsupport.SqlScript.execute(SqlScript.java:89) at com.googlecode.flyway.core.resolver.sql.SqlMigrationExecutor.execute(SqlMigrationExecutor.java:72) at com.googlecode.flyway.core.command.DbMigrate$2.doInTransaction(DbMigrate.java:252) at com.googlecode.flyway.core.command.DbMigrate$2.doInTransaction(DbMigrate.java:250) at com.googlecode.flyway.core.util.jdbc.TransactionTemplate.execute(TransactionTemplate.java:56) at com.googlecode.flyway.core.command.DbMigrate.applyMigration(DbMigrate.java:250) at com.googlecode.flyway.core.command.DbMigrate.access$700(DbMigrate.java:47) at com.googlecode.flyway.core.command.DbMigrate$1.doInTransaction(DbMigrate.java:189) at com.googlecode.flyway.core.command.DbMigrate$1.doInTransaction(DbMigrate.java:138) at com.googlecode.flyway.core.util.jdbc.TransactionTemplate.execute(TransactionTemplate.java:56) at com.googlecode.flyway.core.command.DbMigrate.migrate(DbMigrate.java:137) at com.googlecode.flyway.core.Flyway$1.execute(Flyway.java:872) at com.googlecode.flyway.core.Flyway$1.execute(Flyway.java:819) at com.googlecode.flyway.core.Flyway.execute(Flyway.java:1200) at com.googlecode.flyway.core.Flyway.migrate(Flyway.java:819) at ir.chom.MyApp.<init>(MyApp.java:28) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:526) at org.glassfish.hk2.utilities.reflection.ReflectionHelper.makeMe(ReflectionHelper.java:1117) at org.jvnet.hk2.internal.Utilities.justCreate(Utilities.java:867) at org.jvnet.hk2.internal.ServiceLocatorImpl.create(ServiceLocatorImpl.java:814) at org.jvnet.hk2.internal.ServiceLocatorImpl.createAndInitialize(ServiceLocatorImpl.java:906) at org.jvnet.hk2.internal.ServiceLocatorImpl.createAndInitialize(ServiceLocatorImpl.java:898) at org.glassfish.jersey.server.ApplicationHandler.createApplication(ApplicationHandler.java:300) at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:279) at org.glassfish.jersey.servlet.WebComponent.<init>(WebComponent.java:302) at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:167) at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:349) at javax.servlet.GenericServlet.init(GenericServlet.java:160) at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1280) at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1091) at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:5176) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5460) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) at org.apache.catalina.core.StandardContext.reload(StandardContext.java:3954) at org.apache.catalina.loader.WebappLoader.backgroundProcess(WebappLoader.java:426) at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:1345) at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1530) at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1540) at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1540) at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1519) at java.lang.Thread.run(Thread.java:724)

Parece que este es un problema con JDBC que usa la opción autocommit . Esta opción se puede desactivar con algo como esto:

Connection connection = dataSource.getConnection(); Connection.setAutoCommit(false); // Disables auto-commit.

pero no sé cómo pasar esta opción a la conexión de vuelo. Además, si soluciono esto, creo que tendré problemas para pasar la contraseña a /c .


Aquí hay una solución que me funcionó (asumiendo el uso del complemento de Maven):

  1. Configura el plugin con dos ejecuciones. La primera ejecución crea la base de datos, si es necesario. La segunda ejecución migra una base de datos existente.

    <plugin> <groupId>org.flywaydb</groupId> <artifactId>flyway-maven-plugin</artifactId> <version>${flyway.version}</version> <executions> <execution> <id>create-db</id> <goals> <goal>migrate</goal> </goals> <configuration> <driver>org.postgresql.Driver</driver> <url>jdbc:postgresql://192.168.1.106/</url> <user>postgres</user> <password>password</password> <placeholders> <DATABASE.NAME>MyDatabase</DATABASE.NAME> </placeholders> <locations> <location>com/foo/bar/database/create</location> </locations> <callbacks> <callback>com.foo.bar.database.CreateDatabase</callback> </callbacks> </configuration> </execution> <execution> <id>migrate-db</id> <goals> <goal>migrate</goal> </goals> <configuration> <driver>org.postgresql.Driver</driver> <url>jdbc:postgresql://192.168.1.106/MyDatabase</url> <user>postgres</user> <password>password</password> <locations> <location>com/foo/bar/database/migrate</location> </locations> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>${postgresql.version}</version> </dependency> </dependencies> </plugin>

  2. Defina CreateDatabase de la siguiente manera:

    package com.foo.bar.database; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.regex.Pattern; import static org.bitbucket.cowwoc.requirements.core.Requirements.requireThat; import org.flywaydb.core.api.FlywayException; import org.flywaydb.core.api.callback.BaseFlywayCallback; /** * Creates the database, if it doesn''t already exist. * * @author Gili Tzabari */ public final class CreateDatabase extends BaseFlywayCallback { @Override public void beforeMigrate(Connection connection) { String databaseName = flywayConfiguration.getPlaceholders().get("DATABASE.NAME"); requireThat(databaseName, "${DATABASE.NAME}").isNotNull(); if (!Pattern.matches("^[a-zA-Z0-9_]+$", databaseName)) { // http://.com/q/43375955/14731 throw new IllegalArgumentException("databaseName may only contain alphanumeric characters./n" + "Actual: " + databaseName); } try (PreparedStatement databaseExists = connection.prepareStatement("SELECT 1 FROM pg_database WHERE datname = ?")) { databaseExists.setString(1, databaseName); try (ResultSet rs = databaseExists.executeQuery()) { if (!rs.next()) { try (Statement createDatabase = connection.createStatement()) { // Postgresql complains "CREATE DATABASE cannot run inside a transaction block" // http://.com/q/26482777/14731 boolean transactionsEnabled = !connection.getAutoCommit(); if (transactionsEnabled) connection.setAutoCommit(true); createDatabase.executeUpdate("CREATE DATABASE " + databaseName); if (transactionsEnabled) connection.setAutoCommit(false); } } } } catch (SQLException e) { throw new FlywayException(e); } } }


Flyway siempre opera dentro de la base de datos utilizada en la cadena de conexión jdbc.

Una vez conectados, todos los scripts se ejecutan dentro de una transacción. Como CREATE DATABASE no es compatible con las transacciones, no podrá lograr lo que desea.

Sin embargo, lo que puedes hacer es crear un esquema. Flyway incluso hará esto por usted, si lo apunta a uno que no existe.


No sé si esto es posible incluso en una ruta migratoria.

El objetivo de Flyway es conectarse a una base de datos ya existente (esté vacía o no). También sería una buena práctica mantener la creación de su base de datos separada de las migraciones de su base de datos.


Si tiene un comando de creación de base de datos de esquema en V1 de sus scripts, la ruta puede generarlo pero no la base de datos:

flyway -baselineOnMigrate=true -url=jdbc:mysql://localhost/ -schemas=test_db -user=root -password=root_pass -locations=filesystem:/path/to/scrips/ migrate

y similar a esto en el archivo de script:

DROP SCHEMA IF EXISTS `test_db` ; CREATE SCHEMA `test_db` COLLATE utf8_general_ci ;