Пакет org.springframework.jdbc.datasource.init
предоставляет поддержку для инициализации существующего DataSource
. Средства поддержки встроенных баз данных предоставляют один вариант создания и инициализации DataSource
для приложения. Однако иногда может понадобиться инициализировать экземпляр, который выполняется на каком-либо сервере.
Инициализация базы данных с помощью Spring XML
Если нужно инициализировать базу данных и есть возможность указать ссылку на бин DataSource
, то можно использовать тег 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>
Предыдущий пример выполняет два указанных скрипта для базы данных. Первый скрипт создает схему, а второй заполняет таблицы контрольным набором данных. Местоположение скриптов также может являться шаблоном с подстановочными знаками в обычном стиле Ant, используемом для ресурсов в Spring (например, classpath*:/com/foo/**/sql/*-data.sql
). Если вы используете шаблон, то скрипты запускаются в лексическом порядке их URL-адреса или имени файла.
Логика работы инициализатора базы данных по умолчанию заключается в безусловном выполнении указанных скриптов. Это не всегда то, что требуется – например, если скрипты запускаются в отношении базы данных, в которой уже есть контрольные данные. Вероятность случайного удаления данных снижается, если следовать общепринятой схеме (показанной ранее): сначала создаются таблицы, а затем вставляются данные. Первый шаг завершится ошибкой, если таблицы уже существуют.
Однако для получения большего контроля над созданием и удалением существующих данных пространство имен XML предоставляет несколько дополнительных возможностей. Первая – это флаг для включения и выключения инициализации. Можно установить его в соответствии с окружением (например, извлечь булево значение из свойств системы или из бина окружения). В следующем примере значение получено из системного свойства:
<jdbc:initialize-database data-source="dataSource"
enabled="#{systemProperties.INITIALIZE_DATABASE}">
<jdbc:script location="..."/>
</jdbc:initialize-database>
- Получаем значение для
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
(игнорировать ошибочные DROP
стейтменты) или ALL
(игнорировать все сбои).
Каждая инструкция должна быть разделена символом ;
или новой строкой, если символ ;
вообще отсутствует в скрипте. Можно управлять этим глобально или по скриптам, как показано в следующем примере:
<jdbc:initialize-database data-source="dataSource" separator="@@">
<jdbc:script location="classpath:com/myapp/sql/db-schema.sql" separator=";"/>
<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
и выполняет скрипты, предоставленные в обратном вызове инициализации (аналогично init-method
в XML-определении бина, методу с аннотацией @PostConstruct
в компоненте или методу afterPropertiesSet()
в компоненте, реализующем InitializingBean
). Если другие бины зависят от того же источника данных и используют источник данных в обратном вызове инициализации, может возникнуть проблема, поскольку данные еще не были инициализированы. Распространенным примером является кэш, который инициализируется без задержек и загружает данные из базы данных при запуске приложения.
Чтобы обойти эту проблему, существует два способа: изменить стратегию инициализации кэша на более поздний этап или убедиться, что инициализатор базы данных инициализируется первым.
Изменить стратегию инициализации кэша может быть легко, если приложение находится под вашим управлением, и никак иначе. Некоторые предложения о том, как это осуществить:
-
Сделайте так, чтобы кэш инициализировался отложено при первом использовании, что улучшит время запуска приложения.
-
Пусть ваш кэш или отдельный компонент, инициализирующий кэш, реализует
Lifecycle
илиSmartLifecycle
. Когда контекст приложения запускается, можно автоматически запуститьSmartLifecycle
, установив его флагautoStartup
, и можно вручную запуститьLifecycle
, вызвавConfigurableApplicationContext.start()
для объемлющего контекста. -
Используйте
ApplicationEvent
из Spring или аналогичный кастомный механизм наблюдателя для запуска инициализации кэша.ContextRefreshedEvent
всегда публикуется контекстом, когда он готов к использованию (после инициализации всех бинов), поэтому это зачастую бывает полезным методом (именно так по умолчанию работаетSmartLifecycle
).
Убедиться в том, что инициализатор базы данных инициализируется первым, также несложно. Некоторые соображения о том, как это осуществить:
-
Прибегнете к логике работы
BeanFactory
из Spring по умолчанию, которая заключается в том, что бины инициализируются в порядке регистрации. Это легко можно организовать, приняв общепринятую практику использования набора элементов<import/>
в XML-конфигурации, которые упорядочивают модули вашего приложения и обеспечивают, чтобы база данных и инициализация базы данных были перечислены первыми. -
Отделите
DataSource
и использующие его бизнес-компоненты и контролируйте порядок их запуска, поместив их в отдельные экземплярыApplicationContext
(например, родительский контекст содержитDataSource
, а дочерний – бизнес-компоненты). Такая структура распространена в веб-приложениях Spring, но может применяться и в более широко.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ