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