|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
初始化一个 DataSource
org.springframework.jdbc.datasource.init 包提供了对初始化现有 DataSource 的支持。嵌入式数据库支持为应用程序创建和初始化 DataSource 提供了一种选择。然而,有时你可能需要初始化某个服务器上运行的实例。
使用 Spring XML 初始化数据库
如果你想初始化一个数据库,并且能够提供一个对 DataSource bean 的引用,你可以使用 initialize-database 命名空间中的 spring-jdbc 标签:
<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 | 从名为 enabled 的系统属性中获取 INITIALIZE_DATABASE 的值。 |
控制现有数据处理方式的第二种选项是对失败采取更宽容的态度。为此,您可以控制初始化器在执行脚本中的 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,并将其定义为应用程序中的一个组件。
依赖于数据库的其他组件的初始化
一大类应用程序(那些在 Spring 上下文启动之后才使用数据库的应用)可以直接使用数据库初始化器,而不会产生其他复杂问题。如果你的应用程序不属于这类,那么你可能需要阅读本节的其余部分。
数据库初始化器依赖于一个 DataSource 实例,并在其初始化回调中执行所提供的脚本(类似于 XML Bean 定义中的 init-method、组件中的 @PostConstruct 方法,或实现了 afterPropertiesSet() 接口的组件中的 InitializingBean 方法)。如果其他 Bean 依赖于同一个数据源,并在自身的初始化回调中使用该数据源,就可能会出现问题,因为此时数据尚未完成初始化。一个常见的例子是缓存组件,它在应用启动时立即初始化并从数据库加载数据。
要解决这个问题,你有两个选项:将缓存初始化策略更改为稍后的阶段,或者确保数据库初始化器首先被初始化。
如果你能控制该应用程序,更改缓存初始化策略可能会很容易;否则则不然。 关于如何实现这一点的一些建议包括:
-
使缓存在首次使用时延迟初始化,从而提升应用程序的启动速度。
-
让你的缓存或负责初始化缓存的独立组件实现
Lifecycle或SmartLifecycle接口。当应用上下文启动时,你可以通过设置SmartLifecycle的autoStartup标志来自动启动它;也可以通过对包含该组件的上下文调用Lifecycle方法来手动启动一个ConfigurableApplicationContext.start()组件。 -
使用 Spring 的
ApplicationEvent或类似的自定义观察者机制来触发缓存的初始化。ContextRefreshedEvent在上下文准备就绪(所有 Bean 都已初始化完成)时总会被发布,因此这通常是一个很有用的钩子(SmartLifecycle默认就是通过这种方式工作的)。
确保数据库初始化器首先被初始化也可以很容易实现。以下是一些关于如何实现这一点的建议:
-
依赖 Spring
BeanFactory的默认行为,即 Bean 按照注册顺序进行初始化。你可以通过在 XML 配置中采用一组<import/>元素的常见做法来轻松实现这一点,这些元素用于对应用程序模块进行排序,并确保数据库及其初始化配置被列在最前面。 -
将
DataSource与其使用的业务组件分离,并通过将它们置于独立的ApplicationContext实例中(例如,父上下文包含DataSource,子上下文包含业务组件)来控制它们的启动顺序。这种结构在 Spring Web 应用程序中很常见,但也可以更广泛地应用。