
This article explains in detail how to address database compatibility issues during deployment. We'll discuss what can happen to your production applications if you attempt to deploy without proper preparation. Then, we'll walk through the application lifecycle stages necessary for achieving zero downtime (Note: hereafter — zero downtime). The result of our operations will be applying the backwards-incompatible database change in a backwards-compatible way.
If you want to go through code examples from the article, you can find them at .
Introduction
Zero downtime deployment
What is this mystical zero downtime deployment? Можно сказать, это когда ваше приложение развернуто так, что вы можете успешно вводить новую версию приложения на продакшн, в то время как пользователь не замечает его недоступности. С точки зрения пользователя и компании, это наилучший из возможных сценариев деплоя, поскольку таким образом можно вводить новые функции и устранять ошибки без перебоев в работе.
How to achieve this? There are several methods, here is one of them:
- deploy version #1 of your service
- perform the database migration
- deploy version #2 of your service alongside version #1
- once you see that version #2 is working as expected, remove version #1
- done!
Easy, right? Unfortunately, it’s not that simple, and we will discuss this in detail later. Now, let’s check out another fairly common deployment process — blue-green deployment.
Have you ever heard of ? С Cloud Foundry это чрезвычайно легко сделать. Просто гляньте на , where we describe it in more detail. To summarize briefly, let’s recall how to do a blue-green deployment:
- ensure the operation of two copies of your production code (“blue” and “green”);
- direct all traffic to the blue environment, i.e., so the production URLs point there;
- deploy and test all application changes in the green environment;
- switch the URLs from the blue to the green environment
Blue-green deployment is an approach that allows you to easily introduce new features without worrying that production will break. This is due to the fact that even if something goes wrong, you can easily roll back to the previous environment by simply 'flipping a switch.'
Having read all of the above, you may ask: What does zero downtime have to do with blue-green deployment?
Well, they have quite a lot in common, as maintaining two copies of the same environment requires double the effort to manage them. That’s why some teams, as stated by , adhere to a variation of this approach:
Another option involves using the same database by creating blue-green switches for the web and domain layers. In this approach, databases often become a problem, especially when you need to change their schema to support a new version of the software.
And here we reach the main issue of this article. Database. Let's take another look at this phrase.
perform the database migration.
Now you should ask yourself the question — what if backward database changes are incompatible? Won't my first version of the application break? In fact, that's exactly what will happen...
Thus, even with the huge advantages of zero downtime / blue-green deployment, companies tend to follow the next safer process for deploying their applications:
- prepare a package with the new version of the application
- shut down the running application
- run the scripts for the database migration
- deploy and launch the new version of the application
In this article, we will detail how you can work with the database and code to take advantage of zero downtime deployment.
Database Issues
If you have a stateless application that does not store any data in the database, you can achieve zero downtime deployment right away. Unfortunately, most software needs to store data somewhere. That is why you should think twice before making any changes to the schema. Before we dive into the details of how to change the schema in a way that allows for deployment without downtime, let’s first focus on versioning schemes.
Versioning Scheme
In this article, we will use as a versioning tool (note: this refers to database migrations). Naturally, we will also write a Spring Boot application that has built-in support for Flyway and will perform the schema migration during application context setup. When using Flyway, you can store migration scripts in your project folders (by default in classpath:db/migration). Here you can see an example of such migration files
└── db
└── migration
├── V1__init.sql
├── V2__Add_surname.sql
├── V3__Final_migration.sql
└── V4__Remove_lastname.sqlIn this example, we see 4 migration scenarios that, if not previously performed, will execute one after the other when the application starts. Let's take a look at one of the files (V1__init.sql) as an example.
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');Everything speaks for itself: you can use SQL to define how your database should be altered. For more information about Spring Boot and Flyway, see .
By using a versioning tool with Spring Boot, you gain 2 major advantages:
- you separate database changes from code changes
- database migration occurs along with the rollout of your application, i.e. your deployment process becomes simpler
Troubleshooting Database Issues
In the next section of the article, we will focus on discussing two approaches to database changes.
- backward incompatibility
- backward compatibility
The first will be discussed as a warning that one should not perform zero downtime deployment without prior preparation… The second offers a solution for how to perform deployment without downtime while maintaining backward compatibility.
The project we will be working on will be a simple Spring Boot Flyway application that has Person with first_name and last_name in the database (Note: Person is a table, and first_name and last_name — are the fields in it). We want to rename last_name downward API support (simultaneously with this in surname.
Assumptions
Before we delve into details, a couple of assumptions regarding our applications must be stated. The main outcome we want to achieve will be a fairly simple process.
Note. Business PRO-TIP. Simplifying processes can save you a lot of money on support (the more people work in your company, the more money you can save)!
No need to roll back the database
This simplifies the deployment process (some database rollbacks are virtually impossible, such as rolling back deletions). We prefer to only roll back applications. Thus, even if you have different databases (like SQL and NoSQL), your deployment pipeline will look the same.
There should ALWAYS be an option to roll back the application to one previous version (no more)
Rollback should only be performed when necessary. If there is a bug in the current version that is difficult to fix, we should be able to revert to the last stable version. We assume that this last stable version is the previous one. Maintaining compatibility for code and database across more than one release would be extremely challenging and costly.
Note. For better readability, in this article we will change the major version of the application.
Step 1: Initial State
Application Version: 1.0.0
Database Version: v1
Comment
This will be the initial state of the application.
Database Changes
The database contains last_name.
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');Code Changes
The application saves Person data in last_name:
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.flyway;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastname) {
this.lastName = lastname;
}
@Override
public String toString() {
return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName
+ "]";
}
}Backward-incompatible column renaming
Let's consider an example of how to change a column name:
Attention. The following example is intentionally designed to lead to a failure. We are demonstrating this to show the database compatibility issue.
Application Version: 2.0.0.BAD
Database Version: v2bad
Comment
The current changes DO NOT allow us to run two instances (old and new) simultaneously. Therefore, zero downtime deployment will be hard to achieve (given the assumptions, it is virtually impossible).
A/B testing
The current situation is that we have an application version 1.0.0, deployed in production, and the database v1. We need to deploy a second instance of the application, version 2.0.0.BAD, and update the database to v2bad.
Steps:
- a new instance of application version
2.0.0.BAD, which updates the database tov2bad - in the database
v2badthe columnlast_nameno longer exists — it has been changed tosurname - the database and application update was successful, and some instances are running in
1.0.0, others in2.0.0.BAD. All are connected to the databasev2bad - all instances of version
1.0.0will start throwing errors because they will attempt to insert data into the columnlast_name, which no longer exists - all instances of version
2.0.0.BADwill work without issues
As you can see, if we make backward-incompatible changes to the database and application, A/B testing becomes impossible.
Application Rollback
Let's assume that after attempting an A/B deployment (note: likely, the author meant A/B testing) we decided that we need to roll back the application to version 1.0.0. Let's say we do not want to roll back the database.
Steps:
- we are stopping the application instance of version
2.0.0.BAD - the database is still
v2bad - since the version
1.0.0does not understand whatsurname, we will see errors - the hell has broken loose, we can no longer return
As you can see, if we make backward incompatible changes to the database and application, we cannot revert to the previous version.
Execution logs of the script
Backward incompatible scenario:
01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0.BAD
05) Wait for the app (2.0.0.BAD) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0 <-- this should fail
07) Generate a person by calling POST localhost:9992/person to version 2.0.0.BAD <-- this should pass
Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:
{"firstName":"b73f639f-e176-4463-bf26-1135aace2f57","lastName":"b73f639f-e176-4463-bf26-1135aace2f57"}
Starting app in version 2.0.0.BAD
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:
curl: (22) The requested URL returned error: 500 Internal Server Error
Generate a person in version 2.0.0.BAD
Sending a post to 127.0.0.1:9995/person. This is the response:
{"firstName":"e156be2e-06b6-4730-9c43-6e14cfcda125","surname":"e156be2e-06b6-4730-9c43-6e14cfcda125"}Database Changes
Migration script that renames last_name downward API support (simultaneously with this in surname
Original Flyway script:
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');Script that renames last_name.
-- This change is backward incompatible - you can't do A/B testing
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;Code Changes
We changed the field name lastName to surname.
Renaming the column in a backward-compatible way
This is the most common scenario we may encounter. We need to make backward incompatible changes. We have already proven that to deploy without downtime, we should not just apply the database migration without additional actions. In this section of the article, we will perform 3 deployments of the application along with database migrations to achieve the desired result while maintaining backward compatibility.
Note. Let's remember that we have a database version
v1. It contains the columnsfirst_nameandlast_name. We have to changelast_nametosurname. We also have an application version1.0.0,that does not yet usesurname.
Step 2: Adding surname
Application Version: 2.0.0
Database Version: v2
Comment
By adding a new column and copying its content, we are making backward-compatible changes to the database. At the same time, if we roll back the JAR or have a working old JAR, it will not break during execution.
Deploying a new version
Steps:
- perform the database migration to create the new column
surname. Now your database versionv2 - copy data from
last_namedownward API support (simultaneously with this insurname. Note that, if you have a lot of this data, you should consider batch migration! - write the code where you use BOTH and new, and old column. Now your application version
2.0.0 - read the value from the column
surname, if it is notnull, or from last_name, ifsurnamenot specified. You may removegetLastName()from the code, as it will returnnullwhen rolling back your application with3.0.0up to2.0.0.
If you are using Spring Boot Flyway, these two steps will be executed during the application version startup. 2.0.0 If you are running the database versioning tool manually, you will have to perform these two actions separately (first, update the db version manually, and then deploy the new application).
It is important. Remember that the newly created column MUST NOT be NOT NULL. If you perform a rollback, the old application does not know about the new column and will not set it during
Insert.But if you add this constraint, and your DB isv2, this will require setting a value for the new column. This will lead to constraint violations.It is important. You should remove the method
getLastName(), as in version3.0.0the concept of column does not exist in the code.last_nameThis means that nulls will be set there. You can keep the method and add checks fornull, but a much better solution would be to ensure that in the logicgetSurname()you have chosen the correct non-null value.
A/B testing
The current situation is that we have an application version 1.0.0, deployed in production, and the DB in v1. We need to deploy the second instance of versioned application 2.0.0, which will update the database to v2.
Steps:
- a new instance of application version
2.0.0, which updates the database tov2 - meanwhile, some requests were processed by instances of version
1.0.0 - the update was successful, and you have several working instances of the versioned application
1.0.0and other versions2.0.0.All communicate with the DB inv2 - version
1.0.0does not use the surname column in the DB, while the version2.0.0does use it. They do not interfere with each other, and there should be no errors. - version
2.0.0stores data in both the old and new columns, ensuring backward compatibility.
It is important. If you have any queries that count items based on values from the old/new column, you should remember that now you have duplicate values (likely still migrating). For example, if you want to count users whose last name (whatever the column is called) starts with the letter
A, then until the data migration is complete (old→newcolumn) you may have inconsistent data if you query the new column.
Application Rollback
Now we have versioned application 2.0.0 and the database in v2.
Steps:
- rollback your application to version
1.0.0. - version
1.0.0does not use the surname column in the DB.surname, so the rollback should be successful
DB changes
The database contains a column named last_name.
Original Flyway script:
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');Add script surname.
Attention. Remember, you MUST NOT ADD any NOT NULL constraints to the added column. If you roll back the JAR, the old version will not know about the added column and will set it to NULL automatically. If such a constraint exists, the old application will simply break.
-- NOTE: This field can't have the NOT NULL constraint cause if you rollback, the old version won't know about this field
-- and will always set it to NULL
ALTER TABLE PERSON ADD surname varchar(255);
-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
UPDATE PERSON SET PERSON.surname = PERSON.last_nameCode Changes
We store data both in last_name, and in surname. While reading from last_name, since this column is the most relevant. During the deployment process, some requests may have been processed by an instance of the application that has not yet been updated.
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.flyway;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String surname;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
/**
* Reading from the new column if it's set. If not the from the old one.
*
* When migrating from version 1.0.0 -> 2.0.0 this can lead to a possibility that some data in
* the surname column is not up to date (during the migration process lastName could have been updated).
* In this case one can run yet another migration script after all applications have been deployed in the
* new version to ensure that the surname field is updated.
*
* However it makes sense since when looking at the migration from 2.0.0 -> 3.0.0. In 3.0.0 we no longer
* have a notion of lastName at all - so we don't update that column. If we rollback from 3.0.0 -> 2.0.0 if we
* would be reading from lastName, then we would have very old data (since not a single datum was inserted
* to lastName in version 3.0.0).
*/
public String getSurname() {
return this.surname != null ? this.surname : this.lastName;
}
/**
* Storing both FIRST_NAME and SURNAME entries
*/
public void setSurname(String surname) {
this.lastName = surname;
this.surname = surname;
}
@Override
public String toString() {
return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName + ", surname=" + this.surname
+ "]";
}
}Step 3: Removing last_name from the code
Application Version: 3.0.0
Database Version:v3
Comment
Note: Apparently, in the original article, the author mistakenly copied this block's text from step 2. At this step, changes should be made to the application code aimed at removing functionality that uses the column last_name.
By adding a new column and copying its contents, we created backward-compatible changes to the DB. Also, if we roll back the JAR or have a working old JAR, it will not break during execution.
Application Rollback
Currently, we have an application version 3.0.0 and a database v3. Version 3.0.0 does not store data in last_name. This means that in surname the most up-to-date information is stored.
Steps:
- rollback your application to version
2.0.0. - version
2.0.0uses andlast_nameandsurname. - version
2.0.0will takesurname, if it is not null, otherwise —last_name
Database Changes
There are no structural changes in the DB. The following script is executed, which performs the final migration of old data:
-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
-- ALSO WE'RE NOT CHECKING IF WE'RE NOT OVERRIDING EXISTING ENTRIES. WE WOULD HAVE TO COMPARE
-- ENTRY VERSIONS TO ENSURE THAT IF THERE IS ALREADY AN ENTRY WITH A HIGHER VERSION NUMBER
-- WE WILL NOT OVERRIDE IT.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;
-- DROPPING THE NOT NULL CONSTRAINT; OTHERWISE YOU WILL TRY TO INSERT NULL VALUE OF THE LAST_NAME
-- WITH A NOT_NULL CONSTRAINT.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;Code Changes
Note: The description of this block was also mistakenly copied by the author from step 2. According to the narrative logic of the article, the changes in the code at this step should be aimed at removing elements that work with the column last_name.
We store data both in last_name, and in surname. In addition, we read from the column last_name, since it is the most relevant. During deployment, some requests may be handled by an instance that has not yet been updated.
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.flyway;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String surname;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSurname() {
return this.surname;
}
public void setSurname(String lastname) {
this.surname = lastname;
}
@Override
public String toString() {
return "Person [firstName=" + this.firstName + ", surname=" + this.surname
+ "]";
}
}Step 4: Remove last_name from the database
Application Version: 4.0.0
Database Version: v4
Comment
Because the version code 3.0.0 did not use the column last_name, nothing bad will happen during execution if we roll back to 3.0.0 after removing the column from the database.
Execution logs of the script
We will do it in the following way:
01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0
05) Wait for the app (2.0.0) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0
07) Generate a person by calling POST localhost:9992/person to version 2.0.0
08) Kill app (1.0.0)
09) Run 3.0.0
10) Wait for the app (3.0.0) to boot
11) Generate a person by calling POST localhost:9992/person to version 2.0.0
12) Generate a person by calling POST localhost:9993/person to version 3.0.0
13) Kill app (3.0.0)
14) Run 4.0.0
15) Wait for the app (4.0.0) to boot
16) Generate a person by calling POST localhost:9993/person to version 3.0.0
17) Generate a person by calling POST localhost:9994/person to version 4.0.0
Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:
{"firstName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2","lastName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2"}
Starting app in version 2.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:
{"firstName":"e41ee756-4fa7-4737-b832-e28827a00deb","lastName":"e41ee756-4fa7-4737-b832-e28827a00deb"}
Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:
{"firstName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","lastName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","surname":"0c1240f5-649a-4bc5-8aa9-cff855f3927f"}
Killing app 1.0.0
Starting app in version 3.0.0
Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}
Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}
Killing app 2.0.0
Starting app in version 4.0.0
Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:
{"firstName":"cbe942fc-832e-45e9-a838-0fae25c10a51","surname":"cbe942fc-832e-45e9-a838-0fae25c10a51"}
Generate a person in version 4.0.0
Sending a post to 127.0.0.1:9994/person. This is the response:
{"firstName":"ff6857ce-9c41-413a-863e-358e2719bf88","surname":"ff6857ce-9c41-413a-863e-358e2719bf88"}Database changes
Regarding v3 we simply remove the column last_name and add the missing constraints.
-- REMOVE THE COLUMN
ALTER TABLE PERSON DROP last_name;
-- ADD CONSTRAINTS
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;Code Changes
No changes in the code.
Output
We successfully applied a backward-incompatible column name change by performing several backward-compatible deployments. Below is a summary of the actions taken:
- deploying the application version
1.0.0withv1database schema (column name =last_name) - deploying the application version
2.0.0,which stores the data inlast_nameandsurname. The application reads fromlast_name. The database is at versionv2, containing columns likelast_nameandsurname. surnameis a copy of last_name. (NOTE: this column should not have a not null constraint) - deploying the application version
3.0.0, which stores data only insurnameand reads from surname. As for the database, the latest migration is happeninglast_namedownward API support (simultaneously with this insurname. The constraint is also NOT NULL removed fromlast_name. The database is now at versionv3 - deploying the application version
4.0.0— no changes are made in the code. The database deploymentv4, which removeslast_name. Here you can add any missing constraints to the database.
By following this approach, you can always roll back one version without breaking database/application compatibility.
Code
All the code used in this article is available at . Below is additional description.
Projects
After cloning the repository, you will see the following folder structure.
├── boot-flyway-v1 - 1.0.0 version of the app with v1 of the schema
├── boot-flyway-v2 - 2.0.0 version of the app with v2 of the schema (backward-compatible - app can be rolled back)
├── boot-flyway-v2-bad - 2.0.0.BAD version of the app with v2bad of the schema (backward-incompatible - app cannot be rolled back)
├── boot-flyway-v3 - 3.0.0 version of the app with v3 of the schema (app can be rolled back)
└── boot-flyway-v4 - 4.0.0 version of the app with v4 of the schema (app can be rolled back)Scripts
You can run the scripts described below, which will demonstrate backward-compatible and incompatible changes in the database.
To see the case of backward-compatible changes, run:
./scripts/scenario_backward_compatible.shAnd to see the case of backward-incompatible changes, run:
./scripts/scenario_backward_incompatible.shSpring Boot Sample Flyway
All examples are taken from Spring Boot Sample Flyway.
You can take a look at http://localhost:8080/flyway, there is a list of scripts.
This example also includes an H2 console (at http://localhost:8080/h2-console), so you can view the state of the database (default jdbc URL is jdbc:h2:mem:testdb).
Additional
Also, read other articles in our blog:
Source: habr.com
