初始化 DataSource
org.springframework.jdbc.datasource.init
包提供了初始化现有 DataSource
的支持。嵌入式数据库支持为应用程序创建和初始化 DataSource
提供了一种选项。然而,有时您可能需要初始化运行在某个服务器上的实例。
使用 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>
前面的示例对数据库运行了两个指定的脚本。第一个脚本创建 Schema,第二个用测试数据集填充表。脚本位置也可以是带有通配符的模式,使用 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
(忽略失败的 DROP 语句)或 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
并将其定义为应用程序中的一个组件。
依赖于数据库的其他组件的初始化
大多数应用程序(那些在 Spring 上下文启动后才使用数据库的应用程序)可以毫无额外复杂性地使用数据库初始化器。如果您的应用程序不是此类,您可能需要阅读本节的其余部分。
数据库初始化器依赖于 DataSource
实例,并在其初始化回调中(类似于 XML bean 定义中的 init-method
、组件中的 `@PostConstruct` 方法或实现 InitializingBean
的组件中的 afterPropertiesSet()
方法)运行提供的脚本。如果其他 Bean 依赖于同一个数据源并在初始化回调中使用该数据源,可能会出现问题,因为数据尚未初始化。一个常见的例子是急切初始化并在应用启动时从数据库加载数据的缓存。
为了解决这个问题,您有两个选项:将您的缓存初始化策略更改为更晚的阶段,或确保数据库初始化器首先被初始化。
如果应用程序在您的控制范围内,更改缓存初始化策略可能会很容易。实施此策略的一些建议包括
-
让缓存在使用时延迟初始化,这样可以提高应用程序启动时间。
-
让您的缓存或初始化缓存的独立组件实现
Lifecycle
或SmartLifecycle
。当应用程序上下文启动时,您可以通过设置SmartLifecycle
的autoStartup
标志自动启动它,也可以通过在封闭上下文上调用ConfigurableApplicationContext.start()
手动启动Lifecycle
。 -
使用 Spring
ApplicationEvent
或类似的自定义观察者机制来触发缓存初始化。ContextRefreshedEvent
总是在上下文准备就绪(所有 Bean 都初始化后)时发布,所以这通常是一个有用的钩子(这是SmartLifecycle
的默认工作方式)。
确保数据库初始化器首先被初始化也可能很容易。实施此策略的一些建议包括
-
依靠 Spring
BeanFactory
的默认行为,即 Bean 按照注册顺序初始化。您可以通过在 XML 配置中采用一组 `` 元素来安排应用程序模块的顺序,并确保数据库和数据库初始化首先列出,从而轻松实现这一点。 -
分离
DataSource
和使用它的业务组件,并通过将它们放在不同的ApplicationContext
实例中来控制它们的启动顺序(例如,父上下文包含DataSource
,子上下文包含业务组件)。这种结构在 Spring Web 应用中很常见,但也适用于更广泛的场景。