【问题标题】:Where is the correct place to initialize an embedded database in a DropWizard application?在 DropWizard 应用程序中初始化嵌入式数据库的正确位置在哪里?
【发布时间】:2016-02-13 23:09:02
【问题描述】:

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

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

现在我在.yml 文件中的DataSourceFactory 中配置数据库,该文件由dropwizard-db 模块提供,在调用run() 方法之前不可用。

我也在这个应用程序中使用 Guice,因此涉及 Guice 的解决方案也将被接受。

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

【问题讨论】:

  • 你检查过dropwizard migrations吗?这是用于 DB 模式初始化和相关内容的 dropwizard way。它为 cmd 提供了一些命令,例如 java -jar your-app.jar db status yourconfig.yml
  • 如果您真的想自己动手,那么public void initialize(Bootstrap<AppConfiguration> bootstrap) 方法是一个很好的起点。检查dropwizard internalsdropwizard-hibernate 了解一些高级和低级详细信息。
  • 天哪,migrations 的东西是纯粹的 xml 地狱!
  • Liquibase 支持描述迁移的各种格式。他们支持:XML, YAML, JSON and SQL 格式。 Here 您可以找到示例如何配置 dropwizardLiquibase 以使用 SQL 格式化脚本进行 DB 迁移。还可以考虑检查Liquibase documentation
  • 我将它添加为初始化后首先运行的模块。我选择了一种更加程序化的方法,所以我自己实现了 liquibase 的东西。然后我的模块会收集变更日志并检查数据库是否已启动、变更日志是否已执行以及数据库是否与应用程序期望的一致。这些检查中的任何一个都失败了,应用程序没有完全启动。但是,我使用的是 guice,所以我在创建 Guice 上下文时对其进行了测试。

标签: java guice derby dropwizard


【解决方案1】:

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

首先,在初始化期间,我正在创建一个包来为我进行验证。这个捆绑包是一个简单的概念。它将在 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 方法,为我提供数据源。在初始化(同一个线程)中,我正在调用验证。这意味着,如果验证失败,我的应用程序启动失败,它将拒绝完成启动。

在我的启动代码中,我只是将这个捆绑包添加到 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 负责其余的工作。在应用程序启动期间,它将初始化传递给它的所有包。由于具有可比性,验证我的数据库的包是第一个,将首先执行。只有当该捆绑包成功启动时,其他捆绑包才会启动。

对于 liquibase 部分:

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 的数据库命令来手动执行它们。

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

阿图尔

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-10
    相关资源
    最近更新 更多