Initializing a DataSource

org.springframework.jdbc.datasource.init 包提供了对现有数据源的初始化支持。嵌入式数据库支持为创建和初始化应用程序的数据源提供了一种选择。然而,有时你可能需要初始化一个在某个服务器上运行的实例。

通过使用 Spring XML 初始化数据库

Initializing a Database by Using Spring XML

如果你想初始化一个数据库,并且你可以提供对 DataSource Bean 的引用,你可以使用 spring-jdbc命名空间中的 initialize-database标签:

  1. <jdbc:initialize-database data-source="dataSource">
  2. <jdbc:script location="classpath:com/foo/sql/db-schema.sql"/>
  3. <jdbc:script location="classpath:com/foo/sql/db-test-data.sql"/>
  4. </jdbc:initialize-database>

上面的例子针对数据库运行了两个指定的脚本。第一个脚本创建一个 schema,第二个脚本用测试数据集填充表。脚本的位置也可以是带有通配符的模式,就像 Spring 中资源使用的 Ant 风格一样(例如 classpath*:/com/foo/**/sql/*-data.sql)。如果你使用模式,脚本将按照其 URL 或文件名的词法顺序运行。

数据库初始化器的默认行为是无条件地运行所提供的脚本。这不一定是你想要的—例如,如果你针对一个已经有测试数据的数据库运行脚本。遵循常见的模式(如前面所示),先创建表,然后插入数据,可以减少意外删除数据的可能性。如果表已经存在,第一步就会失败。

然而,为了获得对现有数据的创建和删除的更多控制,XML 命名空间提供了一些额外的选项。第一个是一个标志,用来切换初始化的开与关。你可以根据环境来设置(比如从系统属性或环境豆中提取一个布尔值)。下面的例子从一个系统属性中获取一个值:

  1. <jdbc:initialize-database data-source="dataSource"
  2. enabled="#{systemProperties.INITIALIZE_DATABASE}">
  3. <jdbc:script location="..."/>
  4. </jdbc:initialize-database>

从一个叫做 INITIALIZE_DATABASE 的系统属性中获取启用的值。

控制现有数据发生的第二个选项是对失败的容忍度更高。为此,你可以控制初始化器忽略它从脚本中运行的 SQL 中的某些错误的能力,如下面的例子所示:

  1. <jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
  2. <jdbc:script location="..."/>
  3. </jdbc:initialize-database>

在前面的例子中,我们是说,我们预计,有时候,脚本是针对一个空的数据库运行的,而在脚本中有一些 DROP 语句,因此会失败。因此,失败的 SQL DROP 语句将被忽略,但其他的失败将引起一个异常。如果你的 SQL 方言不支持 DROP ... IF EXISTS(或者类似的),但是你想在重新创建之前无条件地删除所有的测试数据,那么这就很有用。在这种情况下,第一个脚本通常是一组 DROP 语句,接着是一组 CREATE 语句。

ignore-failures 选项可以设置为 NONE(默认)、DROPS(忽略失败的丢弃)或者 ALL(忽略所有失败)。

每条语句应该用;隔开,如果脚本中根本没有 ;字符,则应该用一个新行隔开。你可以全局地控制,也可以逐个脚本地控制,如下例所示:

  1. <jdbc:initialize-database data-source="dataSource" separator="@@">
  2. <jdbc:script location="classpath:com/myapp/sql/db-schema.sql" separator=";"/>
  3. <jdbc:script location="classpath:com/myapp/sql/db-test-data-1.sql"/>
  4. <jdbc:script location="classpath:com/myapp/sql/db-test-data-2.sql"/>
  5. </jdbc:initialize-database>

在这个例子中,两个 test-data 脚本使用 @@作为语句分隔符,只有 db-schema.sql 使用 ;。这个配置指定了默认的分隔符是 @@,并且覆盖了 db-schema 脚本的默认值。

如果你需要比从 XML 命名空间得到更多的控制,你可以直接使用 DataSourceInitializer,并将其定义为应用程序中的一个组件(暴)。比如下面的例子

  1. @Bean
  2. public DataSourceInitializer dataSourceInitializer() {
  3. final DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
  4. // 这里是要注入一个数据源
  5. dataSourceInitializer.setDataSource(jdbcTemplate.getDataSource());
  6. final ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
  7. databasePopulator.addScript(new ClassPathResource("com/myapp/sql/db-schema.sql"));
  8. // 初始化的时候执行脚本
  9. dataSourceInitializer.setDatabasePopulator(databasePopulator);
  10. // 销毁阶段执行脚本
  11. dataSourceInitializer.setDatabaseCleaner(..);
  12. // 是否开启功能
  13. dataSourceInitializer.setEnabled(false);
  14. }

依赖于数据库的其他组件的初始化

Initialization of Other Components that Depend on the Database

一大类应用程序(那些在 Spring 上下文启动后才使用数据库的应用程序)可以使用数据库初始化器,而不需要进一步复杂化。如果你的应用程序不属于这些,你可能需要阅读本节的其余部分。

数据库初始化器依赖于一个 DataSource 实例,并运行其初始化回调中提供的脚本(类似于 XML Bean 定义中的 init-method、组件中的@PostConstruct方法,或者实现 InitializingBean的组件中的 afterPropertiesSet()方法)。如果其他 Bean 依赖于相同的数据源并在初始化回调中使用该数据源,可能会出现问题,因为数据还没有被初始化。一个常见的例子是一个缓存,它在应用启动时急于初始化并从数据库加载数据。

为了解决这个问题,你有两个选择:改变你的缓存初始化策略到较后阶段,或者确保数据库初始化器先被初始化。

如果应用程序是在你的控制之下,改变你的缓存初始化策略可能很容易,否则就不容易。一些关于如何实现的建议包括:

  • 让缓存在第一次使用时懒加载地初始化,这可以改善应用程序的启动时间。
  • 让你的缓存或初始化缓存的独立组件实现 Lifecycle 或 SmartLifecycle。当应用上下文启动时,你可以通过设置其 autoStartup 标志来自动启动 SmartLifecycle,你也可以通过在包围的上下文上调用 ConfigurableApplicationContext.start()来手动启动 Lifecycle。
  • 使用 Spring ApplicationEvent 或类似的自定义观察者机制来触发缓存的初始化。ContextRefreshedEvent 总是由上下文在准备使用时发布(在所有 Bean 被初始化后),所以这通常是一个有用的钩子(SmartLifecycle 默认就是这样工作的)。

确保数据库初始化器先被初始化,也可以很容易。关于如何实现这一点的一些建议包括:

  • 依靠 Spring BeanFactory 的默认行为,也就是按注册顺序初始化 Bean。你可以通过在 XML 配置中采用一套 <import/>元素的常见做法,对你的应用模块进行排序,并确保数据库和数据库初始化被列在前面,从而轻松地安排。
  • 将 DataSource 和使用它的业务组件分开,并通过将它们放在单独的 ApplicationContext 实例中来控制它们的启动顺序(例如,父上下文包含 DataSource,子上下文包含业务组件)。这种结构在 Spring Web 应用程序中很常见,但也可以更普遍地应用。