在 liquibase 开始工作之前,如何在 Postgres DB 中创建模式?

G.O*_*.O. 12 java postgresql spring liquibase

我有独立的应用程序。它在 java、spring-boot、postgres 上,并且有 liquibase。

我需要部署我的应用程序并且 liquibase 应该创建所有表等。但它应该在不公开的自定义模式中进行。liquibase 的所有服务表(databasechangelog 和 databasechangeloglock)也应该在自定义模式中。如何在 liquibase 开始工作之前在 DB 中创建我的模式?我必须在我的应用程序中在部署时、在配置中或类似内容中执行此操作。无需对数据库进行任何手动干预。

应用程序属性:

spring.datasource.jndi-name=java:/PostgresDS
spring.jpa.properties.hibernate.default_schema=my_schema
spring.jpa.show-sql = false
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.datasource.continue-on-error=true
spring.datasource.sql-script-encoding=UTF-8

liquibase.change-log = classpath:liquibase/changelog-master.yaml
liquibase.default-schema = my_schema
Run Code Online (Sandbox Code Playgroud)

更新:

当 liquibase 启动时,它会创建两张表 databasechangelogs 和一张表。之后,liquibase 开始工作。但是我想要 liquibase liquibase.default-schema = my_schema,但是当 liquibase 开始工作时它不存在并且它是一个错误:异常是 liquibase.exception.LockException: liquibase.exception.DatabaseException: ERROR: schema "my_schema" does not exist

我希望 liquibase 在自定义架构中工作,而不是在公共场合:

liquibase.default-schema = my_schema
Run Code Online (Sandbox Code Playgroud)

但是在 liquibase 可以做到之前,必须创建模式。Liquibase 无法执行此操作,因为它尚未启动,并且启动时需要架构。恶性循环。

Pav*_* D. 16

为了解决这个问题,我们需要运行一条 SQL 语句,在 Spring Boot 初始化期间创建 schema,此时DataSourcebean 已初始化,以便可以在 Liquibase 运行之前轻松获取数据库连接。

默认情况下,Spring Boot 通过创建一个InitializingBean命名的SpringLiquibase. 这发生在LiquibaseAutoConfiguration.

知道了这一点,我们可以使用AbstractDependsOnBeanFactoryPostProcessor配置SpringLiquibase来依赖于我们的自定义模式创建bean(SchemaInitBean在下面的示例中),它依赖于DataSource. 这安排了正确的执行顺序。

我的application.properties

db.schema=my_schema
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.open-in-view=false
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.properties.hibernate.default_schema=${db.schema}
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.liquibase.enabled=true
spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.xml
spring.liquibase.defaultSchema=${db.schema}
Run Code Online (Sandbox Code Playgroud)

将下面的类添加@Configuration到项目中,例如将其放入组件扫描处理的包中。

@Slf4j
@Configuration
@ConditionalOnClass({ SpringLiquibase.class, DatabaseChange.class })
@ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled", matchIfMissing = true)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
@Import({SchemaInit.SpringLiquibaseDependsOnPostProcessor.class})
public class SchemaInit {

    @Component
    @ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled", matchIfMissing = true)
    public static class SchemaInitBean implements InitializingBean {

        private final DataSource dataSource;
        private final String schemaName;

        @Autowired
        public SchemaInitBean(DataSource dataSource, @Value("${db.schema}") String schemaName) {
            this.dataSource = dataSource;
            this.schemaName = schemaName;
        }

        @Override
        public void afterPropertiesSet() {
            try (Connection conn = dataSource.getConnection();
                 Statement statement = conn.createStatement()) {
                log.info("Going to create DB schema '{}' if not exists.", schemaName);
                statement.execute("create schema if not exists " + schemaName);
            } catch (SQLException e) {
                throw new RuntimeException("Failed to create schema '" + schemaName + "'", e);
            }
        }
    }


    @ConditionalOnBean(SchemaInitBean.class)
    static class SpringLiquibaseDependsOnPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor {

        SpringLiquibaseDependsOnPostProcessor() {
            // Configure the 3rd party SpringLiquibase bean to depend on our SchemaInitBean
            super(SpringLiquibase.class, SchemaInitBean.class);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案不需要像 Spring Boot Pre-Liquibase 这样的外部库,并且不受data.sql/schema.sql支持限制的影响。我找到这个解决方案的主要动机是我要求架构名称必须是可配置的属性。将所有内容放在一个类中并使用普通 JDBC 是为了简洁起见。


G.O*_*.O. 9

我用我的 application.properties 找到了一个解决方案。

org.springframework.jdbc.datasource.init.ScriptUtils 在 liquibase 之前工作过。

日志:

14:11:14,760 INFO  [org.springframework.jdbc.datasource.init.ScriptUtils] 
(ServerService Thread Pool -- 300) Executing SQL script from URL 
[vfs:/content/app.war/WEB-INF/classes/schema.sql]

14:11:14,761 INFO  [org.springframework.jdbc.datasource.init.ScriptUtils] 
(ServerService Thread Pool -- 300) Executed SQL script from URL 
[vfs:/content/app.war/WEB-INF/classes/schema.sql] in 1 ms.

14:11:14,912 ERROR [stderr] (ServerService Thread Pool -- 300) INFO 9/27/18 
2:11 PM: liquibase: Successfully acquired change log lock

14:11:15,292 ERROR [stderr] (ServerService Thread Pool -- 300) INFO 9/27/18 
2:11 PM: liquibase: Reading from my_schema.databasechangelog

14:11:15,320 ERROR [stderr] (ServerService Thread Pool -- 300) INFO 9/27/18 
2:11 PM: liquibase: Successfully released change log lock
Run Code Online (Sandbox Code Playgroud)

我只是将schema.sqlwithCREATE SCHEMA IF NOT EXISTS my_schema;放入资源目录并且一切正常。

感谢大家的帮助。

更新:它适用于 Spring boot 1.X。如果您使用的是 Spring Boot 2,您应该在属性文件中启用 schema.sql,使用spring.datasource.initialization-mode=always. Spring Boot 中的更多信息- 加载初始数据

更新 2:在 Spring Boot 2.5.2(也可能在早期版本中)中,此解决方案现在不起作用,正如@peterh 在评论中所写。悲伤但真实。我尝试这个解决方案的最后一个版本是 Spring Boot 2.0.9 在文档中Spring Boot说它是从 Spring Boot 2.5.x 重新设计的

更新 3:为什么他们会取消此功能的一些信息-> https://github.com/spring-projects/spring-boot/issues/22741

  • 这是好东西。不幸的是,Spring Boot 2.x 文档说“如果您使用的是高级数据库迁移工具,例如 Flyway 或 Liquibase,则应该单独使用它们来创建和初始化模式。使用基本的 `schema.sql` 和 `data不建议将 .sql` 脚本与 Flyway 或 Liquibase 一起使用,并且将在未来版本中删除支持。” [参考](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-initialize-a-database-using-spring-jdbc)。所以是的,它现在有效......。 (2认同)
  • @peterh是的,你是对的,从一开始它就是一个解决方法,因为 liquibase 在初始化之前需要模式。我希望他们(lb 维护者)能够采取正确的方式,并在编写数据库变更日志之前添加一些内容来创建初始模式 (2认同)

小智 6

基于Pavel D.答案的简单解决方案。无需liquibase也可使用

@Slf4j
@Component
public class SchemaConfig implements BeanPostProcessor {

    @Value("${db.schema}")
    private String schemaName;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (!StringUtils.isEmpty(schemaName) && bean instanceof DataSource) {
            DataSource dataSource = (DataSource) bean;
            try (Connection conn = dataSource.getConnection();
                 Statement statement = conn.createStatement()) {
                log.info("Going to create DB schema '{}' if not exists.", schemaName);
                statement.execute("create schema if not exists " + schemaName);
            } catch (SQLException e) {
                throw new RuntimeException("Failed to create schema '" + schemaName + "'", e);
            }
        }
        return bean;
    }
}
Run Code Online (Sandbox Code Playgroud)


lbr*_*uun 5

您可以使用Spring Boot Pre-Liquibase 模块来实现此目的。这正是它的目的。它在执行 Liquibase 本身之前执行一些 SQL。Pre-Liquibase 在 Spring Boot AutoConfigure 链中设置自身,以保证它始终在 Liquibase之前执行。

步骤1

将以下 Starter 添加到您的项目中:

<dependency>
    <groupId>net.lbruun.springboot</groupId>
    <artifactId>preliquibase-spring-boot-starter</artifactId>
    <version>  ---latest-version---  </version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

第2步

添加一个 SQL 文件,src/main/resources/preliquibase其名称postgresql.sql和内容如下:

CREATE SCHEMA IF NOT EXISTS ${spring.liquibase.default-schema};
Run Code Online (Sandbox Code Playgroud)

语法${}表示占位符变量。Pre-Liquibase 将从 Spring 环境中的属性解析它。

步骤3

像这样设置应用程序属性:

spring.liquibase.default-schema=${my.db.schemaname}
spring.jpa.properties.hibernate.default_schema=${my.db.schemaname}
Run Code Online (Sandbox Code Playgroud)

现在 - 在这个例子中 - 唯一需要决定的是my.db.schemaname值来自哪里。那是你的选择。该示例项目主张它应该来自操作系统环境变量,特别是如果您要部署到云中。

最后的话

警告:Pre-Liquibase 可能过于灵活,因为它允许执行任何SQL 代码。不要试图将原本属于 Liquibase ChangeSet 的内容放入 Pre-Liquibase 文件中。老实说,我能想到的 Pre-Liquibase 的唯一用途是设置一个数据库“主目录”(即模式或目录),其中 Liquibase 数据库对象可以驻留在其中,以便可以通过模式或目录分隔同一应用程序的实例驻留在同一数据库服务器上。

(披露:我是 Pre-Liquibase 模块的作者)

  • @达维奥。我听起来很奇怪。该库仅在启动期间处于活动状态。它不会干扰或改变任何其他 Spring bean 或类似的东西。 (4认同)