Пакет 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, але може застосовуватися й у ширшому.