Repository navigation
Backend Ablauf
← zurück zur Backend-Logik Übersicht
Autor: Maren
Auf dieser Seite werden die Abläufe auf der Backend-Seite näher beleuchtet. Einerseits wird der Standardablauf einer Datenbankabfrage beschrieben, als auch der Ablauf der Tests, die für jede Funktion erstellt werden müssen bevor sie committet werden. Hinzu kommt eine Erläuterung der verwendeten Frameworks sowie der gesetzten Standards, die verwendet werden sollen.
###Standardablauf:
Gestartet wird die Ausführung des Backends in der Java-Hauptklasse HappyHour.java. Zu Beginn wird zunächst eine Logger-Klasse erstellt, die die Fehlerausgabe handhabt. Hinzu kommt eine Boolean Variable mit Flag für den Durchlauf eines Testruns. Dann können alle Eigenschaften der Backend-Programmierung gesetzt werden:
// Die Eigenschaften für das Projekt
public static Settings settings;
public static DAOFactory daoFactory;
public static Spark spark;
public static CmdLineParser parser;Für die einzelnen Eigenschaften (mit Ausnahme des Parser) ist eine seperate Klasse vorhanden. Nach dem Setzen der Eigenschaften wird in der main-Funktion die Ausführung gestartet. Dabei werden die Standard-Einstellungen aus der Settings.java-Klasse geladen, die Logging-Einstellungen durch die Funktionen in der LoggingConfiguration.java-Klasse gesetzt und anschließend eine Verbindung zur Datenbank hergestellt. Hinzu kommt das Starten des Spark Servers durch Aufrufen der Spark.java-Klasse. Nun ist der Server bereit für Anfragen und wird erst beendet, wenn das Testrun-Argument, das vorab erstellt wurde, übergeben wird. Gleichzeitig mit Beenden des Servers, wird auch die Verbindung zur Datenbank geschlossen.
Der Spark Server ist ein REST-Server und wird durch die Main-Funktion in der HappyHour.java-Klasse gestartet. Beim Aufruf der Spark-Funktion wird als erstes der Port für den Zugriff gesetzt. Dabei wird in einer if-Abfrage geprüft, ob die Angabe des Ports existiert und eine Zahl ist:
if (properties.containsKey("server.port")
&& properties.getProperty("server.port") != null
&& properties.getProperty("server.port").matches("-?\\d+")) {
setPort(Integer.valueOf(properties.getProperty("server.port")));Anschließend wird der Frontend/app Ordner gehostet und ein LoginFilter eingebunden, der verhindert, dass nicht authorizierte Personen auf Pfade zugreifen für die es eine Administratorbrechtigung benötigt. Nun fehlt nur noch die Einbindung der einzelnen REST-Module, auf die der Server die Datenanfrage des Frontends weiterleiten kann. Jedes REST-Modul wird entsprechend der Reihenfolge bei einer Anfrage durchlaufen. Das erste REST-Modul, dass den entsprechend Pfad der Anfrage bereithält wird ausgeführt. Die Beschreibung der einzelnen Funktionalitäten der REST-Module und der dazugehörigen Pfade sind auf seperaten Seiten zu finden.
Am Ende der Spark.java-Klasse ist auch die Funktion zum Stoppen des Spark-Servers vorhanden.
Erhält der Spark-Server über die REST-Schnittstelle eine Anfrage, wird dieser Pfad an die eingebundenen REST-Klassen im de.dhbw.hh.rest package weitergeleitet. Der erste Pfad, der mit dem der Anfrage übereinstimmt, wird ausgeführt. Dabei gibt es vier Verben, die für uns relevant sind:
- get: liest etwas aus
- post: erstellt etwas
- put: aktualisiert etwas
- delete: löscht etwas
Ist eine Übereinstimmung erfolgt, wird über die in der DAOFactory.java-Klasse erstellte Datenbank-Factory auf die Interfaces und denen darin befindenden Methoden zugegriffen. Anschließend wird die Anfrage in einer DAO-Klasse, die das entsprechende Interface mit den dazugehörigen Methoden implementiert hat, bearbeitet.
Diese DAO-Klassen kommunizieren direkt mit der H2-Datenbank. Dafür wird eine Variable zum Connectionpool der Datenbank erstellt: private ComboPooledDataSource cpds;
Hinzu kommt die Erstellung eines Objekts der Klasse in einem Konstruktor, bei der die Variable des Connectionpools als Parameter übergeben wird. Nun folgen die Methoden der einzelnen Datenabfragen. Hierfür werden SQL-Abfrage-Strings erstellt, wie z.B.:
String sql = "SELECT hash, data, top FROM route WHERE top=true";
Dadurch können mittels SELECT Daten aus einer bestimmten Tabelle nach WHERE-Bedingungen abgefragt, mittels DELETE gelöscht, mittels UPDATE aktualisiert oder mittels INSERT hinzugefügt werden, je nach dem gewünschten Verb der Anfrage.
Danach wird eine Connection aus dem Connectionpool geholt und das automatische Commiten verboten. Dies beides sind festgesetzte Standard, die weiter unten erläutert werden.
Anschließend wird ein PreparedStatement erstellt, mit dem der SQL-Abfrage-String übergeben wird und anschließend mittels eines ResultSets ausgeführt wird (Auch das PreparedStatement als auch das ResultSet sind festgelegte Standards, die unten beschreiben werden). Jetzt können die Daten aus der Datenbank geholt werden und in eine Collection geschrieben werden. Diese Collection wird an die REST-Schnittstelle zurückgegeben. Tritt bei dem Vorgang ein Fehler auf, wird eine leere Collection zurückgegeben. (In einer Gruppendiskussion wurde entscheiden, dass Collections als Rückgabe verwendet werden).
In der REST-Schnittstelle wird ein RESTResponse Objekt der Klasse RESTResponse erstellt, in das sowohl der Pfad der Anfrage als Name sowie der aktuelle Rückhgabezeitpunkt eingegeben wird. Nun folgt in einer if-Abfrage eine Überprüfung, ob die von der DAO-Klasse zurückgegebene Collection einen Inhalt enthält oder nicht. Ist ein Inhalt enthalten, wird dieser ebenfalls in das RESTResponse Objekt hinzugefügt zusätzlich zu einer kurzen Beschreibung und dem setSuccess. Bei einer leeren Collection wird ebenfalls eine Beschreibung hinzugefügt, dass die Daten nicht gefunden werden konnten o.ä. und dem setError. Die Variable der Daten wird auf Null gesetzt:
if (toproutes.isEmpty() ){
r.setError();
r.setDescription("Rückgabe-Collection ist leer");
r.setData(null);
}
else {
r.setSuccess();
r.setDescription("Rückgabe Collection enthält Daten");
r.setData(toproutes);
} Zum Schluss wird das RESTResponse-Gson-Objekt zu Json-String umgewandelt und zur Anfrage zurückgegeben:
return gson.toJson(r);Eine standardmäßige Anfrage an die Datenbank kann wie folgt in einem Sequenzdiagramm veranschaulicht werden:

Zu Beginn wird eine Testkonfiguration durchgeführt, in der eine temporäre Inmemory-Datenbank erstellt wird. Zu dieser Datenbank kann eine Connection aus einem vorher hinzugefügten Connectionpool aufgebaut werden. Anschließend können Testdaten in diese temporäre Datenbank hinzugefügt werden, wenn diese für den Test benötigt werden. Wichtig dabei ist nicht die Funktion zu vergessen, die bei einem erneuten Durchlauf des Tests die Änderungen der Datenbank zurücksetzt. Danach kann von der zu testenden Klasse ein Objekt erstellt werden und überprüft werden, ob die richtigen Daten übertragen werden. Für diesen Vorgang muss in der entsprechenden Klasse die zu testende Funktion aufgerufen und die richtigen Parameter mit übergeben werden. So kann z.B. getestet werden, ob bei erfolgreicher Abfrage true zurückgegeben wird:
public void testCheckLogin() {
// Übergabe des Namens und des Passworts an die checkLogin Funktion
boolean user = h2UserDAO.checkLogin("Admin", "Passwort");
// Methode muss true zurückgegen, dann war die Überprüfung des Passworts erfolgreich
assertTrue(user);
}Bei einer Abfrage der Daten aus der Inmemory-Datenbank, müssen die Rückgabewerte abgeglichen werden mit den Werten, die erwartet werden. Dies kann durchgeführt werden wie folgt:
// Abfrage der Routen nach einem Hash-Wert
Route r = h2RouteDAO.findRoute("beispielhashwert");
// Überprüfung, ob der übergebene Datensatz mit dem aus der temporären Inmemory-Datenbank übereinstimmt
assertEquals("beispielhashwert", r.getHash());
assertEquals(false, r.isTop());
assertEquals("Ich bin ein Json String Objekt", r.getData());Wird beim Durchlauf des Maven Tests kein Fehler geworfen, so war der Test erfolgreich und die getestete Funktion zum Abfragen, Hinzufügen oder Bearbeiten der Daten aus der H2 Datenbank oder Foursquare ist korrekt. Nun kann nach dem Starten des lokalen Servers noch die Funktionstüchtigkeit getestet werden, in dem im Browser im Lokalhost, mit dem festgesetzten Port, auf den Pfad zugegriffn und eine Anfrage gestartet wird. Werden die zu erwartenden Werte zurückgegeben, so erfüllt die Funktion ihren Zweck.
Für jede erstellte Datenklasse im models-Ordern muss auch eine Testklasse geschrieben werden. Um die Getter und Setter der Datenklassen zu testen wird ein Objekt der Datenklasse erstellt, z.B. der Datenklasse User:
User u = new User("Max Mustermann");Nun erfolgt eine einfache Abfrage, ob der ins Objekt eingegebene Wert mit dem erwarteten Wert übereinstimmt:
assertEquals("Max Mustermann", u.getName());Hinzu kommt der Test der toString()-Funktion, die in jeder Datenklasse vorhanden sein muss. Hierfür erfolgt ebenfalls eine assertEquals-Abfrage, ob der zurückgegebene String mit dem zu erwartenden String übereinstimmt. Wird beim Durchlauf des Maven Tests kein Fehler geworfen, so ist die Datenklasse korrekt angelegt.
Orientiert wird sich bei den verwendeten Framworks an den JDBC Best Practice. Über die JDBC Schnittstelle soll die Verbindung und die Interaktion mit der Datenbank sichergestellt werden.
PreparedStatement: PreparedStatements ist einer von mehreren Wegen, um in Java über die JDBC Schnittstelle SQL Anfragen an die Datenbank auszuführen. Neben Statement und CallableStatment, die durch Java unterstützt werden, gehört PreparedStatement zu den Best Practice von JDBC. Es ermöglicht dynamische SQL Anfragen an die Datenbank zu senden, in dem mehrere Parameter übergeben werden können. So werden nicht mehrere einzelne Anfragen benötigt. Der größte Vorteil liegt in der Performance. PreparedStatements werden vor-kompiliert in der Datenbank und der Zugriffsplan wird gechacht. Dadurch können die parametischen Anfragen deutlich schneller ausgeführt werden als normale Anfragen, da die Ladezeiten in der Datenbak reduziert werden können.
Read more: Why use PreparedStatement in Java JDBC
ConnectionPool: Die Erzeugung einer JDBC Connection dauert relativ lange, was die Antwortzeit deutlich erhöht. Aus diesem Grund ist es sinnvoll JDBC Connection in einem Pool zu cachen, wodurch unmittelbar ein Datenbankzugriff erfolgen kann.
Disable auto commit: In den JDBC Best Practice wird empfohlen auto commit zu deaktivieren, da so mehrere SQL Statements Transaktionen gruppiert werden können. Folglich muss nicht jedes SQL Statement in einer einzelnen Transaktion übermittelt werden und committet werden, sobald es durchgeführt worde.
resultSet: Mit einem ResultSet können auf Daten über die JDBC API zugegriffen werden mittels einer SELECT Anfrage. Der große Vorteil dabei ist, dass der JDBC Code nicht geändert werden muss, wenn die SELECT Anfrage an die Datenbank sich ändern sollte. Es kann dabei sowohl nach Spalten-Name als auch nach Spalten-Index gesucht werden. Die Suche nach dem Spalten-Index wird meist als schneller empfunden, doch die Suche nach dem Spalten-Namen ist deutlich robuster. Aus diesem Grund wird vom JDBC Best Practice empfohlen Namen im ResultSet Iterator zu verwenden, um auf Spalten zuzugreifen.