初始化一个 DataSource
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>
前面的示例针对数据库运行了两个指定的脚本。第一个脚本创建一个模式,第二个脚本使用测试数据集填充表。脚本的位置也可以是带有通配符的模式,使用 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>
从名为
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>
将分隔符脚本设置为
@@
。将
db-schema.sql
的分隔符设置为;
。
在此示例中,两个 test-data
脚本使用 @@
作为语句分隔符,只有 db-schema.sql
使用 ;
。此配置指定默认分隔符为 @@
,并为 db-schema
脚本覆盖该默认设置。
如果你需要比 XML 命名空间提供的更多的控制,你可以直接使用 DataSourceInitializer
并将其定义为应用程序中的一个组件。
初始化依赖于数据库的其他组件
一大类应用程序(那些在 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 应用程序中很常见,但可以更广泛地应用。