在 DropWizard 应用程序中初始化嵌入式数据库的正确位置在哪里?

Where is the correct place to initialize an embedded database in a DropWizard application?

我正在构建一个基于 DropWizard 的应用程序,它将有一个嵌入式 Derby 数据库。

Dropwizard 框架中的哪个位置是测试数据库是否存在以及是否创建数据库的合适位置。

现在我正在 dropwizard-db 模块提供的 .yml 文件中的 DataSourceFactory 中配置数据库,直到 run() 才可用方法被调用。

我在这个应用中也使用了 Guice,所以涉及 Guice 的解决方案也将被接受。

是否有更早的更合适的地方来测试和创建数据库?

根据要求,我将提供我的解决方案。背景故事,我正在使用 guicey (https://github.com/xvik/dropwizard-guicey),在我看来这是一个很棒的框架。我用它来与 guice 集成,但是我希望大多数实现都是相似的并且可以被采用。除此之外,我还使用 liquibase 进行数据库检查和一致性。

首先,在初始化期间,我正在创建一个包来为我进行验证。这个包是一个 guicey 概念。它会在 guice 初始化期间自动为 运行。这个包看起来像这样:

/**
 * Verifying all changelog files separately before application startup.
 * 
 * Will log roll forward and roll back SQL if needed 
 * 
 * @author artur
 *
 */
public class DBChangelogVerifier extends ComparableGuiceyBundle {

    private static final String ID = "BUNDLEID";

    private static final Logger log = Logger.getLogger(DBChangelogVerifier.class);

    private List<LiquibaseConfiguration> configs = new ArrayList<>();

    public void addConfig(LiquibaseConfiguration configuration) {
        this.configs.add(configuration);
    }


    /**
     * Attempts to verify all changelog definitions with the provided datasource
     * @param ds
     */
    public void verify(DataSource ds) {
        boolean throwException = false;
        Contexts contexts = new Contexts("");
        for(LiquibaseConfiguration c : configs) {
            try(Connection con = ds.getConnection()) {
                    Database db = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(con));
                    db.setDatabaseChangeLogLockTableName(c.changeLogLockTableName());
                    db.setDatabaseChangeLogTableName(c.changeLogTableName());
                    Liquibase liquibase = new ShureviewNonCreationLiquibase(c.liquibaseResource(), new ClassLoaderResourceAccessor(), db);
                    liquibase.getLog();
                    liquibase.validate();
                    List<ChangeSet> listUnrunChangeSets = liquibase.listUnrunChangeSets(contexts, new LabelExpression());

                    if(!listUnrunChangeSets.isEmpty()) {
                        StringWriter writer = new StringWriter();
                        liquibase.update(contexts, writer);
                        liquibase.futureRollbackSQL(writer);
                        log.warn(writer.toString());
                        throwException = true;
                    }
            } catch (SQLException | LiquibaseException e) {
                throw new RuntimeException("Failed to verify database.", e);
            }
        }

        if(throwException){
            throw new RuntimeException("Unrun changesets in chengelog.");
        }
    }

    /**
     * Using init to process and validate to avoid starting the application in case of errors. 
     */
    @Override
    public void initialize(GuiceyBootstrap bootstrap) {
        Configuration configuration = bootstrap.configuration();
        if(configuration instanceof DatasourceConfiguration ) {
            DatasourceConfiguration dsConf = (DatasourceConfiguration) configuration;
            ManagedDataSource ds = dsConf.getDatasourceFactory().build(bootstrap.environment().metrics(), "MyDataSource");
            verify(ds);
        }
    }

    @Override
    public String getId() {
        return ID;
    }

}

请注意,ComparableGuiceBundle 是我添加的接口,因此我可以在捆绑包及其初始化函数中进行排序。

guicey 将自动初始化此包,并调用 init 方法,为我提供数据源。在 init(同一个线程)中,我正在调用验证。这意味着,如果验证失败,我的应用程序启动失败,它将拒绝完成启动。

在我的启动代码中,我只是将这个包添加到 Guicey 配置中,这样 Guice 就可以知道它:

// add all bundles to the bundles variable including the Liquibase bundle. 
// registers guice with dropwizard
        bootstrap.addBundle(GuiceBundle.<EngineConfigurationImpl>builder()
                .enableAutoConfig("my.package")
                .searchCommands(true)    
                .bundles(bundles.toArray( new GuiceyBundle[0]))
                .modules(getConfigurationModule(), new CoreModule())
                                   .build()
        );

这就是我需要做的。 Guicey 会处理剩下的事情。在应用程序启动期间,它将初始化传递给它的所有包。由于具有可比性,验证我的数据库的包是第一个,将首先执行。只有当该包成功启动时,其他包才会启动。

对于液碱部分:

public void verify(DataSource ds) {
        boolean throwException = false;
        Contexts contexts = new Contexts("");
        for(LiquibaseConfiguration c : configs) {
            try(Connection con = ds.getConnection()) {
                    Database db = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(con));
                    db.setDatabaseChangeLogLockTableName(c.changeLogLockTableName());
                    db.setDatabaseChangeLogTableName(c.changeLogTableName());
                    Liquibase liquibase = new ShureviewNonCreationLiquibase(c.liquibaseResource(), new ClassLoaderResourceAccessor(), db);
                    liquibase.getLog();
                    liquibase.validate();
                    List<ChangeSet> listUnrunChangeSets = liquibase.listUnrunChangeSets(contexts, new LabelExpression());

                    if(!listUnrunChangeSets.isEmpty()) {
                        StringWriter writer = new StringWriter();
                        liquibase.update(contexts, writer);
                        liquibase.futureRollbackSQL(writer);
                        log.warn(writer.toString());
                        throwException = true;
                    }
            } catch (SQLException | LiquibaseException e) {
                throw new RuntimeException("Failed to verify database.", e);
            }
        }

        if(throwException){
            throw new RuntimeException("Unrun changesets in chengelog.");
        }
    }

正如您从我的设置中看到的那样,我可以有多个可以检查的更新日志配置。在我的启动代码中,我查找它们并将它们添加到包中。

Liquibase 将为您选择正确的数据库。如果没有可用的数据库,它将出错。如果连接未建立,它将出错。

如果找到未运行的变更集,它将打印出前滚和回滚 SQL。如果 md5sum 不正确,它会打印出来。在任何情况下,如果数据库与变更集不一致,它将拒绝启动。

现在就我而言,我不想让 liquibase 创建任何东西。这是一个纯粹的验证过程。但是 liquibase 确实为您提供 运行 所有变更集、创建表等选项。您可以在文档中阅读相关内容。这相当简单。

这种方法几乎将 liquibase 与正常启动集成在一起,而不是使用带有 dropwizard 的数据库命令来手动执行它们。

希望对您有所帮助,如果您有任何问题,请告诉我。

亚瑟