Architektura
Last updated
Last updated
Logika serwisu podzielona jest na 4 główne serwisy:
CompareService
- odpowiada za walidację, inicjalizację i uruchamianie procesu porównania
TableComparer
- odpowiada za proces porównania ze sobą dwóch tabel
BindingService
- odpowiada za wstępne dopasowanie do siebie dwóch tabel i zwrócenie informacji o nich - kolumnach i ich typach, kluczach głównych, indeksach oraz zgodności tych typów oraz informację o koniecznych mapowaniach, które trzeba zaznaczyć w opcjach aby przeprowadzić porównanie tabel
CompareResultService
- odpowiada za zwracanie informacji o statusie porównania jako całość, konkretnych tabel oraz zapytań wygenerowanych w trakcie porównania do wyrównania stanu bazy docelowej
Cała interakcja z bazami danych wydzielona jest do klasy QueryExecutor
, która wewnętrznie korzysta z odpowiednich dla bazy implementacji QueryBuilder
oraz DataManager
.
QueryBuilder
to klasa odpowiedzialna za generowanie zapytań z odpowiednią składnią. Gdy to możliwe buduje ona cache, aby zminimalizować obciążenie przy powtarzających się zapytaniach.
DataManager
to klasa odpowiedzialna za mapowanie danych między bazą danych, a Java'owymi typami danych oraz konwersji oraz z reprezentacji tekstowej danych.
Wszystkie tabele zapisywane są w schemacie compare
CompareInfo
to główna encja, która zapisuje informację o porównaniu baz danych. Zapisana jest tam jest cała konfiguracja oraz w trakcie porównania aktualizowany jest stan zmiennych opisujących jego stan.
Dla każdej pary tabel do porównania tworzona jest encja Table CompareInfo
, która podobnie zapisuje konfigurację oraz służy do zapisu postępu stanu porównania.
Encja TableCompareResultQuery
służy do wydzielenia zapisu wygenerowanych w trakcie porównania dwóch tabel zapytań korygujących bazę docelową.
Encja PrimaryKeyEntry
służy do zapisu listy kluczy, dla których występuje różnica w podziale na operację DELETE, UPDATE, INSERT.
Encja DatabaseConfiguration
służy do zapisu danych dostępowych do bazy danych. Hasło jest zabezpieczane przed zapisem. Jednakowe konfiguracje nie są duplikowane
Encja TableParams
służy do zapisu danych o porównywanej tabeli. Jednakowe konfiguracje nie są duplikowane
Inicjalizacja porównania - walidowane są informacje podane w request oraz zapisywane do bazy
Uruchomienie porównania - proces porównania zostaje zakolejkowany do puli wątków nadzorujących porównania. Rozmiar puli jest konfigurowalny.
Porównywanie tabel - każda para tabel porównywana jest w osobnym wątku, ich jednoczesna liczba jest ograniczona przez max. dozwoloną liczbę połączeń do bazy danych oraz konfigurowalny rozmiar puli wątków
Wątek nadzorujący porównanie czeka, aż wszystkie tabele zostaną porównane i zapisuje informację o jego statusie - jeśli któreś z poszczególnych porównań się nie powiedzie nie przekłąda się to na status całkości porównania
W serwisie TableComparer wyizolowany jest proces porównania ze sobą dwóch tabel
Pobieranie informacji o typach danych kolumn i klucza głównego, walidacja ich zgodności
Porównianie tabel przebiegające w następujący sposób: Serwis sprawdza ile jest rzędów w tabeli źródłowej i docelowej. Następnie iteruje przez wszystkie rzędy posortowane po podanym kluczu głównym pobierając za każdym razem z baz batch N rzędów (domyślnie 1000) oraz porównując rząd po rzędzie. Porównanie polega na sprawdzeniu w pierwszej kolejności zgodności kluczy, a gdy są zgodne skrótu md5 pozostałych porównywanych kolumn. Zapisywane są przy tym informacje, które rzędy (klucze) należy aktualizować, usunąć i dodać do bazy docelowej.
Jeśli została wybrana opcja recheckKeys
serwis po zdefiniowanym czasie (keyRecheckWaitDurationMillis
) ponownie pobiera wszystkie rzędy, które zostały zakwalifikowane jako różniące się i sprawdza, czy nadal należy je zaktualizować/usunąć/dodać
Zapisywanie wyników - na tym etapie zapisywane są wszystkie znalezione klucze (PrimaryKeyEntry
) oraz generowane są zapytania korygujące stan bazy docelowej (TableCompareResultQuery
)
Klasa DataManager
obsługuje mapowanie typów:
z bazy do Javy
z Javy do bazy
z Javy do string
z string do Javy
Wszystkie mapowania odbywają się na podstawie struktury ColumnInfo
:
Zawartość pola type
jest określana domyślnie na podstawie ręcznego mapowania z javaSqlType
i dodatkowo korygowana per konkretna baza danych na podstawie dodatkowych informacji o typie wyciąganym z bazy danych w momencie budowania struktury.
Proces ten można kontrolować dodatkowo podając flagi, które są przekazywane do DataManagera
. Na tę chwilę dostępne są trzy opcje:
mapBooleanTypes
-> jeśli flaga ustawiona na true moduł próbuje mapować typy boolean - jeśli w jednej bazie mamy BOOLEAN lub BIT, a w drugiej CHAR(1) lub NUMBER(1) to wartości z drugiej bazy będą interpretowane jako wartości boolean. Może prowadzić do błędów, jeśli w mapowanej kolumnie są nietypowe wartości. W przypadku opisu tekstowego compare obsługuje jedynie wartości 't' i 'f'.
treatOracleDateAsTimestamp
-> w bazie oracle type DATE obsługuje również godzinę. Jeśli oprócz dat w kolumnach są też godziny należy ustawić tę flagę na true i wtedy kolumna będzie traktowana jako timestamp. Jeśli jej nie ustawimy to część godzinowa będzie ignorowana przy generowaniu końcowych zapytań, ale w trakcie porównania ta godzina będzie powodowała wykrycie różnicy względem rzędu w którym godzina nie jest podana.
mapPostgresMoneyTypeToDouble
-> typ MONEY w bazie postgres może mieć różne formatowanie, (np. $234.42) przez co nie da się przekonwertować go na typ numeric. Domyślnie jest on pobierany jako String, jednak gdy ta flaga jest ustawiona na TRUE to jest on castowany na typ numeric przy pobieraniu, co pozwala na porównywanie z innymi bazami.
Typy boolean są traktowane specjalnie, ponieważ na etapie mapowania musimy wiedzieć, jaki był oryginalny typ danych, w innych przypadkach nie jest to potrzebne.
ColumnType
może przyjmować wartości:
Sam proces mapowania opiera się zazwyczaj na gotowych funkcjach w Javie:
z bazy do Javy -> np. ResultSet::getTimestamp
z Javy do bazy -> np. PreparedStatement::setTimestamp
z Javy do string -> Object.toString
z string do Javy -> np. Timestamp::valueOf
W przypadku bardziej specyficznych typów potrzebne jest dodatkowe przetwarzanie