W lekcji drugiej skonfigurujemy połączenie z bazą danych pod kątem środowiska produkcyjnego, a także przejmiemy kontrolę nad jej tworzeniem i modyfikacjami za pomocą narzędzia Liquibase.
Do tej pory w naszym kursie Javy pokazywaliśmy już, jak połączyć się z bazą danych oraz pozostawić jej zarządzanie w rękach automatycznych bibliotek (JPA). Niestety w przypadku większych projektów to podejście nie jest idealne — dlatego dzisiaj pokażemy jak ‘odzyskać’ nad nią kontrolę. Rozdzielimy także bazę danych testową od tej produkcyjnej, aby móc bez obaw manipulować danymi np. podczas testów.
Profile aplikacji
We wstępie wspominaliśmy o rozróżnieniu pomiędzy środowiskiem produkcyjnym a testowym / developerskim. Oczywiście zależy nam na rozwiązaniu, które pozwoli w wygodny sposób rozdzielić konfigurację takich środowisk i uniknąć wszechobecnych konstrukcji if (…) then {…} . W Springu takim narzędziem są właśnie profile.
Profile to sposób na uporządkowanie konfiguracji i struktury aplikacji oraz wprowadzenie dodatkowych warunków jej działania — np. inicjowania określonych beanów tylko, jeżeli jakiś profil jest aktywny. Jednocześnie może być aktywnych wiele profili, może też nie być aktywny żaden (jest to domyślny stan działania aplikacji). Profili nie trzeba specjalnie deklarować — wystarczy określony profil ‘włączyć’, aby był aktywny (nawet jeśli nie zmieni on działania aplikacji) — w pewnym sensie są więc po prostu etykietkami opisującymi środowisko, w jakim działa aplikacja.
Profile pozwalają nam zasadniczo na dwie rzeczy — dodatkowo wczytać określoną konfigurację (np. pliki properties lub yaml) lub spowodować, że określony bean zostanie uruchomiony w aplikacji. Co ważne — niezależnie od aktywnych profili, domyślna konfiguracja także zostanie wczytana (dotyczy to w szczególności ustawień aplikacji — profile mogą jedynie nadpisać istniejące ustawienia, ale nie mogą ich usuwać czy dezaktywować).
Aktywowanie profili
Spring pozwala na aktywacje profili na dwa podstawowe sposoby — w kodzie, korzystając z XX zanim aplikacja zostanie uruchomiona, oraz za pomocą zmiennej środowiskowej spring.profiles.active. O ile oba sposoby są funkcjonalnie identyczne, polecamy korzystać raczej z konfiguracji poprzez środowisko — w przeciwnym razie łatwo przeoczyć określony sposób uruchamiania aplikacji (np. w projekcie webowym dodając fragment kodu do metody main() naszej aplikacji, nie zostanie on wykonany jeśli aplikację uruchomimy w postaci pliku WAR).
W kodzie
Do wyboru mamy dwie opcje — pierwsza musi być użyta zanim aplikacja będzie uruchomiona i polega na wywołaniu poniższego kodu:
SpringApplication.setAdditionalProfiles("development", "localdevelopment");
druga, w już działającej aplikacji, wymaga dostępu do ApplicationContext (uwaga! w zależności kiedy ten kod się wykona, może on nie mieć zamierzonych efektów; aby upewnić się, że wykonuje się on przy starcie aplikacji, zobacz np ten wątek na SO):
ApplicationContext ctx = null;
((ConfigurableEnvironment)ctx.getEnvironment()).addActiveProfile("development");
W testach
Kolejną bardzo przydaną funkcją jest używanie osobnych profili do testów — dzięki temu możemy jeszcze dokładniej kontrolować środowisko testowe, a także używać oddzielnej konfiguracji (np. innych kluczy do API itp). W przypadku testów wystarczy dodać adnotację @ActiveProfiles nad naszą klasą z testami, np:
@ActiveProfiles({"development", "localdevelopment"})
@SpringBootTest
public class ApplicationTests {
...
}
W środowisku
Aby ustawić aktywne profile na poziomie systemu operacyjnego, można ustawić zmienną środowiskową SPRING_PROFILES_ACTIVE — jest to szczególnie użyteczne w przypadku środowisk produkcyjnych (oprogramowanie do obsługi chmury często pozwala konfigurować zmienne środowiskowe w prosty sposób — np. AWS BeanStalk lub EC2). Jeśli samodzielnie konfigurujesz serwer (lub przygotowujesz skrypty startowe), w przypadku systemów *nix wystarczy wywołać komendę:
export SPRING_PROFILES_ACTIVE=production,remoteproduction
aby aktywować profile production oraz remoteproduction.
W przypadku uruchamiania aplikacji samodzielnie, w zależności od sposobu uruchamiania aplikacji, możesz po prostu przekazać parametr
spring.profiles.active, np w wierszu poleceń
mvn spring-boot:run --spring.profiles.active=development,localdevelopment
lub (jeśli uruchamiamy aplikację z pliku JAR)
java -jar -Dspring.profiles.active=development,localdevelopment app.jar
Powyższe znajdziesz także w dokumentacji Springa
W IDE
Konfiguracja w IDE jest równie prosta i sprowadza się do ustawienia odpowiednich zmiennych przy uruchamianiu. W przypadku Eclipse, do sekcji ‘VM arguments’ dodajemy
-Dspring.profiles.active=development,localdevelopment
natomiast w przypadku IntelliJ IDE można skorzystać z opcji ‘Environment variables’ w oknie konfiguracji uruchamiania.
Korzystanie z profili w aplikacji
Oczywiście sama aktywacja profili na niewiele się przyda, jeśli nasza aplikacja nie będzie owych profili wykorzystywała — jak wspominaliśmy wyżej, są dwa sposoby, na które można użyć profili.
Dodatkowe pliki z ustawieniami
Pierwszy sposób to nadpisywanie istniejących ustawień lub dodawanie nowych. Aby to zrobić, wystarczy dodać kolejny plik .properties (w swojej aplikacji powinieneś już mieć plik application.properties) dodając do nazwy -nazwaProfilu — w naszym przypadku będą to pliki application-development.properties oraz application-production.properties. Jeśli korzystasz z konfiguracji w formacie YAML, zasada jest taka sama — tworzymy nowy plik, dodając do nazwy myślnik oraz nazwę profilu, w której ma obowiązywać.
Uwaga: Pliki te mogą być puste. Ich utworzenie jest dobrym pomysłem, nawet jeśli póki co nie planujesz nadpisywać w nich żadnych ustawień! Dzięki temu będzie jasne, że profil o takiej nazwie istnieje i może być użyty w aplikacji.
Więcej o tym sposobie konfiguracji znajdziesz w dokumentacji
Aktywacja beanów
Drugi sposób polega na warunkowym uruchamianiu beanów — służy do tego adnotacja @Profile(“profileName”). Adnotację tą można dodać nad klasą z adnotacją @Component (lub dziedziczącą po niej, np. @Controller czy @Service) lub nad metodę z adnotacją @Bean.
Uwaga: adnotacja ta może przyjąć wiele nazw profili, bean będzie utworzony jeśli przynajmniej jeden z wymienionych profili jest aktywny.
Szczegółowy opis adnotacji @Profile i jej zastosowania znajdziesz w dokumentacji Springa.
Podsumowanie
Profile są bardzo elastycznym narzędziem, które pozwala znacząco uprościć konfigurację aplikacji dla różnych środowisk. W przypadku projektu bilet będziemy korzystali z 2 profili — development oraz production. W zależności od profilu inna będzie konfiguracja bazy danych oraz poziom logowania.
Łączymy naszą aplikację z bazą danych
W naszym kursie Javy implementowaliśmy już połączenie aplikacji Springowej z bazą danych — jeśli chcesz sobie odświeżyć pamięć, zajrzyj do lekcji 13. Opisywany tam sposób miał jednak kilka wad — po pierwsze wymagał dostępu do działającej na zewnątrz bazy danych za każdym razem, kiedy chcieliśmy uruchomić aplikację. Dodatkowo nie mieliśmy kontroli nad tym, co się w niej znajduje — potencjalnie inna aplikacja mogła namieszać w naszej bazie danych powodując dziwne problemy, mogliśmy zapomnieć stworzyć tabelę itp. Ponieważ projekt bilet ma być aplikacją ‘produkcyjną’, zaradzimy temu z użyciem odpowiednich bibliotek i narzędzi.
Baza danych na żądanie — MariaDB4J
W przypadku bazy danych naszej aplikacji mamy dwa cele — po pierwsze, aplikacja powinna się uruchamiać na dowolnym środowisku bez dodatkowej konfiguracji w trybie ‘testowym’ — tj. nie powodującym zmian widocznych na innych instancjach aplikacji. Po drugie, dane dostępowe do bazy danych nie mogą być na repozytorium (czyli nie możemy ich dodać np. do plików properties).
Dodatkowo, ponieważ planujemy używać MySQL w ‘produkcyjnej’ instalacji, używane przez nas rozwiązanie powinno być zgodne.
Pierwszy cel stosunkowo łatwo możemy zrealizować korzystając z bazy danych w pamięci — HSQLDB czy H2 spełniałyby te kryteria. Niestety, składnia zapytań SQL jest nieco inna (szczególnie w przypadku tworzenia tabel) niż w przypadku MySQL. Ponieważ nie chcemy zdawać się nie JPA i mieć większą kontrolę nad strukturą i danymi w bazie danych (o tym będzie kolejna sekcja), rozwiązanie to nie sprawdzi się w naszym projekcie.
Odpowiedzią byłoby uruchamianie MySQL z poziomu naszej aplikacji, w miarę możliwości bez konieczności instalacji czegokolwiek innego. MySQL dostarczał takie rozwiązanie pod nazwą MySQL MXJ, niestety ostatnia wersja to 5.0.12 (rozwiązanie to nie jest kontynuowane). Na szczęście istnieje też MariaDB4J — w zdecydowanej większości zgodna z MySQL baza danych, którą ktoś przygotował do uruchomienia z poziomu JARa (nie wymaga więc instalacji niczego dodatkowego w systemie). Uwaga! MariaDB bazuje na MySQL, ale jest rozwijana oddzielnie — niektóre najnowsze funkcje mogą nie być zgodne! Jeśli wykorzystujesz w projekcie bardzo specyficzne funkcje MySQL, sprawdź informacje o zgodności. Minusem tego rozwiązania jest rozmiar naszego pliku JAR — urośnie on dość znacznie. W większości przypadków nie jest to problemem, ale warto o tym pamiętać na wszelki wypadek.
Na potrzeby kursu przyjmijmy, że do rozwijania oprogramowania będziemy używać profilu o nazwie development. Zakładamy, że skonfigurowałeś już środowisko na podstawie poprzedniej sekcji tak, aby profil ten był aktywny jeśli uruchamiasz aplikację lokalnie. Potrzebujemy jeszcze konfiguracji, która zamiast głównej bazy danych naszej aplikacji pozwoli nam uruchomić bazę testową, wypełnioną jakimiś przykładowymi danymi. Na początek dodajemy zależność do naszego projektu, dodając do pliku pom.xml:
<dependency>
<groupId>ch.vorburger.mariaDB4j</groupId>
<artifactId>mariaDB4j</artifactId>
<version>2.2.3</version>
</dependency>
Następnie dodajemy konfigurację Spring’a dodając poniższą klasę:
@Configuration
@Profile("development")
public class DevelopmentDatabaseConfig {
protected static final int DB_PORT = 3310;
@Bean(name = "mariadb4j")
public MariaDB4jSpringService mariaDB4j() {
MariaDB4jSpringService service = new MariaDB4jSpringService();
service.setDefaultPort(DB_PORT);
return service;
}
}
Możesz także zajrzeć na przykłady w dokumentacji MariaDB4J. Pozostaje nam tylko poinstruowanie Springa o tym, jak ma się łączyć naszą z bazą danych, poprzez dodanie do pliku application-development.properties poniższych linijek (są to domyślne wartości, z jakimi uruchamiana jest testowa baza danych):
spring.datasource.url=jdbc:mysql://localhost:3310/test
spring.datasource.username=root
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=true
Zwróć uwagę, że zmieniliśmy domyślny port (jest to 3306), aby nie kolidowało z ‘normalną’ bazą danych, którą część programistów może mieć stale uruchomioną. Po uruchomieniu aplikacji z użyciem profilu development, w konsoli powinnaś zobaczyć logi MariaDB4J (będzie tam także wyjątek z Hibernate — ale o tym powiemy sobie za chwilę).
Konfiguracja produkcyjna
Dobrą praktyką jest nie umieszczanie informacji dostępowych do produkcyjnej bazy danych na repozytorium kodu, w głównym pliku .properties można albo użyć danych do środowiska QA albo nie uzupełniać ich wcale — aplikacja w takiej sytuacji nie uruchomi, jeśli nie aktywujemy odpowiedniego profilu lub nie przekażemy tych danych w inny sposób. Spring posiada wiele mechanizmów, dzięki którym możemy takimi ustawieniami zarządzać poza naszą aplikacją — szczegóły rozważymy jednak w części o umieszczaniu aplikacji na serwerze i konfiguracji środowisk.
Przejmujemy kontrolę — Liquibase
Bazy danych to bardzo potężne narzędzia, jednak jak ze wszystkim — możliwości są wprost proporcjonalne do ilości wymaganej konfiguracji. W kursie Javy pozwoliliśmy JPA na samodzielne zarządzanie bazą danych, co było dla nas wystarczające. Byłoby zapewne i w tej sytuacji, ale zróbmy to poprawnie — przejmijmy stery!
O ile standard SQL jest powszechnie implementowany przez różnego rodzaju bazy danych, o tyle niektóre z nich rozszerzają go i udostępniają jeszcze więcej specyficznych funkcji czy typów danych. Przykładem może być rozszerzenie MySQL do danych przestrzennych ‑ponieważ nie jest to część standardowego SQL, JPA mógłby mieć problem z odpowiednim dobraniem typów pól. Dodatkowym problemem mogą być dane początkowe — np. wstępna konfiguracja, przykładowe rekordy, pierwszy użytkownik itp. W przypadku JPA implementacja takiej logiki i jej późniejsze utrzymanie nie należą do najprostszych, szczególnie w przypadku dużych skryptów.
Jednym z najpopularniejszych rozwiązań tego problemu jest Liquibase — narzędzie, które pozwala na zarządzanie bazą danych (zarówno strukturą jak i danymi) w sposób przyrostowy — kolejne zmiany są reprezentowane przez np. kolejne pliki, a narzędzie samo śledzi, które zmiany zostały już zaaplikowane i w razie potrzeby nanosi pozostałe w ustalonej kolejności. Dzięki temu z repozytorium znikają pliki SQL na kilka tysięcy linijek, w których prześledzenie intencji zmian graniczy z cudem, a przypadkowy błąd czy modyfikacja nie w tym miejscu, co trzeba, może się skończyć tragicznie (dla naszych danych) w skutkach. Minusem jest oczywiście konieczność stworzenia struktury bazy danych ‘ręcznie’, poprzez odpowiednio skonstruowane zapytania SQL.
Liquibase można używać jako całkowicie osobne narzędzie, można je zintegrować z Mavenem lub — w naszym przypadku najwygodniejsze — użyć w połączeniu ze Springiem.
Konfiguracja Liquibase ze Springiem
Pierwszym krokiem jest oczywiście dodanie zależności Mavenowej:
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>3.5.3</version>
</dependency>
W przypadku biblioteki Liquibase, jest ona na tyle popularna, że Spring Boot ‘wie’ o konkretnej wersji, możemy więc pominąć numer wersji w pliku pom.xml:
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
Następnie musimy określić, jak będziemy opisywać nasze zmiany. Dostępnych opcji jest kilka, w tym XML, JSON, YAML czy SQL — my użyjemy odrobiny XMLa i głównie SQL’a. W katalogu z zasobami umieszczamy plik migration.xml o treści:
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd">
<includeAll path="migration/"/>
</databaseChangeLog>
Jego jedyną rolą jest określenie, że definicje zmian będziemy umieszczać w katalogu migration, i tam Liquibase będzie ich szukał. Dodajmy więc nasze pierwsze zmiany — w tym wypadku tworzymy tabelę do przechowywania danych logowania użytkowników.
--liquibase formatted sql
--changeset author:jderda
CREATE TABLE `users` (
`email` VARCHAR(254) NOT NULL PRIMARY KEY,
`passwordHash` VARCHAR(60) NOT NULL,
`token` VARCHAR(65) NULL,
`active` BOOL NOT NULL DEFAULT 0,
`deleted` BOOL NOT NULL DEFAULT 0
);
Pierwsze dwie linijki są komentarzami w składni SQL, ale są wymagane w przypadku składni SQL dla Liquibase — dzięki nim przekazujemy do Liquibase informacje o autorze oraz ewentualne dodatkowe metadane. Poza tym zapewne zastanawiasz się skąd długość pola ’email’ ma wartość 254? Tak się składa, że jest to maksymalna długość adresu email, jej nietypowość jest wynikiem różnic w definicji określonych schematów pomiędzy różnymi specyfikacjami (więcej możesz znaleźć w tym świetnym wątku na SO).
Ostatnim krokiem jest konfiguracja Springa — możemy skorzystać z wspomnianej wcześniej integracji, ale możemy też użyć mechanizmu wbudowanego w Spring Boot.
Oczywiście, wybieramy opcję drugą, dlatego do pliku application.properties dodajemy następującą wartość:
liquibase.change-log:classpath:migration.xml
Alternatywnie, możemy nasz plik changelog zapisać w postaci YAML i umieścić go w domyślnej lokacji db/changelog/db.changelog-master.yaml.
Niestety, w tym momencie napotkamy na pewien problem — po uruchomieniu aplikacji, na konsoli zobaczymy błąd i uruchomienie nie powiedzie się. Problem polega na tym, że Spring nie gwarantuje kolejności inicjalizacji beanów (domyślnie, możemy to na nim ‘wymusić’) — w tym wypadku, próbuje stworzyć połączenie do bazy danych i migrację jeszcze przed faktycznym uruchomieniem naszej bazy danych. W większości przypadków kolejność, w jakiej działa Spring nei ma dla nas znaczenia, ale w tym przypadku złożyło się kilka czynników:
- kwestia dotyczy bazy danych, która przeważnie działa w momencie uruchamiania aplikacji
- próbujemy uruchomić migrację danych — proces, który chcemy uruchomić jak najszybciej (zanim inne beany zostaną stworzone)
- ‘ręcznie’ uruchamiamy bazę danych już po uruchomieniu aplikacji
Rozwiązaniem tego problemu jest wymuszenie na Springu określonej kolejności inicjowania beanów — w naszym przypadku chcemy, aby bean MariaDB4jSpringService został uruchomiony przed utworzeniem DataSource. Najprostszym rozwiązaniem jest skorzystanie z adnotacji @DependsOn, pozwalającej określić jakie beany powinny zostać zainicjowane przed tym określonym beanem. Niestety DataSource jest tworzony automatycznie przez Spring Boot, więc nie możemy w prosty sposób dodać rzeczonej adnotacji. Możemy jednak samodzielnie utworzyć DataSource dodając odpowiednią adnotację (Spring jest na tyle sprytny, że nie utworzy kolejnego beana tego samego typu). Do naszej konfiguracji DevelopmentDatabaseConfig dodajmy więc następującą metodę:
@Bean
@DependsOn("mariadb4j")
public DataSource ds() {
SimpleDriverDataSource ds = new SimpleDriverDataSource();
ds.setDriverClass(Driver.class);
ds.setUrl(String.format("jdbc:mysql://localhost:%d/test", DB_PORT));
ds.setUsername("root");
return ds;
}
Ta zmiana pozwoli bez problemu uruchomić aplikację z użyciem profilu development. Uwaga! Użyty tutaj SimpleDriverDataSource nadaje się tylko i wyłącznie do testów i nie powinien być nigdy używany w środowisku produkcyjnym — do tego skorzystaj z automatycznej konfiguracji lub samodzielnie skonfiguruj tzw. pulę połączeń. Warto zwrócić także uwagę, że nasze ustawienia dotyczące bazy danych w pliku application-development.properties nie są już potrzebne. Dobrą praktyką jest jednak zostawienie notki dla innych developerów, gdzie mogą szukać właściwej konfiguracji.
Po uruchomieniu aplikacji, w logach znajdziesz linijkę podobną do tej:
2017-07-02 19:00:23.141 INFO 16865 --- [ost-startStop-1] liquibase : Successfully acquired change log lock
2017-07-02 19:00:24.308 INFO 16865 --- [ost-startStop-1] liquibase : Reading from test.DATABASECHANGELOG
2017-07-02 19:00:24.885 INFO 16865 --- [ost-startStop-1] liquibase : Successfully released change log lock
Oznacza to, że migracja została przeprowadzona (w tym wypadku tylko sprawdzona — baza danych posiada już wszystkie migracje) i nasza baza danych wygląda dokładnie tak, jak tego oczekujemy!
Dane testowe / developerskie
Jedną z użytecznych, choć często niedocenianych rzeczy są dane testowe. Uruchamiając aplikację lokalnie wygodnym byłoby, aby w aplikacji istniał aktywny użytkownik oraz były obecne jakieś rekordy aby można było sprawdzić widoki czy API bez przechodzenia całej ścieżki od początku. Jednocześnie bardzo niefajnym byłoby, gdyby takie dane trafiły na produkcje — użytkownik “test” z hasłem “test” czy fikcyjne zamówienia były źródłem kłopotów niejednej organizacji. Liquibase pozwala nam zaradzić temu problemowi poprzez mechanizm zwany contexts.
W dużym uproszczeniu działa to podobnie jak ‘profile’ w wersji dla bazy danych — wybrane skrypty migracji mogą być uruchamiane tylko w określonym kontekście — czyli np. w developerskiej bazie danych, ale już nie w produkcyjnej. Możemy mieć także kilka kontekstów i w ten sposób osobno zarządzać danymi, które mają się znaleźć wszędzie (np. bazowa lista kategorii sklepu powinna trafić także na produkcję), tymi, które powinny być użyte na środowisku testowym/integracyjnym (np. konkretna lista zamówień czy produktów czy użytkowników) oraz tymi używanymi do developmentu (dane każdego ‘typu’, ułatwiające testowanie na bieżąco).
Aby określić kontekst (lub konteksty), w jakim powinien uruchamiać się dany skrypt, wystarczy w pliku SQL użyć specjalnej składni, przykładowo nagłówek:
--liquibase formatted sql
--changeset author:jderda
wystarczy zmienić na następujący:
--liquibase formatted sql
--changeset author:jderda context:development
a następnie w naszym pliku application-development.properties dodać linijkę:
liquibase.contexts=development
I tutaj czycha pułapka, o której możemy się doczytać w jednym zdaniu dokumentacji (a dokładniej “If you do not specify a context when you run the migrator, ALL contexts will be run.”). Aby uzyskać oczekiwany przez nas efekt, w pliku application.properties dodaj linijkę:
liquibase.contexts=production
Te zmiany wystarczą, aby nasz plik SQL z adnotacją context:development uruchomił się tylko i wyłącznie, jeśli aktywny jest Springowy profil development (czyli w naszym przypadku, tylko na naszej testowej bazie danych).
Bardziej szczegółowa dokumentacja dostępna jest w oficjalnym poradniku Liquibase. Liquibase od wersji 3.3 oferuje także podobne narzędzie o nazwie labels (różnice zostały opisane w poście na blogu Liquibase), w naszym przypadku ograniczymy się jednak tylko do użycia kontekstów.
Podsumowanie
Liquibase nie jest koniecznością w wielu aplikacjach — jeśli wystarczające jest tworzenie i modyfikowanie bazy danych przez JPA, a potencjalny koszt utraty danych (gdy np. zamiast zmienić nazwę kolumny, usuniemy jedną i stworzymy inną w jej miejsce…) nie jest wysoki, Liquibase może okazać się bardziej stratą czasu niż wartością. Najbardziej rozwiązanie to docenią projekty, w których dane są bardzo istotne, a baza danych używana także przez np. analityków lub inne systemy.
W projekcie bilet nie ma silnych przesłanek, aby zastosować tą technologię, jednak w ramach nauki chcieliśmy pokazać co to jest i jak z niej korzystać. Alternatywnym rozwiązaniem do Liquibase jest Flyway — zasada działania jest identyczna, różnią się jedynie detalami i sposobem konfiguracji (oczywiście, w sieci możesz przeczytać dyskusje oraz wrażenia z używania każdej z nich — np. na StackOverflow)
Kod źródłowy
Kody źródłowe są dostępne w serwisie GitHub — użyj przycisków po prawej aby pobrać lub przejrzeć kod do tego modułu. Jeśli masz wątpliwości, jak posługiwać się Git’em, instrukcje i linki znajdziesz w naszym wpisie na temat Git’a.
Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!