En la
parte anterior, descubrimos las características básicas de
Liquibase y escribimos un ejemplo básico de una aplicación de
arranque Spring que usa Liquibase para inicializar la base de datos. El código completo de la aplicación base se puede ver
aquí en GitHub . En este artículo hablaremos sobre
liquibase-maven-plugin y las características adicionales que nos brinda para versionar la estructura de la base de datos. Comencemos con cómo crear automáticamente scripts usando
la función de comparación .
Supongamos que necesitamos hacer algunos cambios en la estructura de nuestra base de datos. Por ejemplo, queremos que el
correo electrónico no sea
nulo . Por supuesto, para un cambio tan pequeño, puede corregir manualmente el código y las secuencias de comandos, pero ¿qué pasa si hay más cambios? En este caso, la capacidad de comparar la base de datos integrada en Liquibase nos ayudará. Una característica interesante es que puede comparar no solo dos bases de datos, sino también una base de datos con un conjunto de entidades JPA en nuestra aplicación. ¡Eso es exactamente lo que haremos ahora!
Crear un script con cambios usando liquibase-diff
En la sección de
complementos del archivo
pom.xml , agregamos este diseño bastante complicado. Esto es
liquibase-maven-plugin , al que se conecta una dependencia para analizar
entidades de hibernación y trabajar con archivos YAML. El complemento nos ayudará a generar automáticamente
scripts de liquibase comparando estructuras en dos bases de datos o incluso comparando la estructura de datos en una base de datos y un conjunto de entidades hiberante en nuestra aplicación (se
agrega liquibase-hibernate5 para esto).
<plugin> <groupId>org.liquibase</groupId> <artifactId>liquibase-maven-plugin</artifactId> <version>3.8.1</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.2.0.RELEASE</version> </dependency> </dependencies> </plugin>
Presta atención a la configuración de
user.name . Es opcional, pero sin ella Liquibase indicará en los scripts creados el nombre del usuario actual del sistema operativo bajo el cual se inicia el complemento.
La configuración del complemento se puede escribir directamente en pom.xml o pasar como parámetros de línea de comando al llamar a maven, pero prefiero la opción con un archivo
liquibase-maven-plugin.properties separado. Su contenido se verá más o menos así.
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
Vale la pena prestar atención a los parámetros
url y
referenceUrl . El script que creará liquibase después de la comparación será la diferencia entre la base que usa el enlace referenceUrl y la base que usa el enlace
url . Si luego ejecuta este script en la base utilizando el enlace
url , se convertirá en el mismo que se encuentra en el enlace referenceUrl. Se debe prestar especial atención al enlace referenceUrl. Como puede ver, no se refiere a la base de datos, sino al paquete de nuestra aplicación en el que se ubican las clases de entidad. Gracias a esto, ahora podemos encontrar un script que agregará a la base de datos los cambios que se hicieron en el código.
Ahora necesitamos configurar el complemento de recursos maven para reemplazar los marcadores de posición en el archivo de configuración, como
@project.basedir@
y
@timestamp@
. Para hacer esto, agregue la sección de
recursos del siguiente formulario a la sección de
compilación <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>
Por cierto, Spring boot cambia el formato estándar para marcadores de posición poblados usando el
complemento maven-resource de
${smth}
a
@smth@
. El hecho es que los marcadores de posición del
@smth@
en Spring Boot se utilizan para sustituir las variables de entorno y los parámetros de la aplicación Spring Boot.
Además, cambiamos ligeramente la sección de
propiedades en
pom.xml para asignar el valor a la variable de
marca de tiempo en el formato que necesitamos. Por desgracia, el formato estándar puede contener caracteres que están prohibidos en los nombres de archivo para algunos sistemas operativos.
<properties> <java.version>1.8</java.version> <timestamp>${maven.build.timestamp}</timestamp> <maven.build.timestamp.format>yyyyMMdd-HHmmssSSS</maven.build.timestamp.format> </properties>
Ahora cambiemos el campo de correo electrónico en la clase Usuario
@Column(name = "email", nullable = false) private String email;
Finalmente, ejecute el comando de compilación usando
liquibase-maven-plugin para comparar.
mvn clean install liquibase:diff -DskipTests=true
En este caso, necesitamos reconstruir completamente el proyecto, porque el complemento (liquibase: diff) usará fuentes no compiladas, sino archivos de clase de entidad compilados de la carpeta de
destino para el análisis.
Si todo se hace correctamente, luego de la ejecución exitosa del comando en la carpeta
resources / db / changelog , verá un archivo con el nombre
db.changelog-20190723-100748666.yaml . Debido al hecho de que usamos la fecha y hora actuales en el nombre del archivo, con cada lanzamiento tendremos un nuevo archivo, lo cual es bastante conveniente. Si ya ha creado una base de datos con la estructura de tabla correspondiente a la lección anterior, el contenido del archivo debería ser así.
databaseChangeLog: - changeSet: id: 1563876485764-1 author: your_liquibase_username (generated) changes: - addNotNullConstraint: columnDataType: varchar(255) columnName: email tableName: users
Como puede ver, este script realiza exactamente el cambio que se realizó en el código. Como ejercicio, recomendaría que ejecute este script en una base de datos vacía y mire el resultado.
Luego, simplemente podemos copiar el changeSet de este archivo a
db.changelog-master.yaml o podemos conectar este archivo con una instrucción
- include: file: db.changelog-20190723-100748666.yaml relativeToChangelogFile: true
También en este archivo debe especificar
logicalFilePath: db/changelog/db.changelog-20190723-100748666.yaml
, similar a cómo se hizo en
db.changelog-master.yaml .
Esto ayudará a hacer frente a algunos problemas que son posibles al usar el bean
liquibase incorporado y el plugin liquibase-maven-plugin en la aplicación. Después de eso, reinicie la aplicación o ejecute el comando:
mvn liquibase:update
Intentemos hacer un cambio más complejo en el código. Por ejemplo, agregue una tabla de roles que tendrá una relación de muchos a muchos con la tabla de usuario.
@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;
Y en la tabla Usuarios agregamos
@ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private Set<Role> roles;
Después de comenzar la comparación, obtenemos un archivo con los siguientes contenidos
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
También podemos agregar fácilmente este archivo a scripts de trabajo.
Revertir cambios
Ahora veamos cómo deshacer los cambios realizados. Por alguna razón, los identificadores que especificamos en changeSets no se pueden usar para revertirlos. Hay tres opciones, especificar un punto de reversión
- a través del número de conjuntos de cambios contando desde el actual
- después de la fecha del cambio
- a través de la etiqueta (establecida usando changeSet de un tipo especial)
La etiqueta se establece de la siguiente manera.
- changeSet: id: some_uniqui_id author: liquibase_user_name changes: - tagDatabase: tag: db_tag
Bueno, los comandos para las tres formas enumeradas para hacer rollback
mvn liquibase:rollback -Dliquibase.rollbackTag=db_tag mvn liquibase:rollback -Dliquibase.rollbackCount=1 mvn liquibase:rollback "-Dliquibase.rollbackDate=Jun 03, 2017"
Y finalmente, algunos materiales adicionales.
- Código para este artículo
- Sobre la reversión en Liquibase
- Sobre la migración con Liquibase
- Liquibase en github
- Muy buen artículo sobre varios enfoques para versionar una base de datos
Por supuesto, estaré muy contento de cualquier comentario, adiciones, aclaraciones, etc.