Предположим, что нам понадобилось внести какие-либо изменения в структуру нашей БД. Например, мы хотим, чтобы email не мог быть null. Безусловно, для такого маленького изменения можно было бы подкорректировать код и скрипты вручную, но как быть если изменений будет больше? В этом случае к нам на помощь придет встроенная в Liquibase возможность сравнения БД. Интересной её особенностью является то, что сравнивать можно не только две базы данных, но и базу данных с набором JPA сущностей в нашем приложении. Именно так мы сейчас и поступим!
Создаем скрипт с изменениями при помощи liquibase-diff
В раздел plugins файла pom.xml добавляем вот такую довольно сложную конструкцию. Это liquibase-maven-plugin, к которому подключена зависимость для анализ hibernate-сущностей и работы с файлами в формате YAML. Плагин поможет нам автоматически генерировать liquibase-скрипты через сравнение структур в двух БД или даже через сравнение структуры данных в БД и набора hiberante-сущностей в нашем приложении (именно для этого добавлен liquibase-hibernate5).
<plugin> <groupId>org.liquibase</groupId> <artifactId>liquibase-maven-plugin</artifactId> <version>3.5.5</version> <configuration> <propertyFile>${project.build.outputDirectory}/liquibase-maven-plugin.properties</propertyFile> <systemProperties> <user.name>your_liquibase_username</user.name> </systemProperties> <logging>info</logging> </configuration> <dependencies> <dependency> <groupId>org.liquibase.ext</groupId> <artifactId>liquibase-hibernate5</artifactId> <version>3.6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.1.5.RELEASE</version> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.24.0-GA</version> </dependency> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.12</version> </dependency> </dependencies> </plugin>
Настройки для плагина можно прописать непосредственно в pom.xml или передать как параметры командной строки при вызове maven, но мне больше нравится вариант с отдельным файлом liquibase-maven-plugin.properties. Его содержимое будет выглядеть как-то так.
changeLogFile= @project.basedir@/src/main/resources/db/changelog/db.changelog-master.yaml url= jdbc:mysql://localhost:3306/geek_db?createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC username= dbusername password= dbpassword driver= com.mysql.cj.jdbc.Driver referenceUrl= hibernate:spring:ru.usharik.liquibase.demo.persist.model?dialect=org.hibernate.dialect.MySQLDialect diffChangeLogFile= @project.basedir@/src/main/resources/db/changelog/db.changelog-@timestamp@.yaml ignoreClasspathPrefix= true
Здесь стоит обратить внимание на параметры url и referenceUrl. Скрипт, который создаст liquibase после сравнения будет представлять из себя разницу между базой по ссылке referenceUrlи базой по ссылке url. Если потом этот скрипт запустить на базе по ссылке url, то она станет такой же как и та, которая находится по ссылке referenceUrl. Особое внимание стоит обратить на ссылку referenceUrl. Как видите она ссылается не на БД, а на пакет нашего приложения в котором находятся классы сущностей. Благодаря этому мы сейчас сможем найти скрипт, который добавит в БД изменения, которые были сделаны в коде.
Теперь нам нужно настроить maven-resource-plugin для замены плейсхолдеров в фале настроек, таких как @project.basedir@ и @timestamp@. Для этого добавим в раздел build раздел resources следующего вида
<resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>*.properties</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <filtering>false</filtering> <includes> <include>**/*.*</include> </includes> </resource> </resources>
К слову, по какой-то причине Spring boot изменяет стандартный формат для плейсхолдеров с ${smth} на @smth@.
Также немного меняем раздел properties в pom.xml, чтобы присвоить значение переменной timestamp в нужном нам формате. Увы, стандартный формат может содержать символы, которые запрещены в именах файлов для некоторых ОС.
<properties> <java.version>1.8</java.version> <timestamp>${maven.build.timestamp}</timestamp> <maven.build.timestamp.format>yyyyMMdd-HHmmssSSS</maven.build.timestamp.format> </properties>
Теперь давайте изменим поле email в классе User
@Column(name = "email", nullable = false) private String email;
И наконец запустим команду сборки, использующую liquibase-maven-plugin для сравнения.
mvn clean install liquibase:diff -DskipTests=true
В данном случае нам нужно полностью пересобрать проект, потому что плагин (liquibase:diff) будет использовать для анализа не исходники, а скомпилированные файлы классов сущностей из папке target.
Если все сделано правильно, то после успешного выполнения команды в папке resources/db/changelog у вас появится файл с именем вида db.changelog-20190723-100748666.yaml. Благодаря тому, что мы используем текущую дату и время в имени файла, при каждом запуске у нас будет появляться новый файл, что довольно удобно. Если у вас уже создана БД со структурой таблиц, соответствующей прошлому уроку, то содержимое файла должно быть таким.
databaseChangeLog: - changeSet: id: 1563876485764-1 author: your_liquibase_username (generated) changes: - addNotNullConstraint: columnDataType: varchar(255) columnName: email tableName: users
Как видите, этот скрипт вносит именно то изменение, которое и было сделано в коде. В качестве упражнения, рекомендовал бы вам запустить данный скрипт против пустой базы данных и посмотреть на результат.
Далее мы можем просто скопировать changeSet из этого файла в db.changelog-master.yaml или можем подключить данный файл в него инструкцией
- include: file: db.changelog-20190723-100748666.yaml relativeToChangelogFile: true
Также в этом файле нужно указать logicalFilePath: db/changelog/db.changelog-20190723-100748666.yaml по аналогии с тем, как это сделано в db.changelog-master.yaml.
Это позволит справится с некоторыми проблемами, которые возможны при совместном использовании встроенного в приложение liquibase бина и liquibase-maven-plugin. После этого перезапустите приложение или выполните команду:
mvn liquibase:update
Давайте попробуем внести какое-то более сложное изменение в код. Например добавим таблицу ролей у которой будет связь типа многие ко многим с таблицей пользователей.
@Entity @Table(name = "roles") public class Role implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @Column(name = "name", unique = true, nullable = false) private String name; @ManyToMany(mappedBy = "roles") private Set<User> users; // далее конструкторы, геттеры, сеттеры }
А в таблицу Users добавляем
@ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private Set<Role> roles;
После запуска сравнения получим файл со следующим содержимым
databaseChangeLog: - changeSet: id: 1563877765929-1 author: your_liquibase_username (generated) changes: - createTable: columns: - column: autoIncrement: true constraints: primaryKey: true primaryKeyName: rolesPK name: id type: BIGINT - column: constraints: nullable: false name: name type: VARCHAR(255) tableName: roles - changeSet: id: 1563877765929-2 author: your_liquibase_username (generated) changes: - createTable: columns: - column: constraints: nullable: false name: user_id type: BIGINT - column: constraints: nullable: false name: role_id type: BIGINT tableName: users_roles - changeSet: id: 1563877765929-3 author: your_liquibase_username (generated) changes: - addPrimaryKey: columnNames: user_id, role_id tableName: users_roles - changeSet: id: 1563877765929-4 author: your_liquibase_username (generated) changes: - addUniqueConstraint: columnNames: name constraintName: UC_ROLESNAME_COL tableName: roles - changeSet: id: 1563877765929-5 author: your_liquibase_username (generated) changes: - addForeignKeyConstraint: baseColumnNames: user_id baseTableName: users_roles constraintName: FK2o0jvgh89lemvvo17cbqvdxaa deferrable: false initiallyDeferred: false referencedColumnNames: id referencedTableName: users - changeSet: id: 1563877765929-6 author: your_liquibase_username (generated) changes: - addForeignKeyConstraint: baseColumnNames: role_id baseTableName: users_roles constraintName: FKj6m8fwv7oqv74fcehir1a9ffy deferrable: false initiallyDeferred: false referencedColumnNames: id referencedTableName: roles
Этот файл мы тоже можем легко добавить к рабочим скриптам.
Откат изменений
Теперь давайте посмотрим, как откатить внесенные изменения. По какой-то причине те идентификаторы которые мы указывали в changeSet-ах не могут быть использованы для отката к ним. Есть три варианта, указать точку отката
- через количество changeSet-ов считая от текущего
- через дату выполнения изменений
- через tag (задается при помощи changeSet-а специального вида)
Тег устанавливается следующим образом.
- changeSet: id: some_uniqui_id author: liquibase_user_name changes: - tagDatabase: tag: db_tag
Ну и команды для трех перечисленных способов сделать rollback
mvn liquibase:rollback -Dliquibase.rollbackTag=db_tag mvn liquibase:rollback -Dliquibase.rollbackCount=1 mvn liquibase:rollback "-Dliquibase.rollbackDate=Jun 03, 2017"
Ну и напоследок некоторые дополнительные материалы
Разумеется, буду очень рад любым замечаниям, дополнениям, уточнениям и т.д.
ссылка на оригинал статьи https://habr.com/ru/post/460907/
Добавить комментарий