初始化一个 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中用于资源文件的引用(例如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(忽略失败的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或类似的自定义观察器机制来触发缓存初始化。当上下文准备就绪(所有bean都已初始化)时,总会发布ContextRefreshedEvent事件,因此这通常是一个有用的时机点(默认情况下,SmartLifecycle就是通过这种方式工作的)。
确保数据库初始化器首先被初始化也可以很简单。关于如何实现这一点的一些建议包括:
-
依赖于Spring
BeanFactory的默认行为,即bean是按照注册顺序进行初始化的。你可以通过在XML配置中采用一组<import/>元素来轻松地安排这一顺序,这些元素会定义应用程序模块的顺序,并确保数据库及其初始化操作被列在首位。 -
将
DataSource与使用它的业务组件分开,并通过将它们放在不同的ApplicationContext实例中来控制它们的启动顺序(例如,父上下文包含DataSource,子上下文包含业务组件)。这种结构在Spring Web应用程序中很常见,但也可以更普遍地应用。