Пакет 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>
  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 (игнорировать ошибочные 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>
  1. Устанавливаем скрипты-разделители в @@.
  2. Устанавливаем разделитель для 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, но может применяться и в более широко.