Целевая аудитория
Эта статья была написана для читателей, которые имеют опыт работы с Java
, Переход в спящий режим
и Пружинный ботинок
. Во всех примерах используется MySQL
но вы также можете использовать другие реляционные базы данных, которые вам удобны.
Вступление
Экосистема Java предоставляет вам множество инструментов для волшебного обновления схем ваших баз данных, но все ли эти инструменты достаточно надежны для использования с производственной базой данных?
В этой статье – первой в серии – мы сосредоточимся на лучших практиках отрасли и функции автоматической генерации схемы Hibernate
. Мы объясним, что мы узнали из него и где его можно использовать.
В следующей статье мы обсудим, как можно вносить изменения в схему базы данных с помощью инструмента миграции базы данных, такого как Liquibase
. Все примеры кода доступны в нашем специальном репозитории GitHub .
Установка
Давайте сначала создадим новую схему базы данных с именем адресная книга
с использованием клиента командной строки MySQL
:
>mysql -u santa -p Enter password: ****** mysql> CREATE DATABASE addressBook; Query OK, 1 row affected (0.12 sec)
Давайте теперь откроем наше Java-приложение , которое использует Spring Boot
и MySQL
. Конфигурации для MySQL
можно найти внутри application.yml |/:
spring: jpa: database: mysql hibernate: ddl-auto: update datasource: url: jdbc:mysql://localhost:3306/addressBook username: santa password: secret
Первые 3 строки объясняют, как подключиться к MySQL
. Наш пароль жестко закодирован для простоты, но в реальной жизни мы бы хранили его в секрете.
ddl-auto: update
показывает, что наша схема MySQL
должна обновляться при запуске приложения (будет обсуждаться в следующем параграфе).
Создание схемы базы данных с нуля
На данном этапе только что была создана схема нашей базы данных. В нашем приложении есть только один класс сущностей с именем User
.
@Entity @Data public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String firstName; private String lastName; private LocalDate dateOfBirth; }
Примечание: аннотация @Data берется из Lombok и автоматически генерирует наши методы получения/установки.
Как показано в предыдущем разделе, мы настроили автоматическое обновление схемы базы данных следующим образом:
spring.jpa.hibernate.ddl-auto: update
Давайте запустим наш JUnit
набор тестов:
mvn clean test
В журналах мы видим, что был выполнен следующий запрос к базе данных:
create table user ( id integer not null auto_increment, date_of_birth date, first_name varchar(255), last_name varchar(255), primary key (id)) engine=InnoDB
Как выполняются тесты с помощью Hibernate
?
При запуске Hibernate
анализирует все классы, которые были оформлены аннотацией @Entity
. Затем он сканирует класс User
и генерирует запрос на создание таблицы SQL
. Имя таблицы, имена столбцов, типы и т.д. Основаны на информации, содержащейся в пользовательском классе (имя класса, имена и типы атрибутов, аннотации и т.д.).
Запуск приложения еще раз
Создана схема базы данных AddressBook
, содержащая таблицу User
.
Какого поведения мы должны ожидать, когда запустим приложение еще раз?
Когда Hibernate снова запускает тесты, он сравнивает класс Пользователь
против таблицы пользователь
. Затем он видит, что класс и таблица синхронизированы и это не вносит никаких дальнейших изменений.
Какой SQL?
В то время как SQL выглядит одинаково при работе с различными поставщиками баз данных, не существует такого понятия, как полностью совместимый SQL . Существуют тонкие различия в том, как каждый SQL обрабатывает даты, конкатенацию строк и т.д. Hibernate элегантно абстрагирует эти различия как “диалекты”.
Внутри вашего pom.xml
мы настроили драйвер mysql
jdbc в качестве зависимости для MySQL 8. Пружинный Ботинок
затем предполагается, что мы используем значение по умолчанию MySQL 8
диалект и настраивает Гибернацию
соответственно, как показано в журналах запуска:
HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
Добавление изменений в существующую базу данных
Давайте теперь добавим Адрес
сущности для нашей модели.
@Data @Entity public class Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String streetAddress; private String zipCode; private String city; //... }
Мы также добавляем связь от User
к Адрес
следующим образом:
@Entity @Data public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String firstName; private String lastName; private LocalDate dateOfBirth; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "user_id", foreignKey = @ForeignKey(name="FK_USER_ID")) private List addressList = new ArrayList<>(); }
Когда приложение запускается (все еще в режиме автоматического обновления
), Hibernate создает таблицу address
следующим образом:
create table address ( id integer not null auto_increment, city varchar(255), street_address varchar(255), zip_code varchar(255), user_id integer, primary key (id) ) engine=InnoDB alter table address add constraint FK_USER_ID foreign key (user_id) references user (id)
Таблица address
и ее связь с user
были добавлены, как и ожидалось. В то время как Hibernate
‘s auto-update
большую часть времени работает нормально, он довольно волшебный и подвержен ошибкам. По нашему опыту, легко переименовать класс или поле, а затем забыть о том факте, что при следующем развертывании приложения будет сгенерирована новая таблица или столбец.
В следующем разделе мы обсудим рекомендации и меры предосторожности при внесении изменений в схему рабочей базы данных.
Автоматическое обновление схемы в рабочей среде?
В своей официальной документации команда Hibernate
рекомендует следующее:
Хотя автоматическое создание схемы очень полезно для целей тестирования и создания прототипов, в производственной среде гораздо более гибко управлять схемой с помощью сценариев инкрементной миграции.
Вот подход, который мы обычно используем:
База данных | H2 | MySQL | MySQL | MySQL |
Настройка автоматического обновления в режиме гибернации | создать-удалить | обновление | утверждать | утверждать |
Резервное копирование БД | никто | никто | mysqldump | mysqldump |
- Для модульных тестов мы используем
H2
. Вся база данных создается в памяти во время запуска и удаляется после выполнения всех тестов (create-drop
). - При запуске локального веб-приложения (на
localhost
) мы запускаемupdate
и копируем из журналов все сгенерированные сценарии обновления (например, для адресной таблицы в нашем примере). Мы будем повторно использовать эти скрипты для нашегопромежуточного
ипроизводственные
среды. - В
постановке
ипроизводственных
сред, мы используем следующую настройку:
spring.jpa.hibernate.ddl-auto=validate
Во время запуска Hibernate проверяет
s, что схема базы данных совместима с нашим сопоставлением JPA/Hibernate
. Если какой-либо класс или атрибут не сопоставлен должным образом, Hibernate
выдает исключение, и приложение не запускается. Мы пытаемся воспроизвести поведение, которое мы будем иметь в рабочей среде, поэтому мы обновляем нашу схему вручную, используя сценарии, собранные в нашей локальной среде разработки.
В постановке
и производство
, , мы всегда создаем резервные копии нашей базы данных и планируем процедуру восстановления. В MySQL это можно сделать с помощью команды mysqldump
Примечание: Вы можете видеть, что предлагаемые процессы одинаковы для staging
и производственные
среды. Нарушение промежуточной
среды нашего приложения не должно иметь большого значения. Однако это возможность выполнить пробный запуск, прежде чем обновлять схему нашей базы данных в рабочей среде.
Добавление конфликтующего изменения
Конфликтующее изменение
– это изменение , которое включает переименование таблицы или столбца . Давайте представим, например, что address
, который используется в нашей существующей схеме, недостаточно конкретен, и что мы хотели бы переименовать таблицу address
в почтовый адрес
. Давайте изменим название Адрес
класс следующим образом:
@Data @Entity public class PostalAddress { //... }
Функция гибернации автоматического обновления
плохо работает с конфликтующими изменениями. Если мы перезапустим наше приложение в режиме update
, оно создаст новую таблицу с именем postal_address
и по-прежнему сохраняет существующую таблицу address
.
Давайте отключим автоматическую схему update
и вместо этого используем validate
, как описано в предыдущем абзаце:
spring.jpa.hibernate.ddl-auto: validate
При запуске приложения Hibernate обнаружит, что наши классы не синхронизированы со схемой базы данных, и выдаст следующее исключение:
Caused by: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [postal_address] at org.hibernate.tool.schema.internal.AbstractSchemaValidator .validateTable(AbstractSchemaValidator.java:121)
Чтобы избежать этой проблемы, нам нужно остановить приложение и запустить приведенный ниже сценарий до развертывания новой версии приложения:
mysqldump --defaults-file="/var/.../extraparams.cnf" ... >mysql -u santa -p Enter password: ****** mysql> use addressBook; -- choose the database schema to be used mysql> RENAME TABLE address to postal_address;
Теперь мы изменили имя нашей таблицы и развернули обновленную версию приложения.
Вышесказанное предполагает, что мы можем перевести наше приложение в автономный режим на несколько минут. Чрезвычайно сложно внести конфликтующие изменения в базу данных, пока она работает в рабочей среде.
Вывод
Мы видели, что Hibernate
‘s auto-update
является отличным инструментом разработки и не должен использоваться в промежуточной и рабочей среде. В процессе подготовки и производства мы видели, что вы можете запускать свои sql-запросы вручную. В нашем последующем блоге (который будет опубликован к 1 марта 2020 года) мы обсудим, как использовать Liquibase в качестве инструмента миграции схемы базы данных для ваших промежуточных и производственных сред.
Спасибо, что прочитали наш блог!
Майкл Мертв. (спасибо моим коллегам Николасу Гиньяру, Лью Мин Шаню и многим другим за рецензирование этой статьи!).
Оригинал: “https://dev.to/onlinepajak/database-schema-changes-with-hibernate-and-spring-boot-3f5k”