Initializing a DataSource

`org.springframework.jdbc.datasource.init`包为初始化现有的`DataSource`提供了支持。嵌入式数据库支持为应用程序创建和初始化`DataSource`提供了一个选项。但是,您有时可能需要初始化在服务器上某个位置运行的实例。

Initializing a Database by Using Spring XML

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

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

前一个示例会针对数据库运行指定的两个脚本。第一个脚本会创建一个架构,而第二个脚本会使用测试数据集填充表。脚本位置还可以是模式,其中使用 Spring 中用于资源的 Ant 常用样式中的通配符(例如,classpath*:/com/foo/*/sql/-data.sql)。如果您使用一个模式,则会按其 URL 或文件名的词法顺序来运行脚本。

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

但是,为了对现有数据的创建和删除获得更多控制,XML 命名空间提供了一些额外的选项。第一个是开启和关闭初始化的标志。您可以根据环境设置它(例如,从系统属性或环境 bean 中获取布尔值)。以下示例从系统属性获取一个值:

<jdbc:initialize-database data-source="dataSource"
	enabled="#{systemProperties.INITIALIZE_DATABASE}"> 1
	<jdbc:script location="..."/>
</jdbc:initialize-database>
1 从名为 INITIALIZE_DATABASE 的系统属性中获取 enabled 的值。

控制对现有数据执行操作的第二个选项是对失败更宽容。为此,您可以控制初始化器忽略脚本中运行的 SQL 中的某些错误的能力,如下例所示:

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

在上一个示例中,我们说我们期望有时在空数据库上运行脚本,并且该脚本中有一些`DROP`语句,因此会出现失败。因此,会忽略失败的 SQL DROP`语句,但其他失败会引发异常。如果您的 SQL 方言不支持`DROP …​ IF EXISTS(或类似方言),但您想要在重新创建所有它之前无条件删除所有测试数据,则这将非常有用。在该情况下,第一个脚本通常是一组`DROP`语句,然后是一组`CREATE`语句。

ignore-failures`选项可以设置为`NONE(默认值)、DROPS(忽略失败的数据库删除)或`ALL`(忽略所有失败)。

如果脚本中根本没有`;`字符,则每个语句都应该用`;`或新行分开。您可以全局或逐个脚本控制它,如下所示:

<jdbc:initialize-database data-source="dataSource" separator="@@"> 1
	<jdbc:script location="classpath:com/myapp/sql/db-schema.sql" separator=";"/> 2
	<jdbc:script location="classpath:com/myapp/sql/db-test-data-1.sql"/>
	<jdbc:script location="classpath:com/myapp/sql/db-test-data-2.sql"/>
</jdbc:initialize-database>
1 将分隔符脚本设置为 @@
2 db-schema.sql 的分隔符设置为 ;

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

如果你需要比 XML 命名空间更多的控制,你可以直接使用 DataSourceInitializer,并将其定义为应用程序中的组件。

Initialization of Other Components that Depend on the Database

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

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

要解决此问题,你有两个选项:将缓存初始化策略更改为后面的阶段,或者确保数据库初始化器首先初始化。

如果你控制应用程序并且没有其他方式,则更改缓存初始化策略可能会很容易。以下列出了如何实现此目的的一些建议:

  • 第一次使用时延迟初始化缓存,这可以缩短应用程序启动时间。

  • 让您的缓存或初始化缓存的单独组件实现 LifecycleSmartLifecycle。当应用程序上下文启动时,可以通过设置其 autoStartup 标记自动启动 SmartLifecycle,并且您可以通过调用外围上下文的 ConfigurableApplicationContext.start() 手动启动 Lifecycle

  • 使用 Spring ApplicationEvent 或类似的自定义观察者机制来触发缓存初始化。当可以使用(在所有 Bean 初始化后)上下文时, ContextRefreshedEvent 始终由上下文发布,因此它经常是一个有用的挂钩(这是 SmartLifecycle 默认工作方式)。

确保数据库初始化器首先初始化也很容易。以下列出了有关如何实现此目的的一些建议:

  • 依赖于 Spring BeanFactory 的默认行为,即 Bean 以注册顺序初始化。您可以通过采用 XML 配置中的一组 &lt;import/&gt; 元素的常用实践(对应用程序模块进行排序,确保数据库和数据库初始化首先列出)来轻松安排 Bean 的顺序。

  • 分离 DataSource 和使用它的业务组件,通过将它们放入单独的 ApplicationContext 实例(例如,父上下文包含 DataSource,子上下文包含业务组件)来控制它们的启动顺序。这种结构在 Spring Web 应用程序中很常见,但可以更普遍地应用。