Пакет 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, но может применяться и в более широко.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ