-
Notifications
You must be signed in to change notification settings - Fork 5
[WIP] Refactor jdbc migration and use service loader #151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 4 commits
13f69b0
4180c7b
4c0e7cf
33a356c
cb73cd9
58d893f
452a16e
ee3eeb6
f51f437
fbf4e5f
a405ce0
bc51a45
a66a434
16aee17
e60e475
4da3958
2ab8ef2
40d6a73
3de01dc
6d14cf8
4391093
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,24 +5,56 @@ | |
| /** | ||
| * Interface to be implemented by Jdbc Java Migrations. By default the migration | ||
| * version and description will be extracted from the class name. The checksum of this migration | ||
| * (for validation) will also be null, unless the migration also implements the | ||
| * MigrationChecksumProvider, in which case it can be returned programmatically. | ||
| * will be 0 by default | ||
| * <p> | ||
| * When the JdbcMigration implements ConfigurationAware, the master | ||
| * {@link MigrationConfig} is automatically injected upon creation, which is | ||
| * useful for getting placeholder and schema information. | ||
| * Note: Instances of JdbcMigration should be stateless, as the <code>migrate</code> method may | ||
| * run multiple times in multi-tenant setups. | ||
| * <p> | ||
| * There are several ways, how the JdbcMigrations are found. | ||
| * <ul> | ||
| * <li><b>ServiceLoader</b> this is the default behaviour<br> | ||
| * in this case add all your migration class names in META-INF/services/io.ebean.migration.JdbcMigration and/or in your | ||
| * module info.</li> | ||
| * <li>Using <b>jdbcMigrations</b> property<br> | ||
| * you can specify all migrations in the jdbcMigrations property</li> | ||
| * <li>Using own <b>jdbcMigrationFactory</b<br> | ||
| * you can write your own jdbcMigrationFactory that provides JdbcMigrations</li> | ||
| * </ul> | ||
| * | ||
| * @author Roland Praml, FOCONIS AG | ||
| */ | ||
| public interface JdbcMigration extends MigrationChecksumProvider { | ||
|
|
||
| /** | ||
| * Execute the migration using the connection. | ||
| * <p> | ||
| * Note: This API has changed with ebean-migration 13.12, as the initialization has changed. | ||
| * See https://github.com/ebean-orm/ebean-migration/issues/90 for migration advice. | ||
| */ | ||
| void migrate(Connection connection); | ||
| void migrate(Connection connection, MigrationConfig config); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This causes an API break. I could avoid this by "default" methods, but then I think no one reads the changelog and the java migrations will not work anymore.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. Yes I agree that people generally don't read release notes. |
||
|
|
||
| @Override | ||
| default int getChecksum() { | ||
| return 0; | ||
| } | ||
|
|
||
|
rPraml marked this conversation as resolved.
|
||
| /** | ||
| * Returns the name of the JdbcMigration. Note, the value must follow the naming conventions, for MigrationVersions. | ||
| * (example: <code>V1_2_1__comment</code>) | ||
| * <p> | ||
| * By default, the simple classname will be returned. | ||
| */ | ||
| default String getName() { | ||
| return getClass().getSimpleName(); | ||
| } | ||
|
|
||
| /** | ||
| * Determines, if this migration can be used for that migrationConfig. | ||
| * Here, platform checks or other things can be implemented. | ||
| * <p> | ||
| * By default, <code>true</code> is returned. | ||
| */ | ||
| default boolean matches(MigrationConfig config) { | ||
| return true; | ||
| } | ||
| } | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,16 @@ | ||
| package io.ebean.migration; | ||
|
|
||
| import java.lang.reflect.Constructor; | ||
| import java.sql.Connection; | ||
| import java.sql.DriverManager; | ||
| import java.sql.SQLException; | ||
| import java.util.ArrayList; | ||
| import java.util.HashSet; | ||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Properties; | ||
| import java.util.ServiceLoader; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
|
|
@@ -32,7 +37,7 @@ public class MigrationConfig { | |
| private boolean setCurrentSchema = true; | ||
| private boolean allowErrorInRepeatable; | ||
|
|
||
| private JdbcMigrationFactory jdbcMigrationFactory = new DefaultMigrationFactory(); | ||
| private Iterable<JdbcMigration> jdbcMigrationFactory = new DefaultMigrationFactory(); | ||
|
|
||
| /** | ||
| * Versions that we want to insert into migration history without actually running. | ||
|
|
@@ -418,17 +423,34 @@ public void setClassLoader(ClassLoader classLoader) { | |
| /** | ||
| * Return the jdbcMigrationFactory. | ||
| */ | ||
| public JdbcMigrationFactory getJdbcMigrationFactory() { | ||
| public Iterable<JdbcMigration> getJdbcMigrationFactory() { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a name mismatch in that So I get it but it seems off. Migrations need to be sorted into version order etc. This doesn't quite look right yet.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agree, I'll rename it to
I don't get you here. They are sorted later here https://github.com/ebean-orm/ebean-migration/pull/151/files#diff-10ad52c1950c7a3f96d7f7286cee908ff73e79324b8414c53ce3ad17204ca9e1R57 and they have to be sorted between the SQL migrations |
||
| return jdbcMigrationFactory; | ||
| } | ||
|
|
||
| /** | ||
| * Set the jdbcMigrationFactory. | ||
| * Set the jdbcMigrationFactory. If not set, the ServiceLoader is used. | ||
| * JdbcMigrationFactory can be either defined with the property <code>jdbcMigrationFactory</code> | ||
| * to a fully qualified class name implementing <code>Iterable<JdbcMigration></code> or by | ||
| * specifying a comma separated list of JdbcMigrations in the <code>jdbcMigrations</code> property. | ||
| * <p> | ||
| * Note: If you plan to run migrations in multi-tenant env in multiple threads, the provided factory | ||
| * must be thread safe! | ||
| */ | ||
| public void setJdbcMigrationFactory(JdbcMigrationFactory jdbcMigrationFactory) { | ||
| public void setJdbcMigrationFactory(Iterable<JdbcMigration> jdbcMigrationFactory) { | ||
| this.jdbcMigrationFactory = jdbcMigrationFactory; | ||
| } | ||
|
|
||
| /** | ||
| * Helper method to set migrations with the <code>jdbcMigrations</code> property. | ||
| */ | ||
| public void setJdbcMigrations(String... classNames) { | ||
| List<JdbcMigration> migrations = new ArrayList<>(classNames.length); | ||
| for (String className : classNames) { | ||
| migrations.add(newInstance(className.trim())); | ||
| } | ||
| setJdbcMigrationFactory(migrations); | ||
| } | ||
|
|
||
| /** | ||
| * Return the minVersion. | ||
| */ | ||
|
|
@@ -484,6 +506,16 @@ public void load(Properties props) { | |
| minVersion = property("minVersion", minVersion); | ||
| minVersionFailMessage = property("minVersionFailMessage", minVersionFailMessage); | ||
|
|
||
| String jdbcMigrationFactory = property("jdbcMigrationFactory"); | ||
| if (jdbcMigrationFactory != null) { | ||
| setJdbcMigrationFactory(newInstance(jdbcMigrationFactory)); | ||
| } | ||
|
|
||
| String jdbcMigrations = property("jdbcMigrations"); | ||
| if (jdbcMigrations != null) { | ||
| setJdbcMigrations(jdbcMigrations.split(",")); | ||
| } | ||
|
|
||
| String patchInsertOn = property("patchInsertOn"); | ||
| if (patchInsertOn != null) { | ||
| setPatchInsertOn(patchInsertOn); | ||
|
|
@@ -498,6 +530,15 @@ public void load(Properties props) { | |
| } | ||
| } | ||
|
|
||
| public <T> T newInstance(String className) { | ||
| try { | ||
| Class<?> cls = Class.forName(className, true, getClassLoader()); | ||
| return (T) cls.getDeclaredConstructor().newInstance(); | ||
| } catch (Exception e) { | ||
| throw new IllegalArgumentException("Error constructing " + className, e); | ||
| } | ||
| } | ||
|
|
||
| private boolean property(String key, boolean value) { | ||
| String val = property(key); | ||
| return val != null ? Boolean.parseBoolean(val) : value; | ||
|
|
@@ -611,25 +652,14 @@ public void setFastMode(boolean fastMode) { | |
| } | ||
|
|
||
| /** | ||
| * Default factory. Uses the migration's class loader and injects the config if necessary. | ||
| * | ||
| * @author Roland Praml, FOCONIS AG | ||
| * Default implementation for service-loader. Note: ServiceLoader is not thread safe, | ||
| * so it is better to retrun a new iterator each time. | ||
| */ | ||
| public class DefaultMigrationFactory implements JdbcMigrationFactory { | ||
| private class DefaultMigrationFactory implements Iterable<JdbcMigration> { | ||
|
|
||
| @Override | ||
| public JdbcMigration createInstance(String className) { | ||
| try { | ||
| Class<?> clazz = Class.forName(className, true, MigrationConfig.this.getClassLoader()); | ||
| JdbcMigration migration = (JdbcMigration) clazz.getDeclaredConstructor().newInstance(); | ||
| if (migration instanceof ConfigurationAware) { | ||
| ((ConfigurationAware) migration).setMigrationConfig(MigrationConfig.this); | ||
| } | ||
| return migration; | ||
| } catch (Exception e) { | ||
| throw new IllegalArgumentException(className + " is not a valid JdbcMigration", e); | ||
| } | ||
| public Iterator<JdbcMigration> iterator() { | ||
| return ServiceLoader.load(JdbcMigration.class, getClassLoader()).iterator(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about service loading JdbcMigration (each migration). I have in my mind that it would be better to add 1 level of indirection (a JdbcMigrationSupplier or JdbcMigrationFactory) ... then typically only that 1 supplier/factory would need to be registered for service loading rather than each JdbcMigration. Hmmm.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You have the first level of indirection in the migrationConfig. You can set your jdbcMigrationFactory/Collection by a property. So you can implement your own search strategy (we did this with our spring-classpath scan) I think, just Service-loading the factory is not a good idea, if you have multiple ebean-servers to different databases with different schemas or even different platforms. So the jdbcMigrations have to be configured the same way as the jdbcMigration-directory |
||
| } | ||
| } | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.