Question: Est-il possible de créer une nouvelle base de données dans un script de migration puis de s'y connecter? Comment?
Mon scénario: J'essaie d'utiliser flyway dans mon Java (application RESTful utilisant Jersey2.4 + Tomcat 7 + PostgreSQL 9.3.1 + EclipseLink) pour gérer les changements entre les différents développeurs qui utilisent git. J'ai écrit mon script init et l'ai exécuté avec:
PGPASSWORD='123456' psql -U postgres -f migration/V1__initDB.sql
et cela a bien fonctionné. Le problème est que je ne peux pas créer de nouvelle base de données avec mes scripts. lorsque j'inclus la ligne suivante dans mon script:
CREATE DATABASE my_database OWNER postgres ENCODING 'UTF8';
Je reçois cette erreur:
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)
Il semble que ce soit un problème avec JDBC qui utilise l'option autocommit
. Cette option peut être désactivée avec quelque chose comme ceci:
Connection connection = dataSource.getConnection();
Connection.setAutoCommit(false); // Disables auto-commit.
mais je ne sais pas comment passer cette option à une connexion de voie de migration. De plus, si je résous ce problème, je pense que je vais avoir un problème avec le passage du mot de passe à \c
commande.
Flyway fonctionne toujours dans la base de données utilisée dans la chaîne de connexion jdbc.
Une fois connecté, tous les scripts s'exécutent dans une transaction. Comme CREATE DATABASE n'est pas pris en charge dans les transactions, vous ne pourrez pas accomplir ce que vous voulez.
Ce que vous pouvez cependant faire, c'est créer un schéma à la place. Flyway le fera même pour vous, si vous le pointez sur un point inexistant.
Je ne sais pas si c'est même possible de le faire en voie de migration.
Flyway est destiné à se connecter à une base de données déjà existante (qu'elle soit vide ou non). Il serait également judicieux de séparer la création de votre base de données de vos migrations de base de données.
Voici une solution de contournement qui a fonctionné pour moi (en supposant l'utilisation du plugin Maven):
Configurez le plugin avec deux exécutions. La première exécution crée la base de données. La deuxième exécution migre une base de données existante.
<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://database-server/</url>
<user>postgres</user>
<password>password</password>
<placeholders>
<DATABASE.NAME>MyDatabase</DATABASE.NAME>
</placeholders>
<locations>
<location>com/foo/bar/database/create</location>
</locations>
</configuration>
</execution>
<execution>
<id>migrate-db</id>
<goals>
<goal>migrate</goal>
</goals>
<configuration>
<driver>org.postgresql.Driver</driver>
<url>jdbc:postgresql://database-server/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>
Puis ajouter V1__Created_database.sql
à la com/foo/bar/database/create
répertoire. Ce fichier contient:
CREATE DATABASE ${DATABASE.NAME}